hyperframes 0.8.183 → 0.8.185

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 (314) hide show
  1. package/dist/{add-HW3ZGR36.js → add-ODTNPRGG.js} +14 -14
  2. package/dist/{agentPromptGenerator-VIIPZCJ3.js → agentPromptGenerator-KYO5MKTW.js} +1 -1
  3. package/dist/{animatedGifPrep-EPLN37ZN.js → animatedGifPrep-PEP5GWOJ.js} +10 -9
  4. package/dist/api.js +18 -17
  5. package/dist/{asset-TOASKL3Z.js → asset-EZG42XPA.js} +2 -2
  6. package/dist/{assetCataloger-PZN5HYG2.js → assetCataloger-OHAJFCA7.js} +10 -9
  7. package/dist/{audio-F5OIQVJO.js → audio-65R4T74I.js} +18 -16
  8. package/dist/{auth-YVQLRS3T.js → auth-VP2SIZFN.js} +1 -1
  9. package/dist/{autoUpdate-EG4ZFW4D.js → autoUpdate-RVSUX4SS.js} +2 -2
  10. package/dist/{backgroundChecks-WINNQVR2.js → backgroundChecks-LEHK74NS.js} +3 -3
  11. package/dist/backgroundChecksWorker.js +3 -3
  12. package/dist/{batchRender-PASZTMEE.js → batchRender-3X6CL7PK.js} +5 -5
  13. package/dist/{beats-KGIOEBLW.js → beats-66VPQLSC.js} +6 -6
  14. package/dist/{benchmark-ZNY2L7HN.js → benchmark-PBJVTOJP.js} +10 -10
  15. package/dist/{browser-REAO7RU4.js → browser-TPL5MWGC.js} +3 -3
  16. package/dist/{bundleWithLocalizedFonts-OAYLYC4W.js → bundleWithLocalizedFonts-67MLTHKM.js} +2 -2
  17. package/dist/{capture-WOSF276U.js → capture-F3GNMRZM.js} +14 -13
  18. package/dist/{capture-IK6GWQDD.js → capture-YYRE7EV3.js} +2 -2
  19. package/dist/{captureCompositionFrame-DDZNKYRE.js → captureCompositionFrame-PXV2ISHD.js} +14 -13
  20. package/dist/{catalog-AKTXS7OJ.js → catalog-6SDD3OHF.js} +14 -14
  21. package/dist/{catch-up-O3VZMPVE.js → catch-up-CWIVA5VG.js} +4 -4
  22. package/dist/{check-6E46NIWI.js → check-YQ6REK2S.js} +35 -34
  23. package/dist/{checkBrowser-UNZHQFNH.js → checkBrowser-5XZCBRXM.js} +32 -31
  24. package/dist/{chunk-TOHA5NAU.js → chunk-2TAS5O4P.js} +56 -11
  25. package/dist/{chunk-3Q47QC46.js → chunk-37CNYTQQ.js} +2 -2
  26. package/dist/chunk-3NPOFC43.js +3805 -0
  27. package/dist/{chunk-EEP5Y5IE.js → chunk-46HEFGXJ.js} +1 -1
  28. package/dist/{chunk-GAGMLVCV.js → chunk-4CTNDBE3.js} +2 -2
  29. package/dist/{chunk-CGK4XZ2J.js → chunk-55YELV7B.js} +1 -1
  30. package/dist/{chunk-TJB4GAV6.js → chunk-5GUKZRQN.js} +1 -1
  31. package/dist/{chunk-J4L5TPLS.js → chunk-5UDRPPN2.js} +48 -6
  32. package/dist/{chunk-SAQYJLI6.js → chunk-6ASIXMFP.js} +1 -1
  33. package/dist/{chunk-GPE3WWTO.js → chunk-6GPR3G3C.js} +14 -11
  34. package/dist/{chunk-MIZDJVPZ.js → chunk-6RIZY7OP.js} +1 -1
  35. package/dist/{chunk-5CS4T6C6.js → chunk-7L37AB57.js} +1 -1
  36. package/dist/{chunk-VH5KESLX.js → chunk-7PS6RVWD.js} +1 -1
  37. package/dist/{chunk-U5YVWG5W.js → chunk-7RAY4WZP.js} +3 -3
  38. package/dist/{chunk-COTLNID2.js → chunk-A6QCO474.js} +5 -5
  39. package/dist/{chunk-YMS5FOMC.js → chunk-B2A56S5C.js} +1 -1
  40. package/dist/{chunk-4FQKS7BJ.js → chunk-BH7TTIPN.js} +11 -13
  41. package/dist/{chunk-LBWRQ4XA.js → chunk-BRAPBUPQ.js} +1 -1
  42. package/dist/{chunk-IC2WYONA.js → chunk-CX3USX23.js} +3 -3
  43. package/dist/{chunk-ITSLKUQM.js → chunk-D42NU3DZ.js} +1 -1
  44. package/dist/{chunk-Q6UYKWBG.js → chunk-EMAWIZ54.js} +481 -36
  45. package/dist/{chunk-ZFO3A3QL.js → chunk-EMSDWBG3.js} +2 -2
  46. package/dist/{chunk-KRFAX5JK.js → chunk-ESBMUZXZ.js} +1 -1
  47. package/dist/{chunk-CAHTO62O.js → chunk-F3OPUWQX.js} +7 -7
  48. package/dist/{chunk-R6OV4J4K.js → chunk-FX6RUJUY.js} +3 -3
  49. package/dist/{chunk-F6WHPHLF.js → chunk-G2UANIDX.js} +1 -1
  50. package/dist/{chunk-DEORFAWP.js → chunk-H5SM5PJI.js} +1 -1
  51. package/dist/{chunk-5OGDBU4G.js → chunk-H6UY725W.js} +2 -2
  52. package/dist/{chunk-K2Z5DZBS.js → chunk-HDIRZGK4.js} +1 -1
  53. package/dist/{chunk-TXGZZKDF.js → chunk-HHSJJFVM.js} +2 -2
  54. package/dist/{chunk-VDJO4DJ7.js → chunk-HXUTX5GR.js} +3 -3
  55. package/dist/{chunk-73LTCCZI.js → chunk-I3IH5MV5.js} +19 -23
  56. package/dist/{chunk-3OLIGYWN.js → chunk-I45HL3VP.js} +3 -3
  57. package/dist/{chunk-TYXH3EKP.js → chunk-I5JRXMCV.js} +16 -1
  58. package/dist/{chunk-RZXUQR42.js → chunk-I7QMXZSB.js} +2 -2
  59. package/dist/{chunk-XOVHO3TT.js → chunk-IDFNKFPL.js} +7 -7
  60. package/dist/{chunk-QX4GGPMY.js → chunk-IDGLGXPA.js} +1 -1
  61. package/dist/{chunk-5MBLWFLM.js → chunk-IPDKZ5IN.js} +33 -6
  62. package/dist/{chunk-ANR6WOC3.js → chunk-ISTRDGIX.js} +1 -1
  63. package/dist/{chunk-4UFJWRHG.js → chunk-IVEFOUZI.js} +211 -83
  64. package/dist/chunk-IZCW2NKT.js +29 -0
  65. package/dist/{chunk-X3IA5RYH.js → chunk-J36JVBLR.js} +9 -2
  66. package/dist/{chunk-Z4R35NJS.js → chunk-J5XEZ24I.js} +3 -3
  67. package/dist/{chunk-OH56PXFH.js → chunk-K2ZKXTVL.js} +1 -1
  68. package/dist/{chunk-ATMDOUNN.js → chunk-K73RJB23.js} +2 -2
  69. package/dist/{chunk-NZ3BE2QE.js → chunk-KQ4JEJLF.js} +10 -10
  70. package/dist/{chunk-VFBAKPRC.js → chunk-KROXPHXX.js} +9 -3
  71. package/dist/{chunk-QGOYNPQT.js → chunk-LLFQA7XF.js} +48 -5
  72. package/dist/chunk-LLMSX6V6.js +17 -0
  73. package/dist/{chunk-XR5BSEUF.js → chunk-LYKSTRAY.js} +3 -3
  74. package/dist/{chunk-UNOSCGBG.js → chunk-OAG47RE6.js} +1 -1
  75. package/dist/{chunk-O7N3A7PE.js → chunk-PHMBMOXF.js} +1 -1
  76. package/dist/{chunk-BP4VNTZL.js → chunk-PMMQRJDE.js} +1 -1
  77. package/dist/{chunk-ADDHW2FS.js → chunk-Q447CX26.js} +82 -12
  78. package/dist/{chunk-WT4PYV2J.js → chunk-Q47FAYYK.js} +202 -55
  79. package/dist/{chunk-AKFADCMS.js → chunk-RTBSA3EA.js} +1 -1
  80. package/dist/{chunk-VAK7A2N6.js → chunk-RUEGYANA.js} +11 -11
  81. package/dist/{chunk-OYLO6TYK.js → chunk-RUOG4VB7.js} +1 -1
  82. package/dist/{chunk-EVAQPW6O.js → chunk-SKYPMHQC.js} +4 -4
  83. package/dist/{chunk-B2SZZFWW.js → chunk-SQVRXHOW.js} +3 -3
  84. package/dist/{chunk-CJYAJ3QM.js → chunk-TAX2ZJAP.js} +24 -16
  85. package/dist/{chunk-GTE5WZAM.js → chunk-TOI35GN2.js} +1 -1
  86. package/dist/{chunk-XJHKJXT4.js → chunk-UPR3MU6Z.js} +1 -1
  87. package/dist/{chunk-DLC2FICN.js → chunk-WQLKDL35.js} +2 -1
  88. package/dist/{chunk-3BA2GP4H.js → chunk-X6L6MD5B.js} +5 -5
  89. package/dist/{chunk-XAQDR5Y5.js → chunk-XGHHVYC7.js} +94 -32
  90. package/dist/{chunk-HLBODNGW.js → chunk-XWC7CNPY.js} +1 -1
  91. package/dist/{clean-WNEIVBF3.js → clean-7J6ZCCGV.js} +19 -18
  92. package/dist/cli.js +53 -53
  93. package/dist/{cloud-6GSSDCJL.js → cloud-7IJCJCDD.js} +4 -4
  94. package/dist/{cloudrun-4NEJRE4T.js → cloudrun-UNZRIIDB.js} +7 -7
  95. package/dist/commands/contrast-audit.browser.js +1 -2
  96. package/dist/commands/layout-audit.browser.js +196 -0
  97. package/dist/{compare-2JWQ5ABA.js → compare-Q7VW5JZT.js} +29 -28
  98. package/dist/{compiler-27KQD7KW.js → compiler-KPHYGFXL.js} +6 -6
  99. package/dist/{component-HCRET3MH.js → component-UUBREJKN.js} +3 -3
  100. package/dist/{compositions-HTSQ6SUJ.js → compositions-T4FTKIDP.js} +14 -13
  101. package/dist/{delete-GHQ5UYM7.js → delete-WZQF2IIA.js} +3 -3
  102. package/dist/{deploy-I3Z3NAVA.js → deploy-JU7A3XNC.js} +1 -1
  103. package/dist/{deterministicFonts-JFLSSKRB.js → deterministicFonts-EGAAE7QL.js} +3 -3
  104. package/dist/{dist-SENYPQGW.js → dist-LM7AVHOU.js} +3 -3
  105. package/dist/{dist-WRDLWRMS.js → dist-ULGTPSU4.js} +8 -4
  106. package/dist/docs/data-attributes.md +1 -1
  107. package/dist/{doctor-UGFVQZHI.js → doctor-VSNGRKXJ.js} +14 -13
  108. package/dist/{events-YS4OVX3I.js → events-CRW2DYUT.js} +3 -3
  109. package/dist/{events-BLJGWMNP.js → events-E63YBUL2.js} +3 -3
  110. package/dist/{feedback-AZ3SJ5HF.js → feedback-OSZWVR6N.js} +9 -9
  111. package/dist/{ffmpeg-2AECXG7V.js → ffmpeg-JLH3Z7G7.js} +10 -9
  112. package/dist/{figma-DRYIJQHV.js → figma-2BQNNR53.js} +3 -3
  113. package/dist/fontLocalizeCli.js +13 -12
  114. package/dist/{get-DDEAOFPB.js → get-P2XI53QW.js} +3 -3
  115. package/dist/{gpuPolicy-UDJT2MQK.js → gpuPolicy-NTKQJY75.js} +10 -9
  116. package/dist/{grade-compare-LJKK5NSY.js → grade-compare-OYASIHIB.js} +30 -29
  117. package/dist/{gsapParserAcorn-7NCK2LVT.js → gsapParserAcorn-SKODXC2N.js} +2 -2
  118. package/dist/{help-TQBGYOID.js → help-HK6K2N4G.js} +1 -1
  119. package/dist/{history-5TOOCF2Y.js → history-JMTYKLXW.js} +6 -6
  120. package/dist/hyperframe.manifest.json +1 -1
  121. package/dist/hyperframe.runtime.iife.js +65 -65
  122. package/dist/hyperframes-player.global.js +1 -1
  123. package/dist/{info-ENI5MP4W.js → info-AW7CIVRE.js} +9 -9
  124. package/dist/{init-JXPXWV3G.js → init-6KIDG4LB.js} +31 -30
  125. package/dist/{inspect-WZNYQ5AB.js → inspect-B7BLYMRZ.js} +30 -29
  126. package/dist/{installer-3SGRBLJW.js → installer-ZP2LQZUQ.js} +7 -7
  127. package/dist/{keyframes-HEOC4CT4.js → keyframes-EQ36A5JE.js} +7 -7
  128. package/dist/{lambda-2VHQ4AL2.js → lambda-E6VURLIS.js} +9 -9
  129. package/dist/{layout-7WEUNO6T.js → layout-XYC64QQQ.js} +30 -29
  130. package/dist/{lint-5YMAF3L7.js → lint-V5DJYWBX.js} +9 -9
  131. package/dist/{lintProject-C2ONSH2U.js → lintProject-JATQKR64.js} +3 -3
  132. package/dist/{list-BIE3X7CO.js → list-RUGVRTVD.js} +3 -3
  133. package/dist/{login-YDIGJWCG.js → login-ZTKGKXGS.js} +4 -4
  134. package/dist/{manager-D2UV6RDQ.js → manager-JUP7PM6N.js} +11 -10
  135. package/dist/{media-treatment-FSKHPMWA.js → media-treatment-VIUPK4OR.js} +16 -15
  136. package/dist/{models-COEK6LXG.js → models-LJS2HBOZ.js} +14 -13
  137. package/dist/{motionShot-HYZ2ZGJQ.js → motionShot-3UAXZ7SN.js} +17 -16
  138. package/dist/{normalize-audio-UXR6WQ7Q.js → normalize-audio-SBYI4YIH.js} +4 -4
  139. package/dist/{open-OFT2GIDG.js → open-S27TUD3A.js} +14 -14
  140. package/dist/{parakeetRunner-4NZ3QXS3.js → parakeetRunner-ETLMLJ4L.js} +15 -14
  141. package/dist/{pipeline-ZKE54JMG.js → pipeline-I2M22M24.js} +11 -10
  142. package/dist/{play-2VG77FT6.js → play-COJU6VJK.js} +22 -21
  143. package/dist/{preflight-QTWDIM5C.js → preflight-FZ24NCHH.js} +11 -10
  144. package/dist/{present-OONXQLJG.js → present-YAHKLC5D.js} +18 -17
  145. package/dist/{preview-6RZJFU76.js → preview-KQOEU5YE.js} +27 -26
  146. package/dist/{producer-6YM3UH3B.js → producer-DTNNG4T6.js} +1 -1
  147. package/dist/{projectConfig-YMW4P2X6.js → projectConfig-4A76VYUP.js} +9 -9
  148. package/dist/{publication-SEINUAPZ.js → publication-SQPMC2W3.js} +4 -4
  149. package/dist/{publish-KZBTBWFO.js → publish-QUEJBGGT.js} +13 -12
  150. package/dist/{remove-background-BIQNMIR7.js → remove-background-FP2ETAQH.js} +12 -11
  151. package/dist/{render-FIH37HJA.js → render-BIFIL4U6.js} +32 -31
  152. package/dist/render-GQFXJWLV.js +1504 -0
  153. package/dist/{render-LW76HOMJ.js → render-P664SMC7.js} +5 -5
  154. package/dist/{render-batch-POLEEYD4.js → render-batch-ZCPMYH6Z.js} +5 -5
  155. package/dist/renderSetupWorker.js +3 -3
  156. package/dist/{resolver-O2SED3XB.js → resolver-BUC2GFZQ.js} +6 -6
  157. package/dist/{sherpa-HTFFFTVQ.js → sherpa-HFT6XEP6.js} +14 -13
  158. package/dist/skills/hyperframes/SKILL.md +52 -174
  159. package/dist/skills/hyperframes/assets/frame-presets/biennale-yellow/FRAME.md +289 -0
  160. package/dist/skills/hyperframes/assets/frame-presets/biennale-yellow/caption-skin.html +220 -0
  161. package/dist/skills/hyperframes/assets/frame-presets/biennale-yellow/frame-showcase.html +1214 -0
  162. package/dist/skills/hyperframes/assets/frame-presets/blockframe/FRAME.md +275 -0
  163. package/dist/skills/hyperframes/assets/frame-presets/blockframe/caption-skin.html +223 -0
  164. package/dist/skills/hyperframes/assets/frame-presets/blockframe/frame-showcase.html +1347 -0
  165. package/dist/skills/hyperframes/assets/frame-presets/blue-professional/FRAME.md +307 -0
  166. package/dist/skills/hyperframes/assets/frame-presets/blue-professional/caption-skin.html +222 -0
  167. package/dist/skills/hyperframes/assets/frame-presets/blue-professional/frame-showcase.html +1372 -0
  168. package/dist/skills/hyperframes/assets/frame-presets/bold-poster/FRAME.md +275 -0
  169. package/dist/skills/hyperframes/assets/frame-presets/bold-poster/caption-skin.html +229 -0
  170. package/dist/skills/hyperframes/assets/frame-presets/bold-poster/frame-showcase.html +1140 -0
  171. package/dist/skills/hyperframes/assets/frame-presets/broadside/FRAME.md +284 -0
  172. package/dist/skills/hyperframes/assets/frame-presets/broadside/caption-skin.html +224 -0
  173. package/dist/skills/hyperframes/assets/frame-presets/broadside/frame-showcase.html +1166 -0
  174. package/dist/skills/hyperframes/assets/frame-presets/capsule/FRAME.md +284 -0
  175. package/dist/skills/hyperframes/assets/frame-presets/capsule/caption-skin.html +223 -0
  176. package/dist/skills/hyperframes/assets/frame-presets/capsule/frame-showcase.html +1465 -0
  177. package/dist/skills/hyperframes/assets/frame-presets/cartesian/FRAME.md +278 -0
  178. package/dist/skills/hyperframes/assets/frame-presets/cartesian/caption-skin.html +221 -0
  179. package/dist/skills/hyperframes/assets/frame-presets/cartesian/frame-showcase.html +1263 -0
  180. package/dist/skills/hyperframes/assets/frame-presets/cobalt-grid/FRAME.md +265 -0
  181. package/dist/skills/hyperframes/assets/frame-presets/cobalt-grid/caption-skin.html +237 -0
  182. package/dist/skills/hyperframes/assets/frame-presets/cobalt-grid/frame-showcase.html +1468 -0
  183. package/dist/skills/hyperframes/assets/frame-presets/code-editorial/FRAME.md +277 -0
  184. package/dist/skills/hyperframes/assets/frame-presets/code-editorial/caption-skin.html +223 -0
  185. package/dist/skills/hyperframes/assets/frame-presets/code-editorial/fonts/EBGaramond-400.woff2 +0 -0
  186. package/dist/skills/hyperframes/assets/frame-presets/code-editorial/fonts/EBGaramond-700.woff2 +0 -0
  187. package/dist/skills/hyperframes/assets/frame-presets/code-editorial/fonts/Inter-400.woff2 +0 -0
  188. package/dist/skills/hyperframes/assets/frame-presets/code-editorial/fonts/Inter-700.woff2 +0 -0
  189. package/dist/skills/hyperframes/assets/frame-presets/code-editorial/fonts/JetBrainsMono-400.woff2 +0 -0
  190. package/dist/skills/hyperframes/assets/frame-presets/code-editorial/fonts/JetBrainsMono-700.woff2 +0 -0
  191. package/dist/skills/hyperframes/assets/frame-presets/code-editorial/fonts/OFL-eb-garamond.txt +93 -0
  192. package/dist/skills/hyperframes/assets/frame-presets/code-editorial/fonts/OFL-inter.txt +93 -0
  193. package/dist/skills/hyperframes/assets/frame-presets/code-editorial/fonts/OFL-jetbrains-mono.txt +93 -0
  194. package/dist/skills/hyperframes/assets/frame-presets/code-editorial/frame-showcase.html +1438 -0
  195. package/dist/skills/hyperframes/assets/frame-presets/coral/FRAME.md +314 -0
  196. package/dist/skills/hyperframes/assets/frame-presets/coral/caption-skin.html +225 -0
  197. package/dist/skills/hyperframes/assets/frame-presets/coral/frame-showcase.html +1268 -0
  198. package/dist/skills/hyperframes/assets/frame-presets/creative-mode/FRAME.md +333 -0
  199. package/dist/skills/hyperframes/assets/frame-presets/creative-mode/caption-skin.html +226 -0
  200. package/dist/skills/hyperframes/assets/frame-presets/creative-mode/frame-showcase.html +1330 -0
  201. package/dist/skills/hyperframes/assets/frame-presets/daisy-days/FRAME.md +274 -0
  202. package/dist/skills/hyperframes/assets/frame-presets/daisy-days/caption-skin.html +229 -0
  203. package/dist/skills/hyperframes/assets/frame-presets/daisy-days/frame-showcase.html +1444 -0
  204. package/dist/skills/hyperframes/assets/frame-presets/editorial-forest/FRAME.md +265 -0
  205. package/dist/skills/hyperframes/assets/frame-presets/editorial-forest/caption-skin.html +222 -0
  206. package/dist/skills/hyperframes/assets/frame-presets/editorial-forest/frame-showcase.html +1273 -0
  207. package/dist/skills/hyperframes/references/captions.md +46 -0
  208. package/dist/skills/hyperframes/references/film.md +47 -0
  209. package/dist/skills/hyperframes/references/format.md +157 -0
  210. package/dist/skills/hyperframes/references/look.md +69 -0
  211. package/dist/skills/hyperframes/references/motion.md +94 -0
  212. package/dist/skills/hyperframes/references/overlays.md +39 -0
  213. package/dist/skills/hyperframes/references/plugin-installation.md +3 -3
  214. package/dist/skills/hyperframes/references/review.md +51 -0
  215. package/dist/skills/hyperframes/references/runtimes.md +43 -0
  216. package/dist/skills/hyperframes/references/skill-lifecycle.md +21 -13
  217. package/dist/skills/hyperframes/references/sound.md +43 -0
  218. package/dist/skills/hyperframes/references/spec.md +76 -0
  219. package/dist/skills/hyperframes/references/story.md +103 -0
  220. package/dist/skills/hyperframes/references/storyboard-format.md +30 -22
  221. package/dist/skills/hyperframes/references/storyboard.md +36 -0
  222. package/dist/skills/hyperframes/references/types/3d.md +25 -0
  223. package/dist/skills/hyperframes/references/types/character.md +20 -0
  224. package/dist/skills/hyperframes/references/types/data.md +19 -0
  225. package/dist/skills/hyperframes/references/types/explainer.md +34 -0
  226. package/dist/skills/hyperframes/references/types/launch.md +31 -0
  227. package/dist/skills/hyperframes/references/types/music.md +24 -0
  228. package/dist/skills/hyperframes/references/types/vertical.md +23 -0
  229. package/dist/skills/hyperframes/scripts/bake-basemap.mjs +324 -0
  230. package/dist/skills/hyperframes/scripts/lib/main-module.test.mjs +1 -6
  231. package/dist/skills/hyperframes/scripts/locate.mjs +228 -0
  232. package/dist/skills/hyperframes-cli/SKILL.md +76 -179
  233. package/dist/skills/hyperframes-cli/references/beats.md +1 -1
  234. package/dist/skills/hyperframes-cli/references/cloud.md +2 -2
  235. package/dist/skills/hyperframes-cli/references/cloudrun.md +1 -1
  236. package/dist/skills/hyperframes-cli/references/conventions.md +17 -0
  237. package/dist/skills/hyperframes-cli/references/feedback.md +14 -0
  238. package/dist/skills/hyperframes-cli/references/init-and-scaffold.md +11 -7
  239. package/dist/skills/hyperframes-cli/references/lambda.md +1 -1
  240. package/dist/skills/hyperframes-cli/references/lint-validate-inspect.md +19 -8
  241. package/dist/skills/hyperframes-cli/references/preview-render.md +8 -2
  242. package/dist/skills/hyperframes-cli/references/upgrade-info-misc.md +8 -0
  243. package/dist/skills/media-use/SKILL.md +47 -91
  244. package/dist/skills/media-use/audio/references/bgm.md +1 -1
  245. package/dist/skills/media-use/audio/references/captions/authoring.md +1 -2
  246. package/dist/skills/media-use/audio/references/captions/motion.md +5 -5
  247. package/dist/skills/media-use/audio/references/tts.md +0 -3
  248. package/dist/skills/media-use/audio/scripts/audio.mjs +5 -6
  249. package/dist/skills/media-use/audio/scripts/carve.mjs +560 -0
  250. package/dist/skills/media-use/audio/scripts/carve.test.mjs +254 -0
  251. package/dist/skills/media-use/audio/scripts/extract-audio-data.py +199 -0
  252. package/dist/skills/media-use/references/local.md +24 -0
  253. package/dist/skills/media-use/references/memory.md +4 -4
  254. package/dist/skills/media-use/references/mixing.md +71 -0
  255. package/dist/skills/media-use/references/operations.md +1 -1
  256. package/dist/skills/media-use/scripts/compatibility.test.mjs +1 -56
  257. package/dist/skills/media-use/scripts/lib/prefs-store.mjs +1 -1
  258. package/dist/skills/media-use/scripts/lib/recipe-store.mjs +1 -1
  259. package/dist/skills/media-use/scripts/prefs.mjs +2 -3
  260. package/dist/skills/media-use/scripts/recipe.mjs +1 -3
  261. package/dist/{skills-J6MBGS5O.js → skills-3WEVO3NU.js} +20 -13
  262. package/dist/{skillsUpdateCheck-ZAXNLBF3.js → skillsUpdateCheck-BFPWFOK5.js} +3 -3
  263. package/dist/{snapshot-TL7TTFS6.js → snapshot-WDQ6S7CJ.js} +458 -98
  264. package/dist/{src-Z5H5N5T4.js → src-NPR2JZOJ.js} +15 -11
  265. package/dist/{src-EL6HGIS4.js → src-RZ574OKP.js} +9 -8
  266. package/dist/{staticProjectServer-EO7X7RAY.js → staticProjectServer-FTGTEZI4.js} +18 -17
  267. package/dist/studio/assets/{hyperframes-player-hz_VqxBZ.js → hyperframes-player-CoOdf435.js} +1 -1
  268. package/dist/studio/assets/{index-CV_ojgp5.js → index-CR85xg2x.js} +1 -1
  269. package/dist/studio/assets/{index-S1ceEagc.js → index-C_6rTpaB.js} +5 -5
  270. package/dist/studio/index.html +1 -1
  271. package/dist/{studio-api-CUZDC5B3.js → studio-api-TAWB7B23.js} +12 -11
  272. package/dist/{studioServer-EZPRV43Y.js → studioServer-HRQGJTBU.js} +49 -48
  273. package/dist/{telemetry-LFUVHARP.js → telemetry-QZ6HJF4W.js} +3 -3
  274. package/dist/templates/_shared/AGENTS.md +14 -19
  275. package/dist/templates/_shared/CLAUDE.md +14 -19
  276. package/dist/templates/blank/index.html +10 -4
  277. package/dist/{timeline-GSH743JE.js → timeline-IKUEW42T.js} +21 -19
  278. package/dist/{tokens-3ZMPSH7B.js → tokens-AUGUEWHS.js} +2 -2
  279. package/dist/{transcribe-4SHNDPWW.js → transcribe-7734CG3N.js} +19 -18
  280. package/dist/{transcribe-TV7JBZQS.js → transcribe-FWU6EMEO.js} +13 -12
  281. package/dist/{updateCheck-W7FTFOQF.js → updateCheck-VIPQ3FPW.js} +2 -2
  282. package/dist/{upgrade-7YYYNAPF.js → upgrade-4VNDQTVO.js} +2 -2
  283. package/dist/{urlDownloader-PQSUS2CP.js → urlDownloader-6CXL4CL7.js} +9 -8
  284. package/dist/{validate-GCHAEVXM.js → validate-QWCMVWID.js} +24 -23
  285. package/dist/{video-KGN7NJS5.js → video-245Q5WN4.js} +10 -9
  286. package/dist/warmCheck.js +37 -36
  287. package/package.json +1 -1
  288. package/dist/chunk-GMNHAOMJ.js +0 -3805
  289. package/dist/render-65K3K6SD.js +0 -719
  290. package/dist/skills/hyperframes/references/brief-contract.md +0 -108
  291. package/dist/skills/hyperframes/references/brief-format.md +0 -72
  292. package/dist/skills/hyperframes/references/capability-menu.md +0 -47
  293. package/dist/skills/hyperframes/references/frame-worker-core.md +0 -82
  294. package/dist/skills/hyperframes/references/intent-interview.md +0 -73
  295. package/dist/skills/hyperframes/references/pitch-round.md +0 -40
  296. package/dist/skills/hyperframes/references/production-loop.md +0 -26
  297. package/dist/skills/hyperframes/references/review-loop.md +0 -37
  298. package/dist/skills/hyperframes/references/route-briefs.md +0 -11
  299. package/dist/skills/hyperframes/references/routes/embedded-captions.md +0 -11
  300. package/dist/skills/hyperframes/references/routes/faceless-explainer.md +0 -12
  301. package/dist/skills/hyperframes/references/routes/general-video.md +0 -12
  302. package/dist/skills/hyperframes/references/routes/motion-graphics.md +0 -11
  303. package/dist/skills/hyperframes/references/routes/music-to-video.md +0 -12
  304. package/dist/skills/hyperframes/references/routes/pr-to-video.md +0 -28
  305. package/dist/skills/hyperframes/references/routes/product-launch-video.md +0 -13
  306. package/dist/skills/hyperframes/references/routes/remotion-to-hyperframes.md +0 -9
  307. package/dist/skills/hyperframes/references/routes/slideshow.md +0 -11
  308. package/dist/skills/hyperframes/references/routes/talking-head-recut.md +0 -11
  309. package/dist/skills/hyperframes/references/script-format.md +0 -52
  310. package/dist/skills/hyperframes/references/subagent-dispatch.md +0 -35
  311. package/dist/skills/hyperframes/references/workflow-catalog.md +0 -10
  312. package/dist/skills/hyperframes/scripts/lib/frame-packets-core.mjs +0 -176
  313. package/dist/skills/media-use/audio/scripts/lib/host-audio.mjs +0 -28
  314. package/dist/skills/media-use/audio/scripts/lib/host-audio.test.mjs +0 -106
