dsh-hyperframes 0.3.2 → 0.4.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 (375) hide show
  1. package/README.en.md +4 -3
  2. package/README.md +5 -4
  3. package/lib/index.d.ts +1 -1
  4. package/lib/index.js +1 -0
  5. package/package.json +2 -2
  6. package/skills/embedded-captions/SKILL.md +28 -3
  7. package/skills/embedded-captions/references/aesthetic-principles.md +2 -3
  8. package/skills/embedded-captions/references/rail.md +1 -1
  9. package/skills/embedded-captions/scripts/check-occlusion.cjs +4 -1
  10. package/skills/embedded-captions/scripts/check-overflow.cjs +1 -0
  11. package/skills/embedded-captions/scripts/check-rail-climax.cjs +5 -1
  12. package/skills/embedded-captions/scripts/fixtures/heroless/theme.json +11 -0
  13. package/skills/embedded-captions/scripts/hf-cli.cjs +33 -0
  14. package/skills/embedded-captions/scripts/make-theme.cjs +95 -57
  15. package/skills/embedded-captions/scripts/make-theme.test.mjs +131 -0
  16. package/skills/embedded-captions/scripts/matte.cjs +1 -13
  17. package/skills/embedded-captions/scripts/measure-layout.cjs +2 -1
  18. package/skills/embedded-captions/scripts/preview-frames.cjs +39 -11
  19. package/skills/embedded-captions/scripts/preview-frames.test.mjs +24 -0
  20. package/skills/embedded-captions/scripts/render-and-composite.sh +8 -52
  21. package/skills/embedded-captions/scripts/safe-zones.cjs +4 -1
  22. package/skills/embedded-captions/scripts/transcribe.cjs +5 -14
  23. package/skills/embedded-captions/scripts/transcribe.test.mjs +41 -0
  24. package/skills/faceless-explainer/SKILL.md +20 -16
  25. package/skills/faceless-explainer/references/story-design.md +1 -1
  26. package/skills/faceless-explainer/scripts/assemble-index.mjs +6 -4
  27. package/skills/faceless-explainer/scripts/assemble-index.test.mjs +57 -2
  28. package/skills/faceless-explainer/scripts/audio.mjs +2 -0
  29. package/skills/faceless-explainer/scripts/build-frame.mjs +6 -3
  30. package/skills/faceless-explainer/scripts/captions.mjs +7 -4
  31. package/skills/faceless-explainer/scripts/frame-packets.mjs +4 -4
  32. package/skills/faceless-explainer/scripts/lib/bgm-volume.mjs +4 -0
  33. package/skills/faceless-explainer/scripts/lib/captured-fonts.mjs +58 -0
  34. package/skills/{hyperframes-core → faceless-explainer}/scripts/lib/frame-packets-core.mjs +5 -23
  35. package/skills/faceless-explainer/scripts/lib/storyboard.mjs +1 -1
  36. package/skills/faceless-explainer/scripts/lib/transitions.json +3 -3
  37. package/skills/faceless-explainer/scripts/transitions.mjs +1 -0
  38. package/skills/faceless-explainer/scripts/transitions.test.mjs +2 -0
  39. package/skills/faceless-explainer/sub-agents/frame-worker.md +1 -1
  40. package/skills/figma/SKILL.md +2 -0
  41. package/skills/general-video/SKILL.md +13 -11
  42. package/skills/general-video/scripts/frame-packets.mjs +4 -4
  43. package/skills/general-video/scripts/lib/frame-packets-core.mjs +194 -0
  44. package/skills/general-video/sub-agents/frame-worker.md +1 -1
  45. package/skills/hyperframes/SKILL.md +40 -30
  46. package/skills/{hyperframes-core → hyperframes}/references/brief-contract.md +15 -15
  47. package/skills/{hyperframes-core → hyperframes}/references/brief-format.md +8 -8
  48. package/skills/hyperframes/references/capability-menu.md +2 -2
  49. package/skills/{hyperframes-core → hyperframes}/references/frame-worker-core.md +1 -1
  50. package/skills/hyperframes/references/intent-interview.md +16 -16
  51. package/skills/hyperframes/references/plugin-installation.md +43 -0
  52. package/skills/{hyperframes-core → hyperframes}/references/production-loop.md +11 -11
  53. package/skills/hyperframes/references/review-loop.md +37 -0
  54. package/skills/hyperframes/references/route-briefs.md +1 -1
  55. package/skills/hyperframes/references/routes/motion-graphics.md +1 -1
  56. package/skills/{hyperframes-core → hyperframes}/references/script-format.md +3 -3
  57. package/skills/hyperframes/references/skill-lifecycle.md +2 -0
  58. package/skills/{hyperframes-core → hyperframes}/references/storyboard-format.md +3 -34
  59. package/skills/hyperframes/scripts/lib/frame-packets-core.mjs +194 -0
  60. package/skills/hyperframes/scripts/plugin-cli.mjs +88 -0
  61. package/skills/hyperframes/scripts/plugin-cli.test.mjs +117 -0
  62. package/skills/hyperframes-animation/SKILL.md +5 -1
  63. package/skills/hyperframes-animation/adapters/gsap.md +1 -1
  64. package/skills/hyperframes-animation/adapters/lottie.md +15 -2
  65. package/skills/hyperframes-animation/adapters/three.md +14 -0
  66. package/skills/hyperframes-animation/blueprints/titlecard-reveal.md +1 -1
  67. package/skills/hyperframes-animation/references/motion-blur.md +133 -0
  68. package/skills/hyperframes-animation/rules/cursor-click-ripple.md +1 -1
  69. package/skills/hyperframes-animation/rules/dynamic-content-sequencing.md +1 -1
  70. package/skills/hyperframes-animation/rules/hacker-flip-3d.md +1 -1
  71. package/skills/hyperframes-animation/rules/kinetic-beat-slam.md +2 -2
  72. package/skills/hyperframes-animation/rules/motion-blur-streak.md +1 -1
  73. package/skills/hyperframes-animation/rules/vertical-spring-ticker.md +1 -1
  74. package/skills/hyperframes-animation/scripts/package-loader.mjs +47 -16
  75. package/skills/hyperframes-animation/scripts/package-loader.test.mjs +52 -0
  76. package/skills/hyperframes-animation/techniques.md +1 -1
  77. package/skills/hyperframes-animation/transitions/TRANSITION-REGISTRY.md +3 -3
  78. package/skills/hyperframes-animation/transitions/css-push.md +4 -4
  79. package/skills/hyperframes-audio/SKILL.md +30 -22
  80. package/skills/hyperframes-audio/scripts/carve.mjs +6 -3
  81. package/skills/hyperframes-audio/scripts/carve.test.mjs +16 -1
  82. package/skills/hyperframes-cli/SKILL.md +44 -35
  83. package/skills/hyperframes-cli/references/init-and-scaffold.md +8 -6
  84. package/skills/hyperframes-cli/references/lint-validate-inspect.md +3 -3
  85. package/skills/hyperframes-cli/references/preview-render.md +35 -32
  86. package/skills/hyperframes-cli/references/upgrade-info-misc.md +50 -1
  87. package/skills/hyperframes-core/SKILL.md +30 -24
  88. package/skills/hyperframes-core/references/composition-patterns.md +3 -42
  89. package/skills/hyperframes-core/references/creator-editing-recipes.md +131 -7
  90. package/skills/hyperframes-core/references/data-attributes.md +11 -11
  91. package/skills/hyperframes-core/references/determinism-rules.md +2 -19
  92. package/skills/hyperframes-core/references/full-screen-motion.md +0 -9
  93. package/skills/hyperframes-core/references/minimal-composition.md +2 -2
  94. package/skills/hyperframes-core/references/sub-compositions.md +3 -66
  95. package/skills/hyperframes-core/references/tailwind.md +1 -20
  96. package/skills/hyperframes-core/references/tracks-and-clips.md +2 -6
  97. package/skills/hyperframes-core/references/variables-and-media.md +3 -3
  98. package/skills/hyperframes-creative/SKILL.md +2 -0
  99. package/skills/hyperframes-creative/references/house-style.md +15 -15
  100. package/skills/hyperframes-creative/references/story-spine.md +3 -1
  101. package/skills/hyperframes-creative/references/storyboard-recipe.md +57 -0
  102. package/skills/hyperframes-creative/references/typography.md +1 -1
  103. package/skills/hyperframes-creative/scripts/package-loader.mjs +47 -16
  104. package/skills/hyperframes-creative/scripts/package-loader.test.mjs +52 -0
  105. package/skills/hyperframes-keyframes/SKILL.md +4 -2
  106. package/skills/hyperframes-registry/SKILL.md +3 -1
  107. package/skills/hyperframes-registry/references/contributing.md +8 -2
  108. package/skills/hyperframes-registry/references/discovery.md +20 -34
  109. package/skills/hyperframes-studio/SKILL.md +77 -0
  110. package/skills/media-use/SKILL.md +14 -12
  111. package/skills/media-use/audio/references/captions/authoring.md +4 -0
  112. package/skills/media-use/audio/references/remove-background.md +1 -1
  113. package/skills/media-use/audio/references/tts-to-captions.md +11 -2
  114. package/skills/media-use/audio/references/tts.md +84 -6
  115. package/skills/media-use/audio/scripts/audio.mjs +15 -6
  116. package/skills/media-use/audio/scripts/gemini-pipeline.test.mjs +113 -0
  117. package/skills/media-use/audio/scripts/lib/audio-meta.mjs +39 -0
  118. package/skills/media-use/audio/scripts/lib/audio-meta.test.mjs +136 -0
  119. package/skills/media-use/audio/scripts/lib/bgm-volume.mjs +4 -0
  120. package/skills/media-use/audio/scripts/lib/bgm.mjs +2 -6
  121. package/skills/media-use/audio/scripts/lib/gemini-auth.mjs +43 -0
  122. package/skills/media-use/audio/scripts/lib/gemini-auth.py +52 -0
  123. package/skills/media-use/audio/scripts/lib/gemini-auth.test.mjs +86 -0
  124. package/skills/media-use/audio/scripts/lib/gemini-auth_test.py +90 -0
  125. package/skills/media-use/audio/scripts/lib/gemini-tts.mjs +122 -0
  126. package/skills/media-use/audio/scripts/lib/gemini-tts.test.mjs +243 -0
  127. package/skills/media-use/audio/scripts/lib/heygen.mjs +2 -1
  128. package/skills/media-use/audio/scripts/lib/tts.mjs +15 -3
  129. package/skills/media-use/audio/scripts/wait-bgm.mjs +8 -4
  130. package/skills/media-use/audio/scripts/wait-bgm.test.mjs +65 -0
  131. package/skills/media-use/references/audio.md +3 -2
  132. package/skills/media-use/references/grading.md +7 -7
  133. package/skills/media-use/references/media-treatment-recipes.md +1 -1
  134. package/skills/media-use/references/memory.md +2 -2
  135. package/skills/media-use/references/meta.md +2 -2
  136. package/skills/media-use/references/operations.md +15 -13
  137. package/skills/media-use/references/resolve.md +22 -11
  138. package/skills/media-use/references/setup-providers.md +11 -11
  139. package/skills/media-use/scripts/audio-duck.mjs +19 -27
  140. package/skills/media-use/scripts/compatibility.test.mjs +96 -0
  141. package/skills/media-use/scripts/lib/config-lock.mjs +45 -0
  142. package/skills/media-use/scripts/lib/duck.mjs +16 -10
  143. package/skills/media-use/scripts/lib/media-fetch.mjs +85 -0
  144. package/skills/media-use/scripts/lib/media-home.mjs +20 -0
  145. package/skills/media-use/scripts/lib/npx-sync.mjs +28 -2
  146. package/skills/media-use/scripts/lib/prefs-store.mjs +2 -2
  147. package/skills/media-use/scripts/lib/recipe-store.mjs +2 -2
  148. package/skills/media-use/scripts/lib/telemetry.mjs +29 -41
  149. package/skills/media-use/scripts/lib/transcriptCutFade.mjs +21 -0
  150. package/skills/media-use/scripts/prefs.mjs +1 -1
  151. package/skills/media-use/scripts/recipe.mjs +1 -1
  152. package/skills/media-use/scripts/resolve-plugin.test.mjs +62 -0
  153. package/skills/media-use/scripts/resolve.mjs +27 -1267
  154. package/skills/media-use/scripts/transcribe.mjs +39 -25
  155. package/skills/media-use/scripts/transcribe.test.mjs +112 -0
  156. package/skills/media-use/scripts/transcript-cut.mjs +39 -8
  157. package/skills/media-use/scripts/transcript-cut.test.mjs +117 -0
  158. package/skills/motion-graphics/SKILL.md +3 -1
  159. package/skills/motion-graphics/agents/builder.md +1 -0
  160. package/skills/motion-graphics/agents/director.md +2 -1
  161. package/skills/motion-graphics/catalog-map.md +25 -13
  162. package/skills/motion-graphics/categories/charts/module.md +2 -1
  163. package/skills/motion-graphics/categories/lower-thirds/module.md +4 -0
  164. package/skills/motion-graphics/categories/news/module.md +1 -1
  165. package/skills/motion-graphics/references/motion-vocabulary.md +4 -0
  166. package/skills/music-to-video/SKILL.md +8 -6
  167. package/skills/music-to-video/references/montage.md +3 -6
  168. package/skills/music-to-video/references/motion-primitives/3d-card-flip/index.html +10 -111
  169. package/skills/music-to-video/references/motion-primitives/3d-card-flip/scene.html +119 -0
  170. package/skills/music-to-video/references/motion-primitives/bg-flow-field/index.html +11 -284
  171. package/skills/music-to-video/references/motion-primitives/bg-flow-field/scene.html +294 -0
  172. package/skills/music-to-video/references/motion-primitives/binary-decrypt/index.html +10 -76
  173. package/skills/music-to-video/references/motion-primitives/binary-decrypt/scene.html +85 -0
  174. package/skills/music-to-video/references/motion-primitives/blur-resolve/index.html +10 -52
  175. package/skills/music-to-video/references/motion-primitives/blur-resolve/scene.html +60 -0
  176. package/skills/music-to-video/references/motion-primitives/braam-punch/index.html +10 -92
  177. package/skills/music-to-video/references/motion-primitives/braam-punch/scene.html +100 -0
  178. package/skills/music-to-video/references/motion-primitives/chromatic-split/index.html +10 -103
  179. package/skills/music-to-video/references/motion-primitives/chromatic-split/scene.html +111 -0
  180. package/skills/music-to-video/references/motion-primitives/chrome-sweep/index.html +10 -60
  181. package/skills/music-to-video/references/motion-primitives/chrome-sweep/scene.html +68 -0
  182. package/skills/music-to-video/references/motion-primitives/counting-punch/index.html +10 -110
  183. package/skills/music-to-video/references/motion-primitives/counting-punch/scene.html +119 -0
  184. package/skills/music-to-video/references/motion-primitives/crash-zoom-in/index.html +10 -112
  185. package/skills/music-to-video/references/motion-primitives/crash-zoom-in/scene.html +120 -0
  186. package/skills/music-to-video/references/motion-primitives/datamosh-smear/index.html +10 -121
  187. package/skills/music-to-video/references/motion-primitives/datamosh-smear/scene.html +131 -0
  188. package/skills/music-to-video/references/motion-primitives/directional-fill/index.html +10 -90
  189. package/skills/music-to-video/references/motion-primitives/directional-fill/scene.html +103 -0
  190. package/skills/music-to-video/references/motion-primitives/dolly-zoom/index.html +10 -94
  191. package/skills/music-to-video/references/motion-primitives/dolly-zoom/scene.html +102 -0
  192. package/skills/music-to-video/references/motion-primitives/electric-arc/index.html +10 -100
  193. package/skills/music-to-video/references/motion-primitives/electric-arc/scene.html +105 -0
  194. package/skills/music-to-video/references/motion-primitives/flash-cut/index.html +10 -89
  195. package/skills/music-to-video/references/motion-primitives/flash-cut/scene.html +97 -0
  196. package/skills/music-to-video/references/motion-primitives/gooey-metaball/index.html +10 -197
  197. package/skills/music-to-video/references/motion-primitives/gooey-metaball/scene.html +206 -0
  198. package/skills/music-to-video/references/motion-primitives/hard-cut/index.html +10 -74
  199. package/skills/music-to-video/references/motion-primitives/hard-cut/scene.html +82 -0
  200. package/skills/music-to-video/references/motion-primitives/hypercut-whip/index.html +10 -36
  201. package/skills/music-to-video/references/motion-primitives/hypercut-whip/scene.html +44 -0
  202. package/skills/music-to-video/references/motion-primitives/iris-open/index.html +10 -61
  203. package/skills/music-to-video/references/motion-primitives/iris-open/scene.html +69 -0
  204. package/skills/music-to-video/references/motion-primitives/kinetic-letter-in/index.html +10 -55
  205. package/skills/music-to-video/references/motion-primitives/kinetic-letter-in/scene.html +64 -0
  206. package/skills/music-to-video/references/motion-primitives/liquid-morph/index.html +10 -88
  207. package/skills/music-to-video/references/motion-primitives/liquid-morph/scene.html +96 -0
  208. package/skills/music-to-video/references/motion-primitives/mask-reveal/index.html +10 -66
  209. package/skills/music-to-video/references/motion-primitives/mask-reveal/scene.html +74 -0
  210. package/skills/music-to-video/references/motion-primitives/mosaic-pack/index.html +10 -81
  211. package/skills/music-to-video/references/motion-primitives/mosaic-pack/scene.html +89 -0
  212. package/skills/music-to-video/references/motion-primitives/neon-flicker/index.html +10 -65
  213. package/skills/music-to-video/references/motion-primitives/neon-flicker/scene.html +73 -0
  214. package/skills/music-to-video/references/motion-primitives/outline-to-fill/index.html +10 -74
  215. package/skills/music-to-video/references/motion-primitives/outline-to-fill/scene.html +82 -0
  216. package/skills/music-to-video/references/motion-primitives/palette-flip/index.html +10 -89
  217. package/skills/music-to-video/references/motion-primitives/palette-flip/scene.html +97 -0
  218. package/skills/music-to-video/references/motion-primitives/particle-burst/index.html +10 -98
  219. package/skills/music-to-video/references/motion-primitives/particle-burst/scene.html +106 -0
  220. package/skills/music-to-video/references/motion-primitives/pixel-dissolve/index.html +10 -78
  221. package/skills/music-to-video/references/motion-primitives/pixel-dissolve/scene.html +86 -0
  222. package/skills/music-to-video/references/motion-primitives/radial-burst-lines/index.html +10 -117
  223. package/skills/music-to-video/references/motion-primitives/radial-burst-lines/scene.html +130 -0
  224. package/skills/music-to-video/references/motion-primitives/screen-shake/index.html +10 -70
  225. package/skills/music-to-video/references/motion-primitives/screen-shake/scene.html +78 -0
  226. package/skills/music-to-video/references/motion-primitives/slot-machine-reveal/index.html +10 -87
  227. package/skills/music-to-video/references/motion-primitives/slot-machine-reveal/scene.html +100 -0
  228. package/skills/music-to-video/references/motion-primitives/spotlight-sweep/index.html +10 -94
  229. package/skills/music-to-video/references/motion-primitives/spotlight-sweep/scene.html +102 -0
  230. package/skills/music-to-video/references/motion-primitives/staggered-exit/index.html +10 -74
  231. package/skills/music-to-video/references/motion-primitives/staggered-exit/scene.html +82 -0
  232. package/skills/music-to-video/references/motion-primitives/text-spectral-rays/index.html +11 -271
  233. package/skills/music-to-video/references/motion-primitives/text-spectral-rays/scene.html +280 -0
  234. package/skills/music-to-video/references/motion-primitives/text-wave-distort/index.html +10 -92
  235. package/skills/music-to-video/references/motion-primitives/text-wave-distort/scene.html +106 -0
  236. package/skills/music-to-video/references/motion-primitives/tile-mosaic/index.html +10 -101
  237. package/skills/music-to-video/references/motion-primitives/tile-mosaic/scene.html +109 -0
  238. package/skills/music-to-video/references/motion-primitives/typewriter-reveal/index.html +10 -79
  239. package/skills/music-to-video/references/motion-primitives/typewriter-reveal/scene.html +91 -0
  240. package/skills/music-to-video/references/motion-primitives/word-grid-burst/index.html +10 -83
  241. package/skills/music-to-video/references/motion-primitives/word-grid-burst/scene.html +96 -0
  242. package/skills/music-to-video/references/planning.md +2 -2
  243. package/skills/music-to-video/scripts/lib/storyboard.mjs +1 -1
  244. package/skills/music-to-video/sub-agents/frame-worker.md +2 -2
  245. package/skills/pr-to-video/SKILL.md +20 -16
  246. package/skills/pr-to-video/references/code-vocabulary.md +14 -12
  247. package/skills/pr-to-video/references/story-design.md +1 -1
  248. package/skills/pr-to-video/scripts/assemble-index.mjs +6 -4
  249. package/skills/pr-to-video/scripts/assemble-index.test.mjs +57 -2
  250. package/skills/pr-to-video/scripts/audio.mjs +2 -0
  251. package/skills/pr-to-video/scripts/build-frame.mjs +6 -3
  252. package/skills/pr-to-video/scripts/captions.mjs +7 -4
  253. package/skills/pr-to-video/scripts/captions.test.mjs +41 -0
  254. package/skills/pr-to-video/scripts/frame-packets.mjs +4 -4
  255. package/skills/pr-to-video/scripts/lib/bgm-volume.mjs +4 -0
  256. package/skills/pr-to-video/scripts/lib/captured-fonts.mjs +58 -0
  257. package/skills/pr-to-video/scripts/lib/frame-packets-core.mjs +194 -0
  258. package/skills/pr-to-video/scripts/lib/storyboard.mjs +1 -1
  259. package/skills/pr-to-video/scripts/lib/transitions.json +3 -3
  260. package/skills/pr-to-video/scripts/transitions.mjs +7 -5
  261. package/skills/pr-to-video/sub-agents/frame-worker.md +1 -1
  262. package/skills/product-launch-video/SKILL.md +20 -16
  263. package/skills/product-launch-video/references/story-design.md +1 -1
  264. package/skills/product-launch-video/scripts/assemble-index.mjs +6 -4
  265. package/skills/product-launch-video/scripts/assemble-index.test.mjs +57 -2
  266. package/skills/product-launch-video/scripts/audio.mjs +2 -0
  267. package/skills/product-launch-video/scripts/audio.test.mjs +16 -0
  268. package/skills/product-launch-video/scripts/build-frame-subsets.test.mjs +152 -0
  269. package/skills/product-launch-video/scripts/build-frame.mjs +6 -3
  270. package/skills/product-launch-video/scripts/captions.mjs +7 -4
  271. package/skills/product-launch-video/scripts/frame-packets.mjs +4 -4
  272. package/skills/product-launch-video/scripts/lib/bgm-volume.mjs +4 -0
  273. package/skills/product-launch-video/scripts/lib/captured-fonts.mjs +58 -0
  274. package/skills/product-launch-video/scripts/lib/frame-packets-core.mjs +194 -0
  275. package/skills/product-launch-video/scripts/lib/storyboard.mjs +1 -1
  276. package/skills/product-launch-video/scripts/lib/transitions.json +3 -3
  277. package/skills/product-launch-video/scripts/transitions.mjs +7 -5
  278. package/skills/product-launch-video/scripts/transitions.test.mjs +68 -0
  279. package/skills/product-launch-video/sub-agents/frame-worker.md +1 -1
  280. package/skills/remotion-to-hyperframes/SKILL.md +7 -4
  281. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/expected.json +5 -5
  282. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/hf-src/compositions/title.html +31 -0
  283. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/hf-src/index.html +10 -25
  284. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/README.md +8 -8
  285. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/expected.json +9 -9
  286. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/hf-src/compositions/scene-1.html +35 -0
  287. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/hf-src/compositions/scene-2.html +35 -0
  288. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/hf-src/compositions/scene-3.html +36 -0
  289. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/hf-src/index.html +28 -71
  290. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/expected.json +3 -3
  291. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/hf-src/compositions/scene-1.html +52 -0
  292. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/hf-src/compositions/scene-2.html +99 -0
  293. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/hf-src/compositions/scene-3.html +55 -0
  294. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/hf-src/index.html +13 -212
  295. package/skills/remotion-to-hyperframes/references/api-map.md +16 -16
  296. package/skills/remotion-to-hyperframes/references/limitations.md +7 -7
  297. package/skills/remotion-to-hyperframes/references/lottie.md +5 -7
  298. package/skills/remotion-to-hyperframes/references/media.md +3 -3
  299. package/skills/remotion-to-hyperframes/references/parameters.md +5 -0
  300. package/skills/remotion-to-hyperframes/references/sequencing.md +71 -18
  301. package/skills/slideshow/SKILL.md +4 -1
  302. package/skills/slideshow/references/standalone-harness.md +0 -3
  303. package/skills/talking-head-recut/SKILL.md +10 -1
  304. package/skills/hyperframes-core/references/review-loop.md +0 -41
  305. package/skills/media-use/scripts/lib/adopt.mjs +0 -128
  306. package/skills/media-use/scripts/lib/adopt.test.mjs +0 -95
  307. package/skills/media-use/scripts/lib/bgm-provider.mjs +0 -20
  308. package/skills/media-use/scripts/lib/brand-provider.mjs +0 -59
  309. package/skills/media-use/scripts/lib/bundled-sfx-provider.mjs +0 -108
  310. package/skills/media-use/scripts/lib/bundled-sfx-provider.test.mjs +0 -85
  311. package/skills/media-use/scripts/lib/cache.mjs +0 -150
  312. package/skills/media-use/scripts/lib/candidates.mjs +0 -91
  313. package/skills/media-use/scripts/lib/candidates.test.mjs +0 -155
  314. package/skills/media-use/scripts/lib/codex-provider.mjs +0 -161
  315. package/skills/media-use/scripts/lib/codex-provider.test.mjs +0 -34
  316. package/skills/media-use/scripts/lib/coverage.test.mjs +0 -117
  317. package/skills/media-use/scripts/lib/cube-build.mjs +0 -181
  318. package/skills/media-use/scripts/lib/cube-build.test.mjs +0 -80
  319. package/skills/media-use/scripts/lib/cube-validate.mjs +0 -198
  320. package/skills/media-use/scripts/lib/cube-validate.test.mjs +0 -125
  321. package/skills/media-use/scripts/lib/cutlist.test.mjs +0 -148
  322. package/skills/media-use/scripts/lib/duck.test.mjs +0 -118
  323. package/skills/media-use/scripts/lib/error-diffusion.test.mjs +0 -102
  324. package/skills/media-use/scripts/lib/freeze.mjs +0 -70
  325. package/skills/media-use/scripts/lib/freeze.test.mjs +0 -46
  326. package/skills/media-use/scripts/lib/grade-analyzer.mjs +0 -264
  327. package/skills/media-use/scripts/lib/grade-analyzer.test.mjs +0 -285
  328. package/skills/media-use/scripts/lib/heygen-cli.mjs +0 -191
  329. package/skills/media-use/scripts/lib/heygen-cli.test.mjs +0 -301
  330. package/skills/media-use/scripts/lib/heygen-search.mjs +0 -45
  331. package/skills/media-use/scripts/lib/heygen-search.test.mjs +0 -49
  332. package/skills/media-use/scripts/lib/heygen-video-provider.mjs +0 -127
  333. package/skills/media-use/scripts/lib/heygen-video-provider.test.mjs +0 -344
  334. package/skills/media-use/scripts/lib/image-provider.mjs +0 -44
  335. package/skills/media-use/scripts/lib/local-models.mjs +0 -318
  336. package/skills/media-use/scripts/lib/local-models.test.mjs +0 -220
  337. package/skills/media-use/scripts/lib/local-run.mjs +0 -78
  338. package/skills/media-use/scripts/lib/local-run.test.mjs +0 -121
  339. package/skills/media-use/scripts/lib/logo-provider.mjs +0 -222
  340. package/skills/media-use/scripts/lib/logo-provider.test.mjs +0 -137
  341. package/skills/media-use/scripts/lib/ltx-video-provider.mjs +0 -94
  342. package/skills/media-use/scripts/lib/ltx-video-provider.test.mjs +0 -316
  343. package/skills/media-use/scripts/lib/lut-preset-provider.mjs +0 -266
  344. package/skills/media-use/scripts/lib/lut-preset-provider.test.mjs +0 -176
  345. package/skills/media-use/scripts/lib/manifest.test.mjs +0 -388
  346. package/skills/media-use/scripts/lib/match.mjs +0 -47
  347. package/skills/media-use/scripts/lib/mflux-provider.mjs +0 -128
  348. package/skills/media-use/scripts/lib/mflux-provider.test.mjs +0 -174
  349. package/skills/media-use/scripts/lib/misses.mjs +0 -49
  350. package/skills/media-use/scripts/lib/misses.test.mjs +0 -78
  351. package/skills/media-use/scripts/lib/npx-sync.test.mjs +0 -50
  352. package/skills/media-use/scripts/lib/parakeet-words.test.mjs +0 -44
  353. package/skills/media-use/scripts/lib/prefs-store.test.mjs +0 -189
  354. package/skills/media-use/scripts/lib/probe.mjs +0 -39
  355. package/skills/media-use/scripts/lib/probe.test.mjs +0 -31
  356. package/skills/media-use/scripts/lib/providers.mjs +0 -5
  357. package/skills/media-use/scripts/lib/recipe-store.test.mjs +0 -367
  358. package/skills/media-use/scripts/lib/registry.mjs +0 -218
  359. package/skills/media-use/scripts/lib/registry.test.mjs +0 -261
  360. package/skills/media-use/scripts/lib/search.mjs +0 -24
  361. package/skills/media-use/scripts/lib/search.test.mjs +0 -32
  362. package/skills/media-use/scripts/lib/sfx-provider.mjs +0 -23
  363. package/skills/media-use/scripts/lib/specs.mjs +0 -132
  364. package/skills/media-use/scripts/lib/specs.test.mjs +0 -157
  365. package/skills/media-use/scripts/lib/stats.mjs +0 -117
  366. package/skills/media-use/scripts/lib/stats.test.mjs +0 -165
  367. package/skills/media-use/scripts/lib/telemetry.test.mjs +0 -373
  368. package/skills/media-use/scripts/lib/tts-local-provider.mjs +0 -92
  369. package/skills/media-use/scripts/lib/tts-local-provider.test.mjs +0 -72
  370. package/skills/media-use/scripts/lib/usage.mjs +0 -57
  371. package/skills/media-use/scripts/lib/usage.test.mjs +0 -54
  372. package/skills/media-use/scripts/lib/voice-provider.mjs +0 -57
  373. package/skills/media-use/scripts/lib/voice-provider.test.mjs +0 -46
  374. package/skills/media-use/scripts/resolve.test.mjs +0 -1120
  375. /package/skills/{hyperframes-core → hyperframes}/references/subagent-dispatch.md +0 -0
