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,128 +0,0 @@
1
- import { readdirSync, statSync, existsSync } from "node:fs";
2
- import { join, extname, basename } from "node:path";
3
- import { readManifest, appendRecord, nextId } from "./manifest.mjs";
4
- import { regenerateIndex } from "./index-gen.mjs";
5
- import { probe } from "./probe.mjs";
6
- import { matchTokens } from "./match.mjs";
7
-
8
- const AUDIO_EXT = new Set([".mp3", ".wav", ".ogg", ".m4a", ".aac"]);
9
- const IMAGE_EXT = new Set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".ico"]);
10
- const VIDEO_EXT = new Set([".mp4", ".webm", ".mov"]);
11
-
12
- function inferType(filePath) {
13
- const ext = extname(filePath).toLowerCase();
14
- if (AUDIO_EXT.has(ext)) {
15
- const lower = filePath.toLowerCase();
16
- if (lower.includes("/bgm/") || lower.includes("/music/") || lower.startsWith("bgm/"))
17
- return "bgm";
18
- if (lower.includes("/sfx/") || lower.includes("/sound") || lower.startsWith("sfx/"))
19
- return "sfx";
20
- if (lower.includes("/voice/") || lower.includes("/narrat") || lower.startsWith("voice/"))
21
- return "voice";
22
- return "bgm";
23
- }
24
- if (IMAGE_EXT.has(ext)) {
25
- if (ext === ".svg" || ext === ".ico") return "icon";
26
- return "image";
27
- }
28
- if (VIDEO_EXT.has(ext)) return "video";
29
- return null;
30
- }
31
-
32
- function walkDir(dir, base = "") {
33
- const files = [];
34
- if (!existsSync(dir)) return files;
35
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
36
- const rel = base ? `${base}/${entry.name}` : entry.name;
37
- if (entry.isDirectory()) {
38
- files.push(...walkDir(join(dir, entry.name), rel));
39
- } else {
40
- files.push(rel);
41
- }
42
- }
43
- return files;
44
- }
45
-
46
- export function scanExistingAssets(projectDir) {
47
- const assetsDir = join(projectDir, "assets");
48
- if (!existsSync(assetsDir)) return [];
49
-
50
- const files = walkDir(assetsDir);
51
- const found = [];
52
- for (const rel of files) {
53
- const type = inferType(rel);
54
- if (!type) continue;
55
- const fullPath = join(assetsDir, rel);
56
- const stat = statSync(fullPath);
57
- if (stat.size === 0) {
58
- // A 0-byte asset would register clean but fail at render — skip it loudly
59
- // rather than adopt a broken file.
60
- console.error(`media-use: skipping 0-byte asset assets/${rel}`);
61
- continue;
62
- }
63
- const meta = probe(fullPath);
64
- found.push({
65
- relativePath: `assets/${rel}`,
66
- type,
67
- size: stat.size,
68
- name: basename(rel, extname(rel)),
69
- ...meta,
70
- });
71
- }
72
- return found;
73
- }
74
-
75
- export function adoptExistingAssets(projectDir) {
76
- const existing = scanExistingAssets(projectDir);
77
- if (existing.length === 0) return [];
78
-
79
- const manifest = readManifest(projectDir);
80
- const knownPaths = new Set(manifest.map((r) => r.path));
81
-
82
- const adopted = [];
83
- for (const asset of existing) {
84
- if (knownPaths.has(asset.relativePath)) continue;
85
-
86
- const id = nextId(projectDir, asset.type);
87
- const record = {
88
- id,
89
- type: asset.type,
90
- path: asset.relativePath,
91
- source: "existing",
92
- description: asset.name.replace(/[-_]/g, " "),
93
- ...(asset.duration != null && { duration: asset.duration }),
94
- ...(asset.width != null && { width: asset.width }),
95
- ...(asset.height != null && { height: asset.height }),
96
- provenance: { provider: "local", adopted: true },
97
- };
98
- appendRecord(projectDir, record);
99
- adopted.push(record);
100
- }
101
-
102
- if (adopted.length > 0) regenerateIndex(projectDir);
103
- return adopted;
104
- }
105
-
106
- // Adopt a pre-existing assets/ file only when it shares a meaningful word with
107
- // the intent. The old test — `name.includes(intent) || intent.includes(name)` —
108
- // silently returned the WRONG file: "whoosh" grabbed a stray who.mp3, and a
109
- // one-letter filename matched every intent. A false negative just falls through
110
- // to a catalog search (safe); a false positive ships the wrong asset. So bias to
111
- // precision: require a shared token, don't guess from substrings.
112
- export function findExistingAsset(projectDir, intent, type) {
113
- const assetsDir = join(projectDir, "assets");
114
- if (!existsSync(assetsDir)) return null;
115
- const intentTokens = matchTokens(intent);
116
- if (intentTokens.size === 0) return null;
117
- for (const rel of walkDir(assetsDir)) {
118
- const t = inferType(rel);
119
- if (!t || (type && t !== type)) continue;
120
- const stem = basename(rel, extname(rel));
121
- for (const tok of matchTokens(stem)) {
122
- if (intentTokens.has(tok)) {
123
- return { relativePath: `assets/${rel}`, type: t, name: stem };
124
- }
125
- }
126
- }
127
- return null;
128
- }
@@ -1,95 +0,0 @@
1
- import { strict as assert } from "node:assert";
2
- import { mkdtempSync, rmSync, mkdirSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
4
- import { tmpdir } from "node:os";
5
- import { findExistingAsset, adoptExistingAssets } from "./adopt.mjs";
6
-
7
- let tmp;
8
- function setup() {
9
- tmp = mkdtempSync(join(tmpdir(), "mu-adopt-test-"));
10
- }
11
- function cleanup() {
12
- if (tmp) rmSync(tmp, { recursive: true, force: true });
13
- }
14
- function drop(rel) {
15
- const full = join(tmp, "assets", rel);
16
- mkdirSync(join(full, ".."), { recursive: true });
17
- writeFileSync(full, "x");
18
- }
19
-
20
- function runTests() {
21
- const tests = [];
22
- const test = (name, fn) => tests.push({ name, fn });
23
-
24
- test("does NOT false-match a short filename stem (who.mp3 vs 'whoosh')", () => {
25
- setup();
26
- drop("sfx/who.mp3");
27
- assert.equal(findExistingAsset(tmp, "whoosh", "sfx"), null);
28
- cleanup();
29
- });
30
-
31
- test("does NOT match a one-letter filename against any intent", () => {
32
- setup();
33
- drop("images/a.jpg");
34
- assert.equal(findExistingAsset(tmp, "gradient tech background", "image"), null);
35
- cleanup();
36
- });
37
-
38
- test("matches on a shared meaningful word", () => {
39
- setup();
40
- drop("images/hero-shot.jpg");
41
- const hit = findExistingAsset(tmp, "hero image", "image");
42
- assert.ok(hit);
43
- assert.equal(hit.relativePath, "assets/images/hero-shot.jpg");
44
- cleanup();
45
- });
46
-
47
- test("matches multi-word overlap", () => {
48
- setup();
49
- drop("images/gradient-tech-bg.jpg");
50
- assert.ok(findExistingAsset(tmp, "gradient tech background", "image"));
51
- cleanup();
52
- });
53
-
54
- test("a shared stopword alone does not match", () => {
55
- setup();
56
- drop("video/the-video.mp4");
57
- assert.equal(findExistingAsset(tmp, "the rocket", null), null);
58
- cleanup();
59
- });
60
-
61
- test("respects the type filter", () => {
62
- setup();
63
- drop("sfx/rocket.mp3");
64
- assert.equal(findExistingAsset(tmp, "rocket", "image"), null, "wrong type is skipped");
65
- assert.ok(findExistingAsset(tmp, "rocket", "sfx"), "right type matches");
66
- cleanup();
67
- });
68
-
69
- test("adoptExistingAssets still imports every typed file (unaffected by match rule)", () => {
70
- setup();
71
- drop("sfx/who.mp3");
72
- drop("images/a.jpg");
73
- assert.equal(adoptExistingAssets(tmp).length, 2);
74
- cleanup();
75
- });
76
-
77
- let passed = 0;
78
- let failed = 0;
79
- for (const { name, fn } of tests) {
80
- try {
81
- fn();
82
- passed++;
83
- console.log(` \x1b[32m✓\x1b[0m ${name}`);
84
- } catch (err) {
85
- failed++;
86
- console.log(` \x1b[31m✗\x1b[0m ${name}`);
87
- console.log(` ${err.message}`);
88
- }
89
- }
90
- console.log(`\n${passed} passed, ${failed} failed`);
91
- if (failed > 0) process.exit(1);
92
- }
93
-
94
- console.log("media-use · adopt / findExistingAsset tests\n");
95
- runTests();
@@ -1,20 +0,0 @@
1
- import { heygenSearch } from "./heygen-search.mjs";
2
-
3
- export const bgmProvider = {
4
- async search(intent) {
5
- const results = heygenSearch("audio sounds list", intent, { type: "music" });
6
- if (!results) return null;
7
- const best = results[0];
8
- return {
9
- url: best.audio_url,
10
- source: "search",
11
- // ext derived from audio_url by resolve.mjs — catalog tracks are .mp3 or .wav
12
- metadata: {
13
- description: best.description || intent,
14
- duration: best.duration || null,
15
- provider: "heygen.audio.sounds",
16
- provenance: { track_id: best.id, score: best.score, query: intent },
17
- },
18
- };
19
- },
20
- };
@@ -1,59 +0,0 @@
1
- import { readFileSync, existsSync } from "node:fs";
2
- import { join } from "node:path";
3
-
4
- function findDesignSpec(projectDir) {
5
- for (const name of ["frame.md", "design.md", "DESIGN.md"]) {
6
- const p = join(projectDir, name);
7
- if (existsSync(p)) return { path: p, name };
8
- }
9
- return null;
10
- }
11
-
12
- function parseFrontmatter(content) {
13
- const match = content.match(/^---\n([\s\S]*?)\n---/);
14
- if (!match) return null;
15
- const yaml = match[1];
16
- const tokens = {};
17
- for (const line of yaml.split("\n")) {
18
- const m = line.match(/^\s*(\w[\w-]*):\s*(.+)/);
19
- if (m) tokens[m[1]] = m[2].trim().replace(/^["']|["']$/g, "");
20
- }
21
- return tokens;
22
- }
23
-
24
- function extractColors(tokens) {
25
- const colors = [];
26
- for (const [k, v] of Object.entries(tokens)) {
27
- if (typeof v === "string" && /^#[0-9a-fA-F]{3,8}$/.test(v)) {
28
- colors.push({ name: k, hex: v });
29
- }
30
- }
31
- return colors;
32
- }
33
-
34
- export const brandProvider = {
35
- async search(intent, { projectDir } = {}) {
36
- if (!projectDir) return null;
37
- const spec = findDesignSpec(projectDir);
38
- if (!spec) return null;
39
- const content = readFileSync(spec.path, "utf8");
40
- const tokens = parseFrontmatter(content);
41
- if (!tokens) return null;
42
- const colors = extractColors(tokens);
43
- return {
44
- localPath: spec.path,
45
- source: "local",
46
- ext: ".md",
47
- metadata: {
48
- description: "Brand tokens from " + spec.name,
49
- provider: "design_spec",
50
- provenance: {
51
- file: spec.name,
52
- colors,
53
- font: tokens.font || tokens.typography || null,
54
- logo: tokens.logo || null,
55
- },
56
- },
57
- };
58
- },
59
- };
@@ -1,108 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { extname, join } from "node:path";
3
-
4
- const LIB_DIR =
5
- process.env.HYPERFRAMES_MEDIA_USE_SFX_DIR ||
6
- join(import.meta.dirname, "..", "..", "audio", "assets", "sfx");
7
-
8
- export const BUNDLED_SFX_RECOVERY_COMMAND = "npx hyperframes skills update media-use";
9
-
10
- export class BundledSfxAssetsError extends Error {
11
- constructor(health) {
12
- super(
13
- `bundled SFX assets are missing or incomplete (${health.detail}). Repair the installed media-use skill: ${health.fix}`,
14
- );
15
- this.name = "BundledSfxAssetsError";
16
- this.code = health.code;
17
- this.fix = health.fix;
18
- }
19
- }
20
-
21
- function unhealthy(detail) {
22
- return {
23
- ok: false,
24
- code: "bundled_sfx_assets_missing",
25
- detail,
26
- fix: BUNDLED_SFX_RECOVERY_COMMAND,
27
- };
28
- }
29
-
30
- export function inspectBundledSfxAssets(libraryDir = LIB_DIR) {
31
- const manifestPath = join(libraryDir, "manifest.json");
32
- if (!existsSync(manifestPath)) return unhealthy(`manifest not found: ${manifestPath}`);
33
-
34
- let manifest;
35
- try {
36
- manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
37
- } catch {
38
- return unhealthy(`manifest is not valid JSON: ${manifestPath}`);
39
- }
40
- if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
41
- return unhealthy(`manifest must contain an object: ${manifestPath}`);
42
- }
43
-
44
- const entries = Object.entries(manifest);
45
- if (entries.length === 0) return unhealthy(`manifest contains no SFX entries: ${manifestPath}`);
46
- for (const [key, entry] of entries) {
47
- if (!entry?.file || typeof entry.file !== "string") {
48
- return unhealthy(`manifest entry "${key}" has no file`);
49
- }
50
- const assetPath = join(libraryDir, entry.file);
51
- if (!existsSync(assetPath)) return unhealthy(`asset not found: ${assetPath}`);
52
- }
53
-
54
- return {
55
- ok: true,
56
- count: entries.length,
57
- detail: `${entries.length} bundled SFX asset${entries.length === 1 ? "" : "s"} available`,
58
- fix: "",
59
- };
60
- }
61
-
62
- const normalize = (value) =>
63
- String(value)
64
- .toLowerCase()
65
- .replace(/[^a-z0-9]+/g, " ")
66
- .trim();
67
-
68
- export function extensionForBundledSfxFile(filename) {
69
- return extname(filename) || ".mp3";
70
- }
71
-
72
- function score(intent, key, entry) {
73
- const query = normalize(intent);
74
- const name = normalize(key);
75
- if (query === name) return 100;
76
- if (query.includes(name) || name.includes(query)) return 50;
77
- const haystack = new Set(normalize(`${key} ${entry.description || ""}`).split(/\s+/));
78
- return query.split(/\s+/).filter((token) => token && haystack.has(token)).length;
79
- }
80
-
81
- export const bundledSfxProvider = {
82
- async search(intent, ctx = {}) {
83
- const libraryDir = ctx.libraryDir || LIB_DIR;
84
- const health = inspectBundledSfxAssets(libraryDir);
85
- if (!health.ok) throw new BundledSfxAssetsError(health);
86
- const manifest = JSON.parse(readFileSync(join(libraryDir, "manifest.json"), "utf8"));
87
-
88
- const ranked = Object.entries(manifest)
89
- .map(([key, entry]) => ({ key, entry, score: score(intent, key, entry) }))
90
- .filter(({ entry, score }) => entry?.file && score > 0)
91
- .sort((a, b) => b.score - a.score || a.key.localeCompare(b.key));
92
- const best = ranked[0];
93
- if (!best) return null;
94
-
95
- const localPath = join(libraryDir, best.entry.file);
96
- return {
97
- localPath,
98
- ext: extensionForBundledSfxFile(best.entry.file),
99
- source: "bundled",
100
- metadata: {
101
- description: best.entry.description || best.key,
102
- duration: best.entry.duration ?? null,
103
- provider: "bundled.sfx",
104
- provenance: { library_key: best.key },
105
- },
106
- };
107
- },
108
- };
@@ -1,85 +0,0 @@
1
- import { strict as assert } from "node:assert";
2
- import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
- import { test } from "node:test";
6
- import {
7
- BUNDLED_SFX_RECOVERY_COMMAND,
8
- BundledSfxAssetsError,
9
- bundledSfxProvider,
10
- extensionForBundledSfxFile,
11
- inspectBundledSfxAssets,
12
- } from "./bundled-sfx-provider.mjs";
13
-
14
- test("derives bundled SFX extension from the manifest filename", () => {
15
- assert.equal(extensionForBundledSfxFile("impact.wav"), ".wav");
16
- assert.equal(extensionForBundledSfxFile("whoosh.ogg"), ".ogg");
17
- assert.equal(extensionForBundledSfxFile("extensionless"), ".mp3");
18
- });
19
-
20
- test("reports an agent-friendly recovery when the bundled SFX manifest is absent", () => {
21
- const libraryDir = mkdtempSync(join(tmpdir(), "media-use-sfx-missing-"));
22
- try {
23
- const health = inspectBundledSfxAssets(libraryDir);
24
- assert.equal(health.ok, false);
25
- assert.equal(health.code, "bundled_sfx_assets_missing");
26
- assert.match(health.detail, /manifest\.json/);
27
- assert.match(health.fix, /hyperframes skills update media-use/);
28
- assert.equal(health.fix, BUNDLED_SFX_RECOVERY_COMMAND);
29
- } finally {
30
- rmSync(libraryDir, { recursive: true, force: true });
31
- }
32
- });
33
-
34
- test("reports the exact missing file from an incomplete bundled SFX install", () => {
35
- const libraryDir = mkdtempSync(join(tmpdir(), "media-use-sfx-incomplete-"));
36
- try {
37
- writeFileSync(
38
- join(libraryDir, "manifest.json"),
39
- JSON.stringify({ whoosh: { file: "whoosh.mp3", description: "transition" } }),
40
- );
41
- const health = inspectBundledSfxAssets(libraryDir);
42
- assert.equal(health.ok, false);
43
- assert.equal(health.code, "bundled_sfx_assets_missing");
44
- assert.match(health.detail, /whoosh\.mp3/);
45
- } finally {
46
- rmSync(libraryDir, { recursive: true, force: true });
47
- }
48
- });
49
-
50
- test("bundled provider raises a typed install error instead of a generic catalog miss", async () => {
51
- const libraryDir = mkdtempSync(join(tmpdir(), "media-use-sfx-provider-"));
52
- try {
53
- await assert.rejects(
54
- () => bundledSfxProvider.search("whoosh", { libraryDir }),
55
- (error) => {
56
- assert.ok(error instanceof BundledSfxAssetsError);
57
- assert.equal(error.code, "bundled_sfx_assets_missing");
58
- assert.match(error.message, /hyperframes skills update media-use/);
59
- return true;
60
- },
61
- );
62
- } finally {
63
- rmSync(libraryDir, { recursive: true, force: true });
64
- }
65
- });
66
-
67
- test("accepts a complete bundled SFX library", () => {
68
- const libraryDir = mkdtempSync(join(tmpdir(), "media-use-sfx-complete-"));
69
- try {
70
- mkdirSync(libraryDir, { recursive: true });
71
- writeFileSync(
72
- join(libraryDir, "manifest.json"),
73
- JSON.stringify({ whoosh: { file: "whoosh.mp3", description: "transition" } }),
74
- );
75
- writeFileSync(join(libraryDir, "whoosh.mp3"), "audio");
76
- assert.deepEqual(inspectBundledSfxAssets(libraryDir), {
77
- ok: true,
78
- count: 1,
79
- detail: "1 bundled SFX asset available",
80
- fix: "",
81
- });
82
- } finally {
83
- rmSync(libraryDir, { recursive: true, force: true });
84
- }
85
- });
@@ -1,150 +0,0 @@
1
- import { readFileSync, writeFileSync, mkdirSync, existsSync, copyFileSync } from "node:fs";
2
- import { join, basename } from "node:path";
3
- import { createHash } from "node:crypto";
4
- import { homedir } from "node:os";
5
- import { readManifest, appendRecord, normalizePrompt } from "./manifest.mjs";
6
-
7
- const SCHEMA_PREFIX = "mu-v1-";
8
- const KEY_HEX_CHARS = 16;
9
- const COMPLETE_SENTINEL = ".hf-complete";
10
-
11
- export function globalMediaDir() {
12
- return join(homedir(), ".media");
13
- }
14
-
15
- export function contentHash(filePath) {
16
- const bytes = readFileSync(filePath);
17
- return createHash("sha256").update(bytes).digest("hex");
18
- }
19
-
20
- function cacheEntryDir(rootDir, sha) {
21
- return join(rootDir, SCHEMA_PREFIX + sha.slice(0, KEY_HEX_CHARS));
22
- }
23
-
24
- function isComplete(entryDir) {
25
- return existsSync(join(entryDir, COMPLETE_SENTINEL));
26
- }
27
-
28
- function markComplete(entryDir) {
29
- writeFileSync(join(entryDir, COMPLETE_SENTINEL), "", "utf8");
30
- }
31
-
32
- // The manifest helpers append their own ".media" to the dir they get, so the
33
- // global manifest must be addressed by HOME, not by globalMediaDir() — passing
34
- // the latter nested it at ~/.media/.media/manifest.jsonl, invisible to the
35
- // Studio /api/assets/global route (which reads the documented flat path).
36
- export function readGlobalManifest() {
37
- return readManifest(homedir());
38
- }
39
-
40
- // Resolve a content-sha (full or unambiguous prefix) to a reusable global-cache
41
- // record, for `resolve --reuse <sha>`. Returns null on no match, or
42
- // { ambiguous: true, count } when a prefix matches multiple distinct entries.
43
- // Completeness (the .hf-complete sentinel) is left to importFromCache so the
44
- // caller can surface an "incomplete cache entry" error distinctly from a miss.
45
- export function findGlobalBySha(shaPrefix) {
46
- const p = String(shaPrefix || "")
47
- .toLowerCase()
48
- .trim();
49
- if (!p) return null;
50
- const matches = readGlobalManifest().filter(
51
- (r) => r.reusable && typeof r.sha === "string" && r.sha.startsWith(p),
52
- );
53
- if (matches.length === 0) return null;
54
- if (matches.length > 1) {
55
- const exact = matches.find((r) => r.sha === p);
56
- if (exact) return exact;
57
- return { ambiguous: true, count: matches.length };
58
- }
59
- return matches[0];
60
- }
61
-
62
- function validateCacheHit(match) {
63
- if (!match?.sha) return null;
64
- return isComplete(cacheEntryDir(globalMediaDir(), match.sha)) ? match : null;
65
- }
66
-
67
- export function cacheGet(prompt, type) {
68
- const key = normalizePrompt(prompt);
69
- if (!key) return null;
70
- return validateCacheHit(
71
- readGlobalManifest().find(
72
- (r) =>
73
- r.reusable &&
74
- normalizePrompt(r.provenance?.prompt) === key &&
75
- (type == null || r.type === type),
76
- ),
77
- );
78
- }
79
-
80
- export function cacheGetByEntity(entity) {
81
- const lower = entity.toLowerCase();
82
- return validateCacheHit(
83
- readGlobalManifest().find((r) => r.reusable && r.entity && r.entity.toLowerCase() === lower),
84
- );
85
- }
86
-
87
- export function cachePut(filePath, record) {
88
- const sha = contentHash(filePath);
89
- // Idempotent: same content already promoted -> don't duplicate the global
90
- // record. ponytail: skips usage_count bump; add it when the metric is needed.
91
- const existing = readGlobalManifest().find((r) => r.sha === sha);
92
- if (existing) return { sha, cached_path: existing.cached_path, deduped: true };
93
-
94
- const dir = globalMediaDir();
95
- const entryDir = cacheEntryDir(dir, sha);
96
- mkdirSync(entryDir, { recursive: true });
97
-
98
- const dest = join(entryDir, basename(filePath));
99
- copyFileSync(filePath, dest);
100
- markComplete(entryDir);
101
-
102
- const globalRecord = {
103
- ...record,
104
- sha,
105
- reusable: true,
106
- cached_path: dest,
107
- };
108
- appendRecord(homedir(), globalRecord);
109
- return { sha, cached_path: dest };
110
- }
111
-
112
- export function importFromCache(cacheRecord, projectDir, localId, localPath) {
113
- const sha = cacheRecord.sha;
114
- const entryDir = cacheEntryDir(globalMediaDir(), sha);
115
- if (!isComplete(entryDir)) return null;
116
-
117
- const cachedFile = cacheRecord.cached_path;
118
- if (!cachedFile || !existsSync(cachedFile)) return null;
119
-
120
- mkdirSync(join(projectDir, ".media"), { recursive: true });
121
- const fullDest = join(projectDir, localPath);
122
- mkdirSync(join(fullDest, ".."), { recursive: true });
123
- copyFileSync(cachedFile, fullDest);
124
-
125
- const projectRecord = {
126
- ...cacheRecord,
127
- id: localId,
128
- path: localPath,
129
- provenance: {
130
- ...cacheRecord.provenance,
131
- imported_from: sha,
132
- },
133
- };
134
- delete projectRecord.sha;
135
- delete projectRecord.reusable;
136
- delete projectRecord.cached_path;
137
-
138
- return projectRecord;
139
- }
140
-
141
- export function promote(projectDir, id) {
142
- const records = readManifest(projectDir);
143
- const record = records.find((r) => r.id === id);
144
- if (!record) throw new Error(`asset not found in project manifest: ${id}`);
145
-
146
- const filePath = join(projectDir, record.path);
147
- if (!existsSync(filePath)) throw new Error(`asset file not found: ${filePath}`);
148
-
149
- return cachePut(filePath, record);
150
- }