@@ -7,6 +7,9 @@ var __dirname = __hf_dirname(__filename);
7
7
  import {
8
8
  writeCaptureFileSync
9
9
  } from "./chunk-MRF4AMKD.js";
10
+ import {
11
+ toDomNode
12
+ } from "./chunk-LLMSX6V6.js";
10
13
  import {
11
14
  parseAngle
12
15
  } from "./chunk-KZI44OPA.js";
@@ -22,7 +25,7 @@ import {
22
25
  findFFmpeg,
23
26
  findFFprobe,
24
27
  getFFmpegInstallHint
25
- } from "./chunk-OH56PXFH.js";
28
+ } from "./chunk-K2ZKXTVL.js";
26
29
  import {
27
30
  pngDimensions,
28
31
  reportArtifacts,
@@ -30,20 +33,20 @@ import {
30
33
  } from "./chunk-57GN6KWT.js";
31
34
  import {
32
35
  captureArtifactRevision
33
- } from "./chunk-UNOSCGBG.js";
34
- import "./chunk-O7N3A7PE.js";
36
+ } from "./chunk-OAG47RE6.js";
37
+ import "./chunk-PHMBMOXF.js";
35
38
  import "./chunk-X7MTZ4T2.js";
36
39
  import {
37
40
  resolveProject
38
- } from "./chunk-TJB4GAV6.js";
41
+ } from "./chunk-5GUKZRQN.js";
39
42
  import {
40
43
  serveStaticProjectHtml
41
- } from "./chunk-Z4R35NJS.js";
42
- import "./chunk-5OGDBU4G.js";
44
+ } from "./chunk-J5XEZ24I.js";
45
+ import "./chunk-H6UY725W.js";
43
46
  import "./chunk-X7MTZ4T2.js";