@@ -1,316 +0,0 @@
1
- import { test } from "node:test";
2
- import assert from "node:assert/strict";
3
- import { dirname } from "node:path";
4
- import { tmpdir } from "node:os";
5
- import { ltxVideoGenerate } from "./ltx-video-provider.mjs";
6
-
7
- const fittingSpecs = { availableRamMB: 20000, gpu: { present: true } };
8
-
9
- test("no fitting local model: falls through without checking for a binary", async (t) => {
10
- t.mock.method(console, "error", () => {});
11
- const calls = [];
12
-
13
- const result = await ltxVideoGenerate(
14
- "a calm ocean wave at sunset",
15
- { specs: { availableRamMB: 100, gpu: { present: true } } },
16
- (...call) => calls.push(call),
17
- () => true,
18
- );
19
-
20
- assert.equal(result, null);
21
- assert.deepEqual(calls, []);
22
- });
23
-
24
- test("binary missing from PATH: prints the model install hint and falls through", async (t) => {
25
- const errors = [];
26
- t.mock.method(console, "error", (message) => errors.push(message));
27
- const calls = [];
28
- const fakeExec = (...call) => {
29
- calls.push(call);
30
- throw new Error("not found");
31
- };
32
-
33
- const result = await ltxVideoGenerate(
34
- "a calm ocean wave at sunset",
35
- { specs: fittingSpecs },
36
- fakeExec,
37
- );
38
-
39
- assert.equal(result, null);
40
- assert.equal(calls.length, 1);
41
- assert.deepEqual(calls[0].slice(0, 2), ["which", ["ltx-2-mlx"]]);
42
- assert.equal(errors.length, 1);
43
- assert.match(errors[0], /git clone https:\/\/github\.com\/dgrauet\/ltx-2-mlx/);
44
- // the install hint is the accept moment: say what the pull costs
45
- assert.match(errors[0], /GB of weights to/);
46
- });
47
-
48
- test("generate argv substitutes a spaced prompt after tokenizing and uses verified defaults", async () => {
49
- const calls = [];
50
- const checkedPaths = [];
51
- const fakeExec = (...call) => calls.push(call);
52
- const pathExists = (path) => {
53
- checkedPaths.push(path);
54
- return false;
55
- };
56
- const intent = "a calm ocean wave at sunset";
57
-
58
- const result = await ltxVideoGenerate(intent, { specs: fittingSpecs }, fakeExec, pathExists);
59
-
60
- assert.equal(result, null);
61
- assert.equal(calls.length, 2);
62
- const [bin, argv, opts] = calls[1];
63
- assert.equal(bin, "ltx-2-mlx");
64
- assert.equal(opts.timeout, 1_800_000);
65
-
66
- const expectedPairs = [
67
- ["--prompt", intent],
68
- ["--width", "512"],
69
- ["--height", "320"],
70
- ["--frames", "33"],
71
- ["--output", checkedPaths[0]],
72
- ];
73
- let previousIndex = -1;
74
- for (const [flag, value] of expectedPairs) {
75
- const index = argv.indexOf(flag);
76
- assert.ok(index > previousIndex, `${flag} should follow the previous required option`);
77
- assert.equal(argv[index + 1], value);
78
- previousIndex = index;
79
- }
80
- assert.equal(argv.filter((arg) => arg === intent).length, 1);
81
- });
82
-
83
- test("successful generation returns the generated MP4 result", async () => {
84
- const calls = [];
85
- const fakeExec = (...call) => calls.push(call);
86
- const intent = "a calm ocean wave at sunset";
87
-
88
- const result = await ltxVideoGenerate(intent, { specs: fittingSpecs }, fakeExec, () => true);
89
-
90
- assert.ok(result);
91
- assert.equal(calls.length, 2);
92
- assert.equal(dirname(result.localPath), tmpdir());
93
- assert.match(result.localPath, /media-use-ltx-\d+-\d+\.mp4$/);
94
- assert.deepEqual(result, {
95
- localPath: result.localPath,
96
- ext: ".mp4",
97
- source: "generated",
98
- metadata: {
99
- description: intent,
100
- provider: "ltx.local",
101
- provenance: { prompt: intent },
102
- },
103
- });
104
- });
105
-
106
- test("generate failure returns null instead of throwing", async (t) => {
107
- t.mock.method(console, "error", () => {});
108
- let calls = 0;
109
- const fakeExec = () => {
110
- calls += 1;
111
- if (calls === 2) {
112
- const error = new Error("generation failed");
113
- error.stderr = "LTX failed";
114
- throw error;
115
- }
116
- };
117
-
118
- const result = await ltxVideoGenerate(
119
- "storm clouds",
120
- { specs: fittingSpecs },
121
- fakeExec,
122
- () => true,
123
- );
124
-
125
- assert.equal(result, null);
126
- assert.equal(calls, 2);
127
- });
128
-
129
- test("missing generated output returns null and says so", async (t) => {
130
- const errors = [];
131
- t.mock.method(console, "error", (message) => errors.push(message));
132
-
133
- const result = await ltxVideoGenerate(
134
- "storm clouds",
135
- { specs: fittingSpecs },
136
- () => {},
137
- () => false,
138
- );
139
-
140
- assert.equal(result, null);
141
- assert.equal(errors.length, 1);
142
- assert.match(errors[0], /wrote no output file/);
143
- });
144
-
145
- // 40GB clears BOTH videogen tiers, so the ladder has two rungs. `fittingSpecs`
146
- // above sits under the large tier's floor on purpose: every other test in this
147
- // file exercises the medium tier alone, which is precisely why a broken large
148
- // tier could sit in the table unnoticed.
149
- const bothTiersSpecs = { availableRamMB: 40000, gpu: { present: true } };
150
-
151
- const isGenerate = (call) => call[0] !== "which";
152
-
153
- test("a top tier that cannot run demotes to the next fitting tier", async (t) => {
154
- const errors = [];
155
- t.mock.method(console, "error", (message) => errors.push(message));
156
- const calls = [];
157
- // The runner is installed, but the large tier's weights are gated: the
158
- // download 401s and `generate` exits non-zero. The medium tier then works.
159
- const fakeExec = (...call) => {
160
- calls.push(call);
161
- if (isGenerate(call) && call[1].includes("dgrauet/ltx-2.3-mlx-q8")) {
162
- const err = new Error("exit 1");
163
- err.stderr = "401 Client Error: Unauthorized for url: .../ltx-2.3-mlx-q8";
164
- throw err;
165
- }
166
- };
167
-
168
- const result = await ltxVideoGenerate(
169
- "storm clouds",
170
- { specs: bothTiersSpecs },
171
- fakeExec,
172
- () => true,
173
- );
174
-
175
- assert.ok(result, "the medium tier still produced a video");
176
- const generated = calls.filter(isGenerate).map((call) => call[1].join(" "));
177
- assert.equal(generated.length, 2, "large attempted first, then medium");
178
- assert.match(generated[0], /dgrauet\/ltx-2\.3-mlx-q8/);
179
- assert.match(generated[1], /dgrauet\/ltx-2\.3-mlx-q4/);
180
- // the demotion is reported, never silent: a smaller model changes the output
181
- assert.equal(errors.length, 1);
182
- assert.match(errors[0], /ltx-2\.3-mlx-q8\) failed/);
183
- assert.match(errors[0], /401/);
184
- });
185
-
186
- test("every fitting tier failing returns null, one reason per tier", async (t) => {
187
- const errors = [];
188
- t.mock.method(console, "error", (message) => errors.push(message));
189
- const fakeExec = (...call) => {
190
- if (isGenerate(call)) throw new Error("mlx out of memory");
191
- };
192
-
193
- const result = await ltxVideoGenerate(
194
- "storm clouds",
195
- { specs: bothTiersSpecs },
196
- fakeExec,
197
- () => true,
198
- );
199
-
200
- assert.equal(result, null);
201
- assert.equal(errors.length, 2, "both tiers tried, both reported");
202
- assert.match(errors[0], /ltx-2\.3-mlx-q8/);
203
- assert.match(errors[1], /ltx-2\.3-mlx-q4/);
204
- });
205
-
206
- test("preferTier pins the attempt to one tier instead of demoting", async (t) => {
207
- t.mock.method(console, "error", () => {});
208
- const calls = [];
209
- const fakeExec = (...call) => {
210
- calls.push(call);
211
- if (isGenerate(call)) throw new Error("boom");
212
- };
213
-
214
- const result = await ltxVideoGenerate(
215
- "storm clouds",
216
- { specs: bothTiersSpecs, preferTier: "large" },
217
- fakeExec,
218
- () => true,
219
- );
220
-
221
- assert.equal(result, null);
222
- const generated = calls.filter(isGenerate).map((call) => call[1].join(" "));
223
- assert.equal(generated.length, 1, "pinned to large: no demotion to medium");
224
- assert.match(generated[0], /dgrauet\/ltx-2\.3-mlx-q8/);
225
- });
226
-
227
- // A failed attempt's temp path is minted per attempt (it carries a timestamp),
228
- // so without cleanup a partial mp4 from a failed tier is orphaned rather than
229
- // overwritten - and a lower tier then succeeding hides it. Partial video files
230
- // are the expensive case, which is why this is pinned.
231
- const outputOf = (argv) => argv[argv.indexOf("--output") + 1];
232
-
233
- test("a failed attempt's partial output is discarded before demoting", async (t) => {
234
- t.mock.method(console, "error", () => {});
235
- const unlinked = [];
236
- const attempted = [];
237
- const fakeExec = (...call) => {
238
- if (!isGenerate(call)) return;
239
- attempted.push(outputOf(call[1]));
240
- if (call[1].includes("dgrauet/ltx-2.3-mlx-q8")) {
241
- // OOM mid-write is one of the advertised demotion cases
242
- const err = new Error("exit 1");
243
- err.stderr = "mlx.core.metal: out of memory";
244
- throw err;
245
- }
246
- };
247
-
248
- const result = await ltxVideoGenerate(
249
- "storm clouds",
250
- { specs: bothTiersSpecs },
251
- fakeExec,
252
- () => true,
253
- (path) => unlinked.push(path),
254
- );
255
-
256
- assert.ok(result, "the medium tier still produced a video");
257
- assert.deepEqual(unlinked, [attempted[0]], "the failed large-tier partial is removed");
258
- });
259
-
260
- test("every tier failing discards every partial, one per attempt", async (t) => {
261
- t.mock.method(console, "error", () => {});
262
- const unlinked = [];
263
- const attempted = [];
264
- const fakeExec = (...call) => {
265
- if (!isGenerate(call)) return;
266
- attempted.push(outputOf(call[1]));
267
- throw new Error("mlx out of memory");
268
- };
269
-
270
- const result = await ltxVideoGenerate(
271
- "storm clouds",
272
- { specs: bothTiersSpecs },
273
- fakeExec,
274
- () => true,
275
- (path) => unlinked.push(path),
276
- );
277
-
278
- assert.equal(result, null);
279
- assert.equal(attempted.length, 2, "both tiers attempted");
280
- assert.deepEqual(unlinked, attempted, "nothing is left behind on the all-fail path");
281
- });
282
-
283
- test("a successful generation is never discarded", async () => {
284
- const unlinked = [];
285
-
286
- const result = await ltxVideoGenerate(
287
- "storm clouds",
288
- { specs: bothTiersSpecs },
289
- () => {},
290
- () => true,
291
- (path) => unlinked.push(path),
292
- );
293
-
294
- assert.ok(result);
295
- assert.deepEqual(unlinked, [], "the returned artifact must survive");
296
- });
297
-
298
- test("an unremovable partial does not mask the generate failure", async (t) => {
299
- t.mock.method(console, "error", () => {});
300
- const fakeExec = (...call) => {
301
- if (isGenerate(call)) throw new Error("mlx out of memory");
302
- };
303
-
304
- const result = await ltxVideoGenerate(
305
- "storm clouds",
306
- { specs: bothTiersSpecs },
307
- fakeExec,
308
- () => true,
309
- () => {
310
- throw new Error("EPERM: operation not permitted");
311
- },
312
- );
313
-
314
- // cleanup is best-effort: a partial we cannot delete must not become the error
315
- assert.equal(result, null);
316
- });
@@ -1,266 +0,0 @@
1
- import { existsSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { withReservedFile, withReservedFileSync } from "./manifest.mjs";
4
- import { freezeUrl } from "./freeze.mjs";
5
- import { tokenOverlap } from "./match.mjs";
6
- import { buildCube } from "./cube-build.mjs";
7
- import { validateCube, validateCubeFile } from "./cube-validate.mjs";
8
-
9
- const SKILL_DIR = join(import.meta.dirname, "..", "..");
10
- const LUT_DIR = join(SKILL_DIR, "luts");
11
- const LUT_INDEX = join(LUT_DIR, "index.json");
12
- export const LIBRARY_LUT_OFFLINE_CODE = "MEDIA_USE_LIBRARY_LUT_OFFLINE";
13
-
14
- // Presets this released resolver can safely emit at this stack layer.
15
- // Effect-backed treatment presets join after their runtime support lands.
16
- export const RESOLVABLE_PRESET_IDS = [
17
- "neutral",
18
- "warm-daylight",
19
- "clean-studio",
20
- "skin-soft",
21
- "food-pop",
22
- "night-lift",
23
- "muted-editorial",
24
- "vintage-wash",
25
- "mono-clean",
26
- "mono-fade",
27
- "soft-boost",
28
- "bright-pop",
29
- "deep-contrast",
30
- "creator-camcorder",
31
- "vhs-playback",
32
- "home-movie-8mm",
33
- "editorial-halftone",
34
- "two-ink-print",
35
- ];
36
-
37
- const PRESET_SYNONYMS = {
38
- neutral: ["neutral", "identity", "none", "ungraded", "natural base"],
39
- "warm-daylight": [
40
- "warm daylight",
41
- "warm natural light",
42
- "golden daylight",
43
- "sunlit",
44
- "warm sunny",
45
- "warm clean",
46
- "clean warm",
47
- "warm product",
48
- ],
49
- "clean-studio": [
50
- "clean studio",
51
- "studio clean",
52
- "cool studio",
53
- "product studio",
54
- "cool clean",
55
- "clean cool",
56
- "cool crisp",
57
- ],
58
- "skin-soft": ["skin soft", "soft skin", "portrait soft", "beauty skin"],
59
- "food-pop": ["food pop", "food vibrant", "appetizing", "restaurant color"],
60
- "night-lift": ["night lift", "night", "low light lift", "city night"],
61
- "muted-editorial": ["muted editorial", "editorial muted", "magazine muted"],
62
- "vintage-wash": ["vintage wash", "vintage", "retro wash", "aged film"],
63
- "mono-clean": ["mono clean", "black white clean", "monochrome clean"],
64
- "mono-fade": ["mono fade", "black white fade", "faded monochrome"],
65
- "soft-boost": [
66
- "soft boost",
67
- "soft bright",
68
- "gentle boost",
69
- "natural lift",
70
- "natural light",
71
- "gentle lift",
72
- "soft natural",
73
- ],
74
- "bright-pop": [
75
- "bright pop",
76
- "bright punchy",
77
- "vivid bright",
78
- "fresh pop",
79
- "fresh",
80
- "bright fresh",
81
- "clean colorful",
82
- ],
83
- "deep-contrast": ["deep contrast", "high contrast punchy", "punchy contrast", "bold contrast"],
84
- "creator-camcorder": ["creator camcorder", "creator video", "ugc camera", "handheld creator"],
85
- "vhs-playback": ["vhs playback", "vhs tape", "analog tape", "degraded tape"],
86
- "home-movie-8mm": ["8mm home movie", "8mm film", "family film", "small gauge film"],
87
- "editorial-halftone": ["editorial halftone", "halftone", "print dots", "newsprint"],
88
- "two-ink-print": ["two ink print", "two ink editorial", "duotone print", "poster print"],
89
- };
90
-
91
- function presetCandidates() {
92
- return RESOLVABLE_PRESET_IDS.map((id) => ({
93
- kind: "preset",
94
- preset: id,
95
- synonyms: PRESET_SYNONYMS[id] ?? [],
96
- text: [id, ...(PRESET_SYNONYMS[id] ?? [])].join(" "),
97
- }));
98
- }
99
-
100
- export function readBundledLutIndex() {
101
- if (!existsSync(LUT_INDEX)) return [];
102
- const parsed = JSON.parse(readFileSync(LUT_INDEX, "utf8"));
103
- const entries = Array.isArray(parsed) ? parsed : parsed.looks;
104
- if (!Array.isArray(entries)) return [];
105
- return entries.map((entry) => {
106
- const params =
107
- entry.params && typeof entry.params === "object" && !Array.isArray(entry.params)
108
- ? entry.params
109
- : null;
110
- const url = typeof entry.url === "string" && entry.url.trim() ? entry.url.trim() : null;
111
- return {
112
- id: String(entry.id),
113
- description: String(entry.description ?? entry.id),
114
- tags: Array.isArray(entry.tags) ? entry.tags.map(String) : [],
115
- intensity: Number.isFinite(Number(entry.intensity)) ? Number(entry.intensity) : 1,
116
- ...(params && { params }),
117
- ...(url && { url }),
118
- };
119
- });
120
- }
121
-
122
- function libraryCandidates() {
123
- return readBundledLutIndex().map((entry) => ({
124
- kind: "library",
125
- ...entry,
126
- text: [entry.id, entry.description, ...entry.tags].join(" "),
127
- }));
128
- }
129
-
130
- export function matchColorLook(intent) {
131
- const normalized = String(intent ?? "")
132
- .trim()
133
- .toLowerCase()
134
- .replace(/\s+/g, " ");
135
- if (RESOLVABLE_PRESET_IDS.includes(normalized)) {
136
- return { kind: "preset", preset: normalized, score: 99 };
137
- }
138
-
139
- const candidates = [...presetCandidates(), ...libraryCandidates()]
140
- .map((candidate, index) => ({
141
- ...candidate,
142
- index,
143
- score: tokenOverlap(intent, candidate.text),
144
- }))
145
- .filter((candidate) => candidate.score >= 2)
146
- .sort((a, b) => b.score - a.score || a.index - b.index);
147
-
148
- if (candidates.length === 0) return null;
149
- const best = candidates[0];
150
- if (best.kind === "preset") {
151
- return { kind: "preset", preset: best.preset, score: best.score };
152
- }
153
- return {
154
- kind: "library",
155
- id: best.id,
156
- description: best.description,
157
- tags: best.tags,
158
- intensity: best.intensity,
159
- ...(best.params && { params: best.params }),
160
- ...(best.url && { url: best.url }),
161
- score: best.score,
162
- };
163
- }
164
-
165
- export function isLibraryLutOfflineMiss(err) {
166
- return err?.code === LIBRARY_LUT_OFFLINE_CODE;
167
- }
168
-
169
- function libraryRecord(match, { id, localPath, fullPath, via }) {
170
- return {
171
- id,
172
- localPath,
173
- fullPath,
174
- lut: { src: localPath, intensity: match.intensity },
175
- source: "library",
176
- description: match.description,
177
- metadata: {
178
- provider: "cube_lut.library",
179
- provenance: {
180
- look_id: match.id,
181
- tags: match.tags,
182
- via,
183
- },
184
- },
185
- };
186
- }
187
-
188
- function assertValidCubeText(cube, label) {
189
- const check = validateCube(cube);
190
- if (!check.ok) throw new Error(`${label}: ${check.error}`);
191
- }
192
-
193
- function assertValidCubeFile(path, label) {
194
- const check = validateCubeFile(path);
195
- if (!check.ok) throw new Error(`${label}: ${check.error}`);
196
- }
197
-
198
- function offlineLibraryMiss(match) {
199
- const err = new Error(`library LUT "${match.id}" is CDN-only and --local-only is set`);
200
- err.code = LIBRARY_LUT_OFFLINE_CODE;
201
- return err;
202
- }
203
-
204
- export async function freezeLibraryLut(match, { projectDir, type, localOnly = false }) {
205
- if (!match || match.kind !== "library") {
206
- throw new Error("freezeLibraryLut requires a library match");
207
- }
208
-
209
- // Prefer the CDN url so looks download on-demand (like bgm/image). Fall back
210
- // to deterministic buildCube params when offline (--local-only) or if the
211
- // download/validation fails, so resolution is never blocked on the network.
212
- if (match.url && !localOnly) {
213
- try {
214
- return await withReservedFile(
215
- projectDir,
216
- type,
217
- ".cube",
218
- async ({ id, localPath, fullPath }) => {
219
- const tmpPath = `${fullPath}.tmp`;
220
- try {
221
- // Download + validate at a .tmp path, then atomically rename. A crash
222
- // (SIGKILL/OOM) between write and validate can't orphan an invalid .cube
223
- // at the final path — only a validated cube is ever renamed into place.
224
- await freezeUrl(match.url, tmpPath);
225
- assertValidCubeFile(tmpPath, `downloaded library LUT ${match.id} failed validation`);
226
- renameSync(tmpPath, fullPath);
227
- return libraryRecord(match, { id, localPath, fullPath, via: "url" });
228
- } finally {
229
- rmSync(tmpPath, { force: true });
230
- }
231
- },
232
- );
233
- } catch (err) {
234
- if (!match.params) {
235
- throw new Error(`failed to freeze library LUT ${match.id}: ${err.message}`);
236
- }
237
- // else: fall through to the params fallback below
238
- }
239
- }
240
-
241
- if (match.params) {
242
- return withReservedFileSync(projectDir, type, ".cube", ({ id, localPath, fullPath }) => {
243
- const tmpPath = `${fullPath}.tmp`;
244
- try {
245
- const cube = buildCube(match.params);
246
- assertValidCubeText(cube, `invalid library LUT ${match.id}`);
247
- // Write + validate at .tmp, then atomic rename — same no-orphan guarantee
248
- // as the url path above.
249
- writeFileSync(tmpPath, cube);
250
- assertValidCubeFile(tmpPath, `invalid frozen LUT ${localPath}`);
251
- renameSync(tmpPath, fullPath);
252
- return libraryRecord(match, {
253
- id,
254
- localPath,
255
- fullPath,
256
- via: match.url ? "params-fallback" : "params",
257
- });
258
- } finally {
259
- rmSync(tmpPath, { force: true });
260
- }
261
- });
262
- }
263
-
264
- if (match.url) throw offlineLibraryMiss(match); // url-only entry, offline
265
- throw new Error(`misconfigured library LUT "${match.id}": expected params or url`);
266
- }