44
47
  import "./chunk-XUHOIDKG.js";
45
- import "./chunk-NZ3BE2QE.js";
46
- import "./chunk-EVAQPW6O.js";
48
+ import "./chunk-KQ4JEJLF.js";
49
+ import "./chunk-SKYPMHQC.js";
47
50
  import "./chunk-6JR2XUEE.js";
48
51
  import "./chunk-TTVHYD5R.js";
49
52
  import {
@@ -53,7 +56,7 @@ import {
53
56
  recordArtifacts,
54
57
  retainSnapshotOutput
55
58
  } from "./chunk-U6RXZNZE.js";
56
- import "./chunk-GPE3WWTO.js";
59
+ import "./chunk-6GPR3G3C.js";
57
60
  import "./chunk-ALVXFXUG.js";
58
61
  import {
59
62
  DEFAULT_ZOOM_SCALE,
@@ -63,43 +66,44 @@ import {
63
66
  resolveCropRegion,
64
67
  runFfmpegOnce,
65
68
  seekCompositionTimeline
66
- } from "./chunk-XOVHO3TT.js";
69
+ } from "./chunk-IDFNKFPL.js";
67
70
  import "./chunk-FH7WODJH.js";
68
- import "./chunk-GTE5WZAM.js";
71
+ import "./chunk-TOI35GN2.js";
69
72
  import "./chunk-MOEGCMKI.js";
70
- import "./chunk-B2SZZFWW.js";
73
+ import "./chunk-SQVRXHOW.js";
71
74
  import "./chunk-DKPNNYBZ.js";
72
- import "./chunk-F6WHPHLF.js";
75
+ import "./chunk-G2UANIDX.js";
73
76
  import "./chunk-K2FLNJVB.js";
74
77
  import "./chunk-442JNXRM.js";
75
78
  import "./chunk-EHSBBBR2.js";
76
79
  import "./chunk-DQBY7FRR.js";
77
80
  import {
78
81
  resolveLocalBrowserGpuMode
79
- } from "./chunk-ZFO3A3QL.js";
80
- import "./chunk-K2Z5DZBS.js";
82
+ } from "./chunk-EMSDWBG3.js";
83
+ import "./chunk-HDIRZGK4.js";
81
84
  import "./chunk-XT7X6QCP.js";
82
85
  import "./chunk-4YYJOFKS.js";
83
86
  import "./chunk-HVW7ABXD.js";
84
- import "./chunk-AKFADCMS.js";
87
+ import "./chunk-RTBSA3EA.js";
85
88
  import {
86
89
  runWithConcurrencyLimit
87
90
  } from "./chunk-JHOCK3DG.js";
88
- import "./chunk-VDJO4DJ7.js";
89
- import "./chunk-BP4VNTZL.js";
91
+ import "./chunk-HXUTX5GR.js";
92
+ import "./chunk-PMMQRJDE.js";
90
93
  import "./chunk-AWDFDFI3.js";
91
94
  import "./chunk-P6LJ4Y32.js";
92
95
  import "./chunk-GMECXFAN.js";
93
- import "./chunk-5CS4T6C6.js";
96
+ import "./chunk-7L37AB57.js";
94
97
  import "./chunk-EOVYQ3IG.js";
95
- import "./chunk-DLC2FICN.js";
96
- import "./chunk-DEORFAWP.js";
98
+ import "./chunk-WQLKDL35.js";
99
+ import "./chunk-H5SM5PJI.js";
97
100
  import "./chunk-VWG7OQKU.js";
98
101
  import {
99
102
  defineCommand
100
103
  } from "./chunk-S3S4TGEH.js";
101
104
  import "./chunk-54HJ63Q2.js";
102
105
  import {
106
+ CliRuntimeError,
103
107
  failCommand
104
108
  } from "./chunk-JVBRJMOP.js";
105
109
  import {
@@ -108,22 +112,26 @@ import {
108
112
  import "./chunk-KLIKCX6Q.js";
109
113
  import "./chunk-EVMY7IPL.js";
110
114
  import "./chunk-7FMUTGOH.js";
111
- import "./chunk-73LTCCZI.js";
115
+ import "./chunk-I3IH5MV5.js";
116
+ import "./chunk-IZCW2NKT.js";
112
117
  import {
113
118
  createRuntimeStartTimeResolver,
114
119
  isClipVisibleAt,
115
120
  isInClipWindow
116
- } from "./chunk-VFBAKPRC.js";
121
+ } from "./chunk-KROXPHXX.js";
117
122
  import "./chunk-RQVDTPGD.js";
118
- import "./chunk-WT4PYV2J.js";
123
+ import "./chunk-Q47FAYYK.js";
119
124
  import {
120
125
  readElementRateSpec,
121
126
  sourceTimeAt,
122
127
  timeAtSourceTime
123
- } from "./chunk-GMNHAOMJ.js";
128
+ } from "./chunk-3NPOFC43.js";
124
129
  import "./chunk-VC76R6W2.js";
125
130
  import "./chunk-UJKQWGFF.js";
126
- import "./chunk-YMS5FOMC.js";
131
+ import {
132
+ topLevelElements,
133
+ trackKindOf
134
+ } from "./chunk-B2A56S5C.js";
127
135
  import "./chunk-OFQWTKPW.js";
128
136
  import "./chunk-DPVXBVLL.js";
129
137
  import "./chunk-3GXQPCJC.js";
@@ -141,23 +149,26 @@ import "./chunk-XT6MXC7A.js";
141
149
  import "./chunk-4UMPIPCQ.js";
142
150
  import {
143
151
  lintProject
144
- } from "./chunk-ADDHW2FS.js";
152
+ } from "./chunk-Q447CX26.js";
145
153
  import "./chunk-VAS6HQX3.js";
146
154
  import "./chunk-6BNTGNGD.js";
147
- import "./chunk-OYLO6TYK.js";
155
+ import "./chunk-RUOG4VB7.js";
148
156
  import "./chunk-MMQ52NIZ.js";
149
157
  import "./chunk-BSQ7XANM.js";
150
158
  import {
151
159
  parseHTML
152
160
  } from "./chunk-SDYDNHEE.js";
153
161
  import "./chunk-IXLUTE6N.js";
154
- import "./chunk-TYXH3EKP.js";
162
+ import "./chunk-I5JRXMCV.js";
155
163
  import "./chunk-W2LGJSEN.js";
164
+ import {
165
+ withMeta
166
+ } from "./chunk-ISTRDGIX.js";
156
167
  import "./chunk-K4WIOZFP.js";
157
168
  import "./chunk-XUXGIA7H.js";
158
169
  import "./chunk-WERLJGIC.js";
159
170
  import "./chunk-ISN5GRYI.js";
160
- import "./chunk-RZXUQR42.js";
171
+ import "./chunk-I7QMXZSB.js";
161
172
  import {
162
173
  normalizeErrorMessage
163
174
  } from "./chunk-KNU5COPB.js";
@@ -181,6 +192,15 @@ function sampleSnapshotRange([start, end], frames, duration, exclusiveEnd = fals
181
192
  const last = exclusiveEnd || end === duration ? end - Math.min(1e-3, (end - start) / (frames * 2)) : end;
182
193
  return Array.from({ length: frames }, (_, i) => start + (last - start) * i / (frames - 1));
183
194
  }
195
+ function snapshotStartResolver(document2, durations) {
196
+ return createRuntimeStartTimeResolver({
197
+ documentRef: document2,
198
+ timelineRegistry: Object.fromEntries(
199
+ Object.entries(durations).map(([key, value]) => [key, { duration: () => value }])
200
+ ),
201
+ includeAuthoredTimingAttrs: true
202
+ });
203
+ }
184
204
  function resolveSnapshotCompositionRange(html, durations, id, duration) {
185
205
  const { document: document2 } = parseHTML(html);
186
206
  const matches = Array.from(document2.querySelectorAll("[data-composition-id]")).filter(
@@ -190,14 +210,7 @@ function resolveSnapshotCompositionRange(html, durations, id, duration) {
190
210
  throw new Error(
191
211
  `Composition ID ${JSON.stringify(id)} matched ${matches.length} elements; expected exactly one`
192
212
  );
193
- const registry = Object.fromEntries(
194
- Object.entries(durations).map(([key, value]) => [key, { duration: () => value }])
195
- );
196
- const resolver = createRuntimeStartTimeResolver({
197
- documentRef: document2,
198
- timelineRegistry: registry,
199
- includeAuthoredTimingAttrs: true
200
- });
213
+ const resolver = snapshotStartResolver(document2, durations);
201
214
  let start = 0, end = duration;
202
215
  for (let el = matches[0]; el; el = el.parentElement) {
203
216
  if (!el.hasAttribute("data-composition-id") && !el.hasAttribute("data-start")) continue;
@@ -218,6 +231,208 @@ import { randomUUID } from "crypto";
218
231
  import { existsSync, mkdtempSync, readFileSync, mkdirSync, rmSync, writeFileSync } from "fs";
219
232
  import { tmpdir } from "os";
220
233
  import { resolve, join, relative, isAbsolute, basename, posix } from "path";
234
+
235
+ // src/commands/snapshotReview.ts
236
+ var REVIEW_SETTLE_SEC = 0.3;
237
+ var REVIEW_MID_GAP_SEC = 0.5;
238
+ var REVIEW_SEAM_OFFSETS = [-0.2, -0.1, 0, 0.1, 0.2];
239
+ var REVIEW_PHONE_WIDTH = 360;
240
+ var SAME_TIME_SEC = 0.05;
241
+ var EVEN_BEATS = 5;
242
+ function roundMs(value) {
243
+ return Math.round(value * 1e3) / 1e3;
244
+ }
245
+ function sceneId(el, index) {
246
+ return el.id || el.getAttribute("data-composition-id") || el.getAttribute("data-layout-name") || `${el.tagName.toLowerCase()}-${index + 1}`;
247
+ }
248
+ function resolveReviewTimeline(html, durations, duration, compositionId) {
249
+ const bounds = compositionId ? resolveSnapshotCompositionRange(html, durations, compositionId, duration) : [0, duration];
250
+ const { document: document2 } = parseHTML(html);
251
+ const target = compositionId ? Array.from(document2.querySelectorAll("[data-composition-id]")).find(
252
+ (el) => el.getAttribute("data-composition-id") === compositionId
253
+ ) : document2.querySelector("[data-composition-id]");
254
+ if (!target) {
255
+ return { window: bounds, scenes: [], beats: [], notes: ["No composition root found."] };
256
+ }
257
+ const resolver = snapshotStartResolver(document2, durations);
258
+ const span = (el) => {
259
+ const start = resolver.resolveStartForElement(el, 0);
260
+ if (!Number.isFinite(start)) return null;
261
+ const length = resolver.resolveDurationForElement(el);
262
+ const from = Math.max(bounds[0], start);
263
+ const to = Math.min(bounds[1], length != null ? start + length : bounds[1]);
264
+ return to > from ? [roundMs(from), roundMs(to)] : null;
265
+ };
266
+ const scenes = [];
267
+ const rows = topLevelElements(toDomNode(target)).filter((node) => {
268
+ const kind = trackKindOf(node).kind;
269
+ return kind === "video" || kind === "graphics";
270
+ });
271
+ rows.forEach((node, index) => {
272
+ const interval = span(node.el);
273
+ if (interval)
274
+ scenes.push({ id: sceneId(node.el, index), start: interval[0], end: interval[1] });
275
+ });
276
+ const beats = [];
277
+ const notes = [];
278
+ for (const el of Array.from(target.querySelectorAll("[data-beat]"))) {
279
+ const name = el.getAttribute("data-beat")?.trim() || sceneId(el, beats.length);
280
+ const timed = el.hasAttribute("data-start") || el.hasAttribute("data-composition-id");
281
+ const interval = timed ? span(el) : null;
282
+ if (!interval) {
283
+ notes.push(`data-beat="${name}" was skipped: give it a data-start so the beat has a time.`);
284
+ continue;
285
+ }
286
+ beats.push({ time: settledTime(interval[0], interval[1]), label: `beat:${name}` });
287
+ }
288
+ scenes.sort((a, b) => a.start - b.start || a.end - b.end);
289
+ return { window: bounds, scenes, beats, notes };
290
+ }
291
+ function sceneBeats(scene) {
292
+ const settled = settledTime(scene.start, scene.end);
293
+ const middle = roundMs((scene.start + scene.end) / 2);
294
+ const beats = [{ time: settled, label: scene.id }];
295
+ return middle - settled >= REVIEW_MID_GAP_SEC ? [...beats, { time: middle, label: `${scene.id} mid` }] : beats;
296
+ }
297
+ function settledTime(start, end) {
298
+ return roundMs(Math.min(start + REVIEW_SETTLE_SEC, (start + end) / 2));
299
+ }
300
+ function planReview(timeline, lastFrame) {
301
+ const { window: bounds, scenes } = timeline;
302
+ const notes = [...timeline.notes];
303
+ const clamp = (time) => roundMs(Math.min(Math.max(time, bounds[0]), lastFrame));
304
+ const distinctStarts = mergeBeats(
305
+ scenes.map((scene) => ({ time: scene.start, label: scene.id }))
306
+ );
307
+ const beats = mergeBeats([
308
+ ...distinctStarts.length >= 2 ? scenes.flatMap(sceneBeats) : evenBeats(bounds, notes),
309
+ ...timeline.beats
310
+ ]).map((beat) => ({ ...beat, time: clamp(beat.time) }));
311
+ const seams = seamTimes(scenes, bounds).map((time) => ({
312
+ time,
313
+ ...seamSides(scenes, time),
314
+ times: REVIEW_SEAM_OFFSETS.map((offset) => clamp(time + offset))
315
+ }));
316
+ return { window: bounds, scenes, beats, seams, notes };
317
+ }
318
+ function evenBeats(bounds, notes) {
319
+ notes.push(
320
+ `Fewer than two scene starts; ${EVEN_BEATS} beats are spread evenly instead. Time scenes as sub-composition hosts or clips to review each one.`
321
+ );
322
+ const [start, end] = bounds;
323
+ return Array.from({ length: EVEN_BEATS }, (_, index) => {
324
+ const time = roundMs(start + (index + 0.5) / EVEN_BEATS * (end - start));
325
+ return { time, label: `t=${time}` };
326
+ });
327
+ }
328
+ function mergeBeats(beats) {
329
+ const merged = [];
330
+ for (const beat of [...beats].sort((a, b) => a.time - b.time)) {
331
+ const same = merged.find((entry) => Math.abs(entry.time - beat.time) < SAME_TIME_SEC);
332
+ if (same) same.label = `${same.label} + ${beat.label}`;
333
+ else merged.push({ ...beat });
334
+ }
335
+ return merged;
336
+ }
337
+ function seamTimes(scenes, bounds) {
338
+ const inside = (time) => time > bounds[0] + SAME_TIME_SEC && time < bounds[1] - SAME_TIME_SEC;
339
+ const starts = scenes.map((scene) => scene.start).filter(inside);
340
+ const ends = scenes.map((scene) => scene.end).filter(inside).filter((end) => !starts.some((start) => Math.abs(start - end) < SAME_TIME_SEC));
341
+ const seams = [];
342
+ for (const time of [...starts, ...ends].sort((a, b) => a - b)) {
343
+ if (!seams.some((seam) => Math.abs(seam - time) < SAME_TIME_SEC)) seams.push(time);
344
+ }
345
+ return seams;
346
+ }
347
+ function seamSides(scenes, time) {
348
+ const before = scenes.filter((scene) => scene.start < time - SAME_TIME_SEC && scene.end >= time - SAME_TIME_SEC).sort((a, b) => b.start - a.start)[0];
349
+ const after = scenes.filter((scene) => Math.abs(scene.start - time) < SAME_TIME_SEC).sort((a, b) => a.end - b.end)[0];
350
+ const next = after ?? scenes.find((scene) => scene.start > time);
351
+ return { from: before?.id ?? null, to: next?.id ?? null };
352
+ }
353
+ function pad(index) {
354
+ return String(index).padStart(2, "0");
355
+ }
356
+ function stamp(time) {
357
+ return `${Number(time.toFixed(3))}s`;
358
+ }
359
+ var SEAM_TILE_LABELS = ["cut -0.2", "-0.1", "cut", "+0.1", "+0.2"];
360
+ function reviewFrames(plan) {
361
+ const beats = plan.beats.map((beat, index) => ({
362
+ role: "beat",
363
+ index: index + 1,
364
+ time: beat.time,
365
+ label: beat.label,
366
+ file: `beat-${pad(index + 1)}-at-${stamp(beat.time)}.png`
367
+ }));
368
+ const seams = plan.seams.flatMap(
369
+ (seam, index) => seam.times.map((time, slot) => ({
370
+ role: "seam",
371
+ index: index + 1,
372
+ time,
373
+ label: slot === 2 ? `cut ${seam.from ?? "start"} -> ${seam.to ?? "end"}` : SEAM_TILE_LABELS[slot] ?? "",
374
+ file: `seam-${pad(index + 1)}-${slot + 1}-at-${stamp(time)}.png`
375
+ }))
376
+ );
377
+ return [...beats, ...seams];
378
+ }
379
+ var BEAT_SHEET_PAGE = 16;
380
+ async function writeReviewSheets(dir, frames, written) {
381
+ const { createContactSheet } = await import("./contactSheet-CAGMKI3E.js");
382
+ const { join: join2 } = await import("path");
383
+ const captured = frames.flatMap((frame) => {
384
+ const path = written(frame);
385
+ return path ? [{ frame, path }] : [];
386
+ });
387
+ const beats = captured.filter((entry) => entry.frame.role === "beat");
388
+ const beatLabel = ({ frame }) => `${stamp(frame.time)} ${frame.label}`;
389
+ const pages = async (name, cellWidth, colsFor) => {
390
+ const sheets = [];
391
+ for (let start = 0; start < beats.length; start += BEAT_SHEET_PAGE) {
392
+ const page = beats.slice(start, start + BEAT_SHEET_PAGE);
393
+ const suffix = beats.length > BEAT_SHEET_PAGE ? `-${start / BEAT_SHEET_PAGE + 1}` : "";
394
+ const sheet = await createContactSheet(
395
+ page.map((entry) => entry.path),
396
+ join2(dir, `${name}${suffix}.jpg`),
397
+ {
398
+ cols: colsFor(page.length),
399
+ maxImages: page.length,
400
+ cellWidth,
401
+ labelMode: "custom",
402
+ labels: page.map(beatLabel)
403
+ }
404
+ );
405
+ if (sheet) sheets.push(sheet);
406
+ }
407
+ return sheets;
408
+ };
409
+ const seams = [];
410
+ const seamNumbers = [
411
+ ...new Set(captured.filter((e) => e.frame.role === "seam").map((e) => e.frame.index))
412
+ ];
413
+ for (const index of seamNumbers) {
414
+ const strip = captured.filter((e) => e.frame.role === "seam" && e.frame.index === index);
415
+ const sheet = await createContactSheet(
416
+ strip.map((entry) => entry.path),
417
+ join2(dir, `review-seam-${pad(index)}.jpg`),
418
+ {
419
+ cols: strip.length,
420
+ maxImages: strip.length,
421
+ cellWidth: 384,
422
+ labelMode: "custom",
423
+ labels: strip.map(({ frame }) => `${stamp(frame.time)} ${frame.label}`)
424
+ }
425
+ );
426
+ if (sheet) seams.push(sheet);
427
+ }
428
+ return {
429
+ beats: await pages("review-beats", 480, (count) => count <= 3 ? count : count <= 9 ? 3 : 4),
430
+ seams,
431
+ phone: await pages("review-phone", REVIEW_PHONE_WIDTH, (count) => Math.min(4, count))
432
+ };
433
+ }
434
+
435
+ // src/commands/snapshot.ts
221
436
  function orbitStageSource() {
222
437
  return `function(cam) {
223
438
  var root = document.querySelector("[data-composition-id]")
@@ -478,7 +693,8 @@ function serveCapturePage(projectDir, html, proxy) {
478
693
  return serveStaticProjectHtml(projectDir, html, void 0, [], proxy === true);
479
694
  }
480
695
  async function captureSnapshots(projectDir, opts) {
481
- const { bundleWithLocalizedFonts } = await import("./bundleWithLocalizedFonts-OAYLYC4W.js");
696
+ const log = opts.log ?? ((message) => console.log(message));
697
+ const { bundleWithLocalizedFonts } = await import("./bundleWithLocalizedFonts-67MLTHKM.js");
482
698
  const capture = await captureArtifactRevision(projectDir).catch(warnSnapshotsNotRecorded);
483
699
  const invocation = randomUUID();
484
700
  const artifacts = [];
@@ -486,6 +702,9 @@ async function captureSnapshots(projectDir, opts) {
486
702
  const warnings = [];
487
703
  let completed = false;
488
704
  const numFrames = opts.frames ?? 5;
705
+ let review;
706
+ let reviewList;
707
+ const reviewPaths = /* @__PURE__ */ new Map();
489
708
  const html = await bundleWithLocalizedFonts(projectDir);
490
709
  const server = await serveCapturePage(projectDir, html, opts.autoProxy);
491
710
  const savedPaths = [];
@@ -517,10 +736,10 @@ async function captureSnapshots(projectDir, opts) {
517
736
  const parts = [`${fontReport.loaded.length} loaded`];
518
737
  if (fontReport.errored.length > 0) parts.push(`${fontReport.errored.length} failed`);
519
738
  if (fontReport.unused.length > 0) parts.push(`${fontReport.unused.length} unused`);
520
- console.log(`
739
+ log(`
521
740
  ${c.dim("Fonts:")} ${parts.join(", ")}`);
522
741
  if (fontReport.errored.length > 0) {
523
- console.log(` ${c.error("Fonts FAILED:")} ${fontReport.errored.join(", ")}`);
742
+ log(` ${c.error("Fonts FAILED:")} ${fontReport.errored.join(", ")}`);
524
743
  }
525
744
  }
526
745
  if (fontReport.errored.length)
@@ -536,19 +755,20 @@ async function captureSnapshots(projectDir, opts) {
536
755
  return 0;
537
756
  });
538
757
  if (duration <= 0 && !opts.at?.length) {
539
- return [];
758
+ return { paths: [] };
540
759
  }
760
+ const readTiming = () => page.evaluate(() => ({
761
+ html: document.documentElement.outerHTML,
762
+ durations: Object.fromEntries(
763
+ Object.entries(window.__timelines ?? {}).map(([id, timeline]) => [
764
+ id,
765
+ Number(timeline.duration?.())
766
+ ])
767
+ )
768
+ }));
541
769
  let range = opts.range;
542
- if (opts.composition) {
543
- const timing = await page.evaluate(() => ({
544
- html: document.documentElement.outerHTML,
545
- durations: Object.fromEntries(
546
- Object.entries(window.__timelines ?? {}).map(([id, timeline]) => [
547
- id,
548
- Number(timeline.duration?.())
549
- ])
550
- )
551
- }));
770
+ if (opts.composition && !opts.review) {
771
+ const timing = await readTiming();
552
772
  range = resolveSnapshotCompositionRange(
553
773
  timing.html,
554
774
  timing.durations,
@@ -556,7 +776,18 @@ async function captureSnapshots(projectDir, opts) {
556
776
  duration
557
777
  );
558
778
  }
559
- const { times: positions, appendedTail } = range ? {
779
+ if (opts.review) {
780
+ const timing = await readTiming();
781
+ const timeline = resolveReviewTimeline(
782
+ timing.html,
783
+ timing.durations,
784
+ duration,
785
+ opts.composition
786
+ );
787
+ review = planReview(timeline, Math.min(timeline.window[1], duration) - 1 / 30);
788
+ reviewList = reviewFrames(review);
789
+ }
790
+ const { times: positions, appendedTail } = reviewList ? { times: reviewList.map((frame) => frame.time), appendedTail: false } : range ? {
560
791
  times: sampleSnapshotRange(range, numFrames, duration, !!opts.composition),
561
792
  appendedTail: false
562
793
  } : computeSnapshotTimes(duration, {
@@ -565,11 +796,15 @@ async function captureSnapshots(projectDir, opts) {
565
796
  includeEnd: opts.includeEnd
566
797
  });
567
798
  if (range)
568
- console.log(
799
+ log(
569
800
  ` Sampling ${positions.length} frames within ${range[0]}\u2013${range[1]}s${opts.composition ? ` (composition ${opts.composition})` : ""}`
570
801
  );
802
+ if (review)
803
+ log(
804
+ ` Review: ${review.beats.length} beat(s) and ${review.seams.length} seam(s) within ${review.window[0]}\u2013${review.window[1]}s${opts.composition ? ` (composition ${opts.composition})` : ""}`
805
+ );
571
806
  if (appendedTail) {
572
- console.log(
807
+ log(
573
808
  ` ${c.dim(`Note: added an end-of-timeline frame at ${positions[positions.length - 1].toFixed(2)}s. Short beats between your --at times may still be skipped \u2014 pass them explicitly.`)}`
574
809
  );
575
810
  }
@@ -591,7 +826,7 @@ async function captureSnapshots(projectDir, opts) {
591
826
  let syncVideoFrameVisibility = null;
592
827
  let extractMediaMetadata = null;
593
828
  try {
594
- const engine = await import("./src-EL6HGIS4.js");
829
+ const engine = await import("./src-RZ574OKP.js");
595
830
  injectVideoFramesBatch = engine.injectVideoFramesBatch;
596
831
  syncVideoFrameVisibility = engine.syncVideoFrameVisibility;
597
832
  extractMediaMetadata = engine.extractMediaMetadata;
@@ -729,7 +964,8 @@ async function captureSnapshots(projectDir, opts) {
729
964
  await recaptureSnapshotComposite(page);
730
965
  const timeLabel = formatSnapshotTimestamp(time);
731
966
  const slot = opts.keep ? `at-${timeLabel}` : `${String(i).padStart(2, "0")}-at-${timeLabel}`;
732
- const filename = opts.keep ? `${slot}.png` : `frame-${slot}.png`;
967
+ const reviewFrame = reviewList?.[i];
968
+ const filename = reviewFrame ? reviewFrame.file : opts.keep ? `${slot}.png` : `frame-${slot}.png`;
733
969
  const framePath = join(snapshotDir, filename);
734
970
  let crop;
735
971
  if (opts.zoom) {
@@ -768,9 +1004,11 @@ async function captureSnapshots(projectDir, opts) {
768
1004
  zoom: opts.zoom,
769
1005
  zoomScale: opts.zoom ? opts.zoomScale ?? DEFAULT_ZOOM_SCALE : 1,
770
1006
  angle: opts.angle,
771
- browserGpuMode: opts.browserGpuMode
1007
+ browserGpuMode: opts.browserGpuMode,
1008
+ ...reviewFrame ? { review: { role: reviewFrame.role, label: reviewFrame.label } } : {}
772
1009
  }
773
1010
  });
1011
+ if (reviewFrame) reviewPaths.set(reviewFrame, framePath);
774
1012
  if (opts.against) {
775
1013
  const refPng = await extractVideoFrameToBuffer(opts.against, time);
776
1014
  if (!refPng) {
@@ -806,20 +1044,32 @@ async function captureSnapshots(projectDir, opts) {
806
1044
  artifacts,
807
1045
  opts.zoom,
808
1046
  warnings,
809
- completed
1047
+ completed,
1048
+ log
810
1049
  ) : false;
811
1050
  if (!recorded) {
812
1051
  const paths = [...replaced, ...artifacts.map((artifact) => artifact.image)];
813
1052
  if (paths.length > 0) await forgetRecordedArtifacts(projectDir, paths).catch(() => void 0);
814
1053
  }
815
1054
  }
816
- return savedPaths;
1055
+ return {
1056
+ paths: savedPaths,
1057
+ ...review && reviewList ? {
1058
+ review: {
1059
+ plan: review,
1060
+ frames: reviewList.flatMap((frame) => {
1061
+ const path = reviewPaths.get(frame);
1062
+ return path ? [{ ...frame, path }] : [];
1063
+ })
1064
+ }
1065
+ } : {}
1066
+ };
817
1067
  }
818
1068
  function warnSnapshotsNotRecorded(error) {
819
1069
  console.warn(` ${c.warn("\u26A0")} Snapshots were not recorded: ${normalizeErrorMessage(error)}`);
820
1070
  return void 0;
821
1071
  }
822
- async function recordSnapshotArtifacts(projectDir, capture, artifacts, zoom, warnings, completed) {
1072
+ async function recordSnapshotArtifacts(projectDir, capture, artifacts, zoom, warnings, completed, log) {
823
1073
  const records = await recordArtifacts(
824
1074
  projectDir,
825
1075
  capture,
@@ -832,10 +1082,65 @@ async function recordSnapshotArtifacts(projectDir, capture, artifacts, zoom, war
832
1082
  ).catch(warnSnapshotsNotRecorded);
833
1083
  const record = records?.[0];
834
1084
  if (!record) return false;
835
- console.log(` Revision: ${record.revision ?? "none"} (${record.attribution})`);
836
- console.log(` State: ${join(projectDir, ".hyperframes", "video-state.json")}`);
1085
+ log(` Revision: ${record.revision ?? "none"} (${record.attribution})`);
1086
+ log(` State: ${join(projectDir, ".hyperframes", "video-state.json")}`);
837
1087
  return true;
838
1088
  }
1089
+ function snapshotEnvelope(projectDir, outputDir, captured, extra) {
1090
+ const review = captured.review;
1091
+ const frames = review ? review.frames.map((frame) => ({
1092
+ path: frame.path,
1093
+ time: frame.time,
1094
+ role: frame.role,
1095
+ label: frame.label
1096
+ })) : captured.paths.map((path) => ({ path: resolve(projectDir, path), time: timeFromName(path) }));
1097
+ return {
1098
+ ok: true,
1099
+ outputDir,
1100
+ frames,
1101
+ contactSheets: extra.contactSheets.map((sheet) => resolve(sheet)),
1102
+ ...extra.describedPath ? { descriptions: extra.describedPath } : {},
1103
+ ...review ? {
1104
+ review: {
1105
+ window: review.plan.window,
1106
+ scenes: review.plan.scenes,
1107
+ beats: review.plan.beats.map((beat, index) => ({
1108
+ ...beat,
1109
+ path: review.frames.find(
1110
+ (frame) => frame.role === "beat" && frame.index === index + 1
1111
+ )?.path
1112
+ })),
1113
+ seams: review.plan.seams.map((seam, index) => ({
1114
+ ...seam,
1115
+ paths: review.frames.filter((frame) => frame.role === "seam" && frame.index === index + 1).map((frame) => frame.path),
1116
+ sheet: extra.reviewSheets?.seams[index]
1117
+ })),
1118
+ sheets: extra.reviewSheets ?? { beats: [], seams: [], phone: [] },
1119
+ notes: review.plan.notes
1120
+ }
1121
+ } : {}
1122
+ };
1123
+ }
1124
+ function timeFromName(path) {
1125
+ const match = /at-([\d.]+)s\.png$/.exec(path);
1126
+ return match ? Number(match[1]) : null;
1127
+ }
1128
+ function printReview(review, sheets, log) {
1129
+ const beats = review.plan.beats.map(
1130
+ (beat) => `${formatSnapshotTimestamp(beat.time)} ${beat.label}`
1131
+ );
1132
+ log(` Beat sheet (settled frame per scene): ${sheets.beats.join(", ") || "none"}`);
1133
+ log(` ${c.dim(beats.join(" \xB7 "))}`);
1134
+ review.plan.seams.forEach((seam, index) => {
1135
+ const sides = `${seam.from ?? "start"} -> ${seam.to ?? "end"}`;
1136
+ const strip = sheets.seams[index] ?? "not written";
1137
+ log(` Seam ${index + 1} at ${formatSnapshotTimestamp(seam.time)} (${sides}): ${strip}`);
1138
+ });
1139
+ log(
1140
+ ` Phone sheet (beats at ${REVIEW_PHONE_WIDTH} px wide): ${sheets.phone.join(", ") || "none"}`
1141
+ );
1142
+ for (const note of review.plan.notes) log(` ${c.dim(note)}`);
1143
+ }
839
1144
  var snapshot_default = defineCommand({
840
1145
  meta: {
841
1146
  name: "snapshot",
@@ -901,6 +1206,16 @@ var snapshot_default = defineCommand({
901
1206
  type: "string",
902
1207
  description: "Reference video (e.g. footage being rebuilt): also save its frame at each captured time and a render|reference pair sheet"
903
1208
  },
1209
+ review: {
1210
+ type: "boolean",
1211
+ description: "Capture the review set instead of evenly spaced frames: each scene at its start + 0.3 s and its middle, and each data-beat at its start + 0.3 s, on review-beats.jpg, a 5-frame strip around every cut (review-seam-NN.jpg), and the beat frames at phone width (review-phone.jpg). Scopes to --composition; ignores --frames.",
1212
+ default: false
1213
+ },
1214
+ json: {
1215
+ type: "boolean",
1216
+ description: "Print one JSON envelope (paths, times, sheets; the review plan under --review)",
1217
+ default: false
1218
+ },
904
1219
  describe: {
905
1220
  type: "string",
906
1221
  description: "Gemini vision frame analysis. Runs by default when GEMINI_API_KEY is set. Pass a custom question (e.g. --describe 'Is the logo visible in every beat?') to override the default prompt, or --describe false to opt out."
@@ -917,8 +1232,22 @@ var snapshot_default = defineCommand({
917
1232
  }
918
1233
  },
919
1234
  async run({ args }) {
1235
+ const asJson = args.json === true;
1236
+ const log = (message) => asJson ? console.error(message) : console.log(message);
1237
+ const fail = (message) => {
1238
+ if (asJson) console.log(JSON.stringify(withMeta({ ok: false, error: message }), null, 2));
1239
+ else log(message);
1240
+ failCommand();
1241
+ };
920
1242
  if ([args.at, args.range, args.composition].filter((value) => value !== void 0).length > 1)
921
1243
  throw new Error("Use only one of --at, --range, or --composition");
1244
+ const reviewMode = args.review === true;
1245
+ if (reviewMode) {
1246
+ const clash = ["at", "range", "zoom", "keep", "against"].find(
1247
+ (flag) => args[flag] !== void 0 && args[flag] !== false
1248
+ );
1249
+ if (clash) throw new Error(`--review picks its own frames; drop --${clash}`);
1250
+ }
922
1251
  const frames = parseInt(args.frames, 10) || 5;
923
1252
  const range = args.range === void 0 ? void 0 : parseSnapshotRange(String(args.range));
924
1253
  const composition = args.composition === void 0 ? void 0 : String(args.composition).trim();
@@ -927,28 +1256,32 @@ var snapshot_default = defineCommand({
927
1256
  const lintResult = await lintProject(project.dir);
928
1257
  if (hasDefinitiveEntryMismatch(lintResult)) {
929
1258
  const candidate = definitiveEntryMismatchComposition(lintResult);
930
- console.log("");
1259
+ log("");
931
1260
  for (const line of formatLintFindings(lintResult, { errorsFirst: true })) {
932
- console.log(line);
1261
+ log(line);
933
1262
  }
934
- console.log("");
935
- console.log(c.error(" Aborting snapshot because the default index.html entry is blank."));
1263
+ log("");
1264
+ log(c.error(" Aborting snapshot because the default index.html entry is blank."));
936
1265
  if (candidate && posix.basename(candidate) === "index.html") {
937
1266
  const candidateDir = posix.dirname(candidate);
938
1267
  const target = `<project>/${candidateDir}`;
939
- console.log(
1268
+ log(
940
1269
  c.dim(
941
1270
  ` Move or mount the authored file, or snapshot its directory directly: hyperframes snapshot ${target}. Only use the directory form when its assets are self-contained under that directory; otherwise mount it from the project root.`
942
1271
  )
943
1272
  );
944
1273
  } else if (candidate) {
945
- console.log(
1274
+ log(
946
1275
  c.dim(
947
1276
  ` Move or mount ${candidate} as a project index.html before snapshotting; snapshot accepts project directories, not individual HTML files.`
948
1277
  )
949
1278
  );
950
1279
  }
951
- console.log("");
1280
+ log("");
1281
+ if (asJson) {
1282
+ const error = "The default index.html entry is blank; see the lint findings on stderr.";
1283
+ console.log(JSON.stringify(withMeta({ ok: false, error }), null, 2));
1284
+ }
952
1285
  failCommand();
953
1286
  }
954
1287
  const timeout = parseInt(args.timeout, 10) || 5e3;
@@ -959,20 +1292,18 @@ var snapshot_default = defineCommand({
959
1292
  const zoomTarget = args.zoom ? parseZoomTarget(String(args.zoom)) : void 0;
960
1293
  const zoomScale = parseZoomScale(args["zoom-scale"]);
961
1294
  const against = args.against ? resolve(String(args.against)) : void 0;
962
- if (against && !existsSync(against)) {
963
- console.log(`${c.error("\u2717")} --against video not found: ${against}`);
964
- failCommand();
965
- }
966
- const label = atTimestamps ? `${atTimestamps.length} frames at [${atTimestamps.map(formatSnapshotTimestamp).join(", ")}]` : `${frames} frames`;
1295
+ if (against && !existsSync(against))
1296
+ fail(`${c.error("\u2717")} --against video not found: ${against}`);
1297
+ const label = reviewMode ? "review frames (beats, seams, phone)" : atTimestamps ? `${atTimestamps.length} frames at [${atTimestamps.map(formatSnapshotTimestamp).join(", ")}]` : `${frames} frames`;
967
1298
  const angleLabel = camera && (camera.yaw !== 0 || camera.pitch !== 0) ? ` ${c.dim(`(angle yaw ${camera.yaw}\xB0 pitch ${camera.pitch}\xB0)`)}` : "";
968
- console.log(`${c.accent("\u25C6")} Capturing ${label} from ${c.accent(project.name)}${angleLabel}`);
1299
+ log(`${c.accent("\u25C6")} Capturing ${label} from ${c.accent(project.name)}${angleLabel}`);
969
1300
  let managedSnapshotDir;
970
1301
  try {
971
1302
  const snapshotDir = args.output ? resolve(String(args.output)) : artifactDirectory(project.dir, "snapshots");
972
1303
  if (!args.output) managedSnapshotDir = snapshotDir;
973
1304
  if (args.output) retainSnapshotOutput(project.dir, snapshotDir);
974
1305
  await withProjectLock(project.dir, "snapshot", async () => {
975
- const paths = await captureSnapshots(project.dir, {
1306
+ const captured = await captureSnapshots(project.dir, {
976
1307
  frames,
977
1308
  range,
978
1309
  composition,
@@ -986,53 +1317,71 @@ var snapshot_default = defineCommand({
986
1317
  autoProxy: args.proxy,
987
1318
  browserGpuMode: resolveLocalBrowserGpuMode(args["browser-gpu"]),
988
1319
  against,
989
- keep: args.keep === true
1320
+ keep: args.keep === true,
1321
+ review: reviewMode,
1322
+ log
990
1323
  });
1324
+ const paths = captured.paths;
991
1325
  reportArtifacts(
992
1326
  "snapshot",
993
1327
  "frame",
994
1328
  paths.map((path) => resolve(project.dir, path))
995
1329
  );
996
1330
  if (paths.length === 0) {
997
- console.log(
998
- `
999
- ${c.error("\u2717")} Could not determine composition duration \u2014 no frames captured`
1000
- );
1001
- failCommand();
1331
+ fail(`
1332
+ ${c.error("\u2717")} Could not determine composition duration \u2014 no frames captured`);
1002
1333
  }
1003
- console.log(`
1334
+ log(`
1004
1335
  ${c.success("\u25C7")} ${paths.length} snapshots saved to ${snapshotDir}`);
1005
1336
  for (const p of paths) {
1006
- console.log(` ${p}`);
1337
+ log(` ${p}`);
1007
1338
  }
1008
1339
  if (against) {
1009
- console.log(
1340
+ log(
1010
1341
  ` ${c.dim("ref-*.png + pair-*.jpg")} beside each frame (reference frame, render | reference sheet)`
1011
1342
  );
1012
1343
  }
1344
+ let contactSheets = [];
1345
+ let reviewSheets;
1013
1346
  try {
1014
1347
  const { createSnapshotContactSheet } = await import("./contactSheet-CAGMKI3E.js");
1015
- const sheets = args.keep === true ? [] : await createSnapshotContactSheet(
1016
- snapshotDir,
1017
- join(snapshotDir, "contact-sheet.jpg")
1018
- );
1019
- reportArtifacts("snapshot", "contact-sheet", sheets);
1020
- for (const sheet of sheets) {
1021
- console.log(` ${resolve(sheet)} (grid view for AI review)`);
1348
+ if (captured.review) {
1349
+ const written = new Map(
1350
+ captured.review.frames.map((frame) => [frame.file, frame.path])
1351
+ );
1352
+ reviewSheets = await writeReviewSheets(
1353
+ snapshotDir,
1354
+ captured.review.frames,
1355
+ (frame) => written.get(frame.file) ?? null
1356
+ );
1357
+ contactSheets = [...reviewSheets.beats, ...reviewSheets.seams, ...reviewSheets.phone];
1358
+ printReview(captured.review, reviewSheets, log);
1359
+ } else if (args.keep !== true) {
1360
+ contactSheets = await createSnapshotContactSheet(
1361
+ snapshotDir,
1362
+ join(snapshotDir, "contact-sheet.jpg")
1363
+ );
1364
+ }
1365
+ reportArtifacts("snapshot", "contact-sheet", contactSheets);
1366
+ if (!captured.review) {
1367
+ for (const sheet of contactSheets) {
1368
+ log(` ${resolve(sheet)} (grid view for AI review)`);
1369
+ }
1022
1370
  }
1023
1371
  } catch (err) {
1024
1372
  console.warn(
1025
1373
  ` ${c.warn("\u26A0")} Contact sheet skipped: ${normalizeErrorMessage(err)}. Individual snapshot PNGs are still available.`
1026
1374
  );
1027
1375
  }
1376
+ let describedPath;
1028
1377
  if (describeArg !== null) {
1029
1378
  try {
1030
1379
  const geminiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
1031
1380
  if (!geminiKey) {
1032
1381
  if (args.describe !== void 0)
1033
- console.log(` ${c.dim("--describe: GEMINI_API_KEY not set, skipping")}`);
1382
+ log(` ${c.dim("--describe: GEMINI_API_KEY not set, skipping")}`);
1034
1383
  } else if (paths.length > 0) {
1035
- console.log(` ${c.dim("Describing frames with Gemini vision...")}`);
1384
+ log(` ${c.dim("Describing frames with Gemini vision...")}`);
1036
1385
  const { GoogleGenAI } = await loadOptionalPackage("@google/genai", "--describe");
1037
1386
  const ai = new GoogleGenAI({ apiKey: geminiKey });
1038
1387
  const model = process.env.HYPERFRAMES_GEMINI_MODEL || "gemini-3.1-flash-lite-preview";
@@ -1100,17 +1449,28 @@ ${c.success("\u25C7")} ${paths.length} snapshots saved to ${snapshotDir}`);
1100
1449
  }
1101
1450
  const descPath = join(snapshotDir, "descriptions.md");
1102
1451
  writeFileSync(descPath, descriptions.join("\n"));
1103
- console.log(` ${c.dim("descriptions.md")} (Gemini frame analysis)`);
1452
+ describedPath = descPath;
1453
+ log(` ${c.dim("descriptions.md")} (Gemini frame analysis)`);
1104
1454
  }
1105
1455
  } catch (descErr) {
1106
1456
  const msg = normalizeErrorMessage(descErr);
1107
- console.log(` ${c.dim(`--describe failed: ${msg}`)}`);
1457
+ log(` ${c.dim(`--describe failed: ${msg}`)}`);
1108
1458
  }
1109
1459
  }
1460
+ if (asJson) {
1461
+ const envelope = snapshotEnvelope(project.dir, snapshotDir, captured, {
1462
+ contactSheets,
1463
+ reviewSheets,
1464
+ describedPath
1465
+ });
1466
+ console.log(JSON.stringify(withMeta(envelope), null, 2));
1467
+ }
1110
1468
  });
1111
1469
  } catch (err) {
1470
+ if (err instanceof CliRuntimeError && err.result.presented) throw err;
1112
1471
  const msg = normalizeErrorMessage(err);
1113
- console.error(`
1472
+ if (asJson) console.log(JSON.stringify(withMeta({ ok: false, error: msg }), null, 2));
1473
+ else console.error(`
1114
1474
  ${c.error("\u2717")} Snapshot failed: ${msg}`);
1115
1475
  failCommand();
1116
1476
  } finally {