hyperframes 0.8.184 → 0.8.186

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-5ZTD3MUZ.js → add-UGK7AZI6.js} +14 -14
  2. package/dist/{agentPromptGenerator-VIIPZCJ3.js → agentPromptGenerator-KYO5MKTW.js} +1 -1
  3. package/dist/{animatedGifPrep-AGASIWVA.js → animatedGifPrep-HPZPSB43.js} +9 -9
  4. package/dist/api.js +18 -18
  5. package/dist/{asset-LSTEMPIB.js → asset-X2GTFWOY.js} +2 -2
  6. package/dist/{assetCataloger-BKBMNL3A.js → assetCataloger-5NWEG4IU.js} +9 -9
  7. package/dist/audio-7PBM5F3H.js +2317 -0
  8. package/dist/{auth-MJWKYK77.js → auth-WABSMO46.js} +1 -1
  9. package/dist/{autoUpdate-2GGDKYR2.js → autoUpdate-6SCMLTSC.js} +2 -2
  10. package/dist/{backgroundChecks-7GRX5VQY.js → backgroundChecks-XOGPAAR3.js} +3 -3
  11. package/dist/backgroundChecksWorker.js +3 -3
  12. package/dist/{batchRender-BCV2LELR.js → batchRender-VR6IA77K.js} +5 -5
  13. package/dist/{beats-7BCFRBAL.js → beats-3IWXLMSC.js} +7 -7
  14. package/dist/{benchmark-2R6CN5MQ.js → benchmark-2SSYATCX.js} +10 -10
  15. package/dist/{browser-6AFGOI7G.js → browser-PMRN345D.js} +3 -3
  16. package/dist/{bundleWithLocalizedFonts-2EGPA5ZO.js → bundleWithLocalizedFonts-TT32MZBO.js} +2 -2
  17. package/dist/{capture-HOIAW6MO.js → capture-4VD6AKUJ.js} +13 -13
  18. package/dist/{capture-HA2UHLIU.js → capture-BDGYTE57.js} +2 -2
  19. package/dist/{captureCompositionFrame-4236FVWJ.js → captureCompositionFrame-5FVLAETA.js} +17 -13
  20. package/dist/{catalog-A6CZAWW4.js → catalog-HUYZO5D7.js} +14 -14
  21. package/dist/{catch-up-TNA4DJOA.js → catch-up-SSGTR2ZT.js} +4 -4
  22. package/dist/{check-5E5U2EQZ.js → check-JWUWHGM2.js} +34 -34
  23. package/dist/{checkBrowser-FNINXBMZ.js → checkBrowser-X7G6YNR7.js} +31 -31
  24. package/dist/{chunk-UJKUB2LH.js → chunk-22QKQDEE.js} +11 -11
  25. package/dist/{chunk-2PVE4WS2.js → chunk-27H2363S.js} +33 -6
  26. package/dist/{chunk-2ULXZ4PG.js → chunk-2II34JPM.js} +154 -21
  27. package/dist/{chunk-3QVMJ7GX.js → chunk-2PZZMAME.js} +11 -10
  28. package/dist/{chunk-X3CG5YQA.js → chunk-3YHGQJI6.js} +3 -3
  29. package/dist/{chunk-EEP5Y5IE.js → chunk-46HEFGXJ.js} +1 -1
  30. package/dist/{chunk-DWUGKEHR.js → chunk-4AFNEKU7.js} +1 -1
  31. package/dist/{chunk-R2BFTECP.js → chunk-4NTW45W5.js} +3 -3
  32. package/dist/{chunk-7HJALUIO.js → chunk-4XFFJAWS.js} +1 -1
  33. package/dist/{chunk-SN7KI5JA.js → chunk-5ZMIVTHN.js} +2 -2
  34. package/dist/{chunk-QL2PNIXC.js → chunk-5ZMXGNGC.js} +24 -16
  35. package/dist/{chunk-UKOTVJFV.js → chunk-6IOBAOU7.js} +1 -1
  36. package/dist/{chunk-JIUTXKWA.js → chunk-77QM6YRQ.js} +2 -1
  37. package/dist/{chunk-ES426XRJ.js → chunk-7AUMTARQ.js} +3 -3
  38. package/dist/{chunk-5CS4T6C6.js → chunk-7L37AB57.js} +1 -1
  39. package/dist/{chunk-RGXVEYHI.js → chunk-7M7TMM4S.js} +81 -10
  40. package/dist/{chunk-3TXNNBI2.js → chunk-7MLFYMFB.js} +2 -2
  41. package/dist/{chunk-MFAETWIF.js → chunk-7MVODWQF.js} +1 -1
  42. package/dist/{chunk-WHPN2TIT.js → chunk-ACLABKEF.js} +6 -6
  43. package/dist/{chunk-YMS5FOMC.js → chunk-B2A56S5C.js} +1 -1
  44. package/dist/{chunk-YALM6XN4.js → chunk-BRTDLVK3.js} +3 -3
  45. package/dist/{chunk-VDM3FEDP.js → chunk-DQZMJS2P.js} +1 -1
  46. package/dist/{chunk-PSKEKWVU.js → chunk-DTXDQ6OP.js} +1 -1
  47. package/dist/{chunk-Q33P4EAC.js → chunk-EZINX6HH.js} +3 -3
  48. package/dist/{chunk-3MDJIWOV.js → chunk-FCWFV4P7.js} +75 -35
  49. package/dist/{chunk-ABZYYUUH.js → chunk-FRLTKTOR.js} +1 -1
  50. package/dist/{chunk-E32ZQ2WO.js → chunk-GTMBII7X.js} +1 -1
  51. package/dist/{chunk-YOZPQCSR.js → chunk-HOFX63F2.js} +3 -3
  52. package/dist/{chunk-LRGYPXHF.js → chunk-HZ7M2MZA.js} +1 -1
  53. package/dist/{chunk-TYXH3EKP.js → chunk-I5JRXMCV.js} +16 -1
  54. package/dist/{chunk-LLC4CS6I.js → chunk-ID5AJ4L3.js} +3 -3
  55. package/dist/{chunk-FGJEUV4S.js → chunk-K6KBOORN.js} +48 -4
  56. package/dist/{chunk-EM7C7H7S.js → chunk-KNLSYCI5.js} +7 -7
  57. package/dist/{chunk-4N4P3ZWL.js → chunk-LDW3KVGK.js} +2 -2
  58. package/dist/{chunk-OLZENUPI.js → chunk-LLFQA7XF.js} +1 -1
  59. package/dist/chunk-LLMSX6V6.js +17 -0
  60. package/dist/{chunk-5UDRPPN2.js → chunk-MY6CVBDD.js} +7 -4
  61. package/dist/{chunk-7NRI7HYX.js → chunk-NPDPDEPP.js} +1 -1
  62. package/dist/{chunk-BCHSTE7U.js → chunk-PEIV2SOG.js} +1 -1
  63. package/dist/{chunk-ADDHW2FS.js → chunk-Q447CX26.js} +82 -12
  64. package/dist/{chunk-YWNJTZGN.js → chunk-R6IGQ5NO.js} +2 -2
  65. package/dist/{chunk-YWHCXIMD.js → chunk-RFJ6SX4U.js} +5 -5
  66. package/dist/{chunk-J3QM7AHP.js → chunk-RNZEQTWZ.js} +2 -2
  67. package/dist/{chunk-SAX7HQ5E.js → chunk-ROWEDRVX.js} +1 -1
  68. package/dist/{chunk-PFYWVUHA.js → chunk-RPUIMFHF.js} +1 -1
  69. package/dist/{chunk-OYLO6TYK.js → chunk-RUOG4VB7.js} +1 -1
  70. package/dist/{chunk-UNNXFKVG.js → chunk-RVZNSKRH.js} +2 -2
  71. package/dist/{chunk-VNVQGDPC.js → chunk-T4RP55P2.js} +1 -1
  72. package/dist/{chunk-GDEV42FA.js → chunk-TH7J3JZ5.js} +524 -42
  73. package/dist/{chunk-VHZ6DL3M.js → chunk-TMK4Q7JA.js} +10 -7
  74. package/dist/{chunk-74JMKLVU.js → chunk-TQ2RRB5V.js} +1 -1
  75. package/dist/{chunk-ICJEAFPD.js → chunk-U4BMRIE6.js} +16 -7
  76. package/dist/{chunk-HR23IKLZ.js → chunk-U7QTMRLW.js} +2 -2
  77. package/dist/{chunk-KN46TGHV.js → chunk-UAJCTEK2.js} +1 -1
  78. package/dist/{chunk-CXABAXB3.js → chunk-UJZT6HNN.js} +1 -1
  79. package/dist/chunk-UOZX5W5T.js +3852 -0
  80. package/dist/{chunk-FFJ5TDRD.js → chunk-VHBBV5DH.js} +10 -3
  81. package/dist/{chunk-ZRHO6YMM.js → chunk-WHMFW5ET.js} +4 -4
  82. package/dist/{chunk-WTFUDNW2.js → chunk-WLHNIIXU.js} +3 -3
  83. package/dist/{chunk-ZOCHDXDG.js → chunk-Y2M6XSNK.js} +1 -1
  84. package/dist/{chunk-2XA7KXQO.js → chunk-YNH6ERDL.js} +2 -2
  85. package/dist/{chunk-JB6ARMQS.js → chunk-YQICCAVA.js} +1 -1
  86. package/dist/{chunk-7MB2ZAQF.js → chunk-YRAJDFIN.js} +1 -1
  87. package/dist/{chunk-IWTNTUPQ.js → chunk-Z5233OAN.js} +11 -13
  88. package/dist/{chunk-TE427YID.js → chunk-ZKBW7NU2.js} +2 -2
  89. package/dist/{chunk-4KKKBEP3.js → chunk-ZVBEGBII.js} +3 -3
  90. package/dist/{clean-HGASSMWI.js → clean-IM2JIAUT.js} +18 -18
  91. package/dist/cli.js +53 -53
  92. package/dist/{cloud-JPDVNXHN.js → cloud-6IPBCJSX.js} +4 -4
  93. package/dist/{cloudrun-AMMGSDRP.js → cloudrun-MWZ6S2I2.js} +7 -7
  94. package/dist/commands/contrast-audit.browser.js +1 -2
  95. package/dist/commands/layout-audit.browser.js +196 -0
  96. package/dist/{compare-LHHVHUN3.js → compare-WD7A2TV5.js} +28 -28
  97. package/dist/{compiler-Q3N6QXMT.js → compiler-2EKSMC3I.js} +6 -6
  98. package/dist/{component-D72J32Q7.js → component-JKALWH36.js} +3 -3
  99. package/dist/{compositions-UQRJ5ROG.js → compositions-643NAG4A.js} +13 -13
  100. package/dist/{delete-P42VIDDA.js → delete-EASPXKHQ.js} +2 -2
  101. package/dist/{deploy-WZTH57O4.js → deploy-CDKCQ5MX.js} +1 -1
  102. package/dist/{deterministicFonts-VQ5UBMXY.js → deterministicFonts-EGAAE7QL.js} +3 -3
  103. package/dist/{dist-DYS24AMA.js → dist-F6FOKRSU.js} +10 -4
  104. package/dist/{dist-SENYPQGW.js → dist-LM7AVHOU.js} +3 -3
  105. package/dist/docs/data-attributes.md +1 -1
  106. package/dist/{doctor-KEJIG2SK.js → doctor-7XUVYDJQ.js} +13 -13
  107. package/dist/{events-CYKSNR7N.js → events-5TTPYBKG.js} +3 -3
  108. package/dist/{events-UZXFQPMT.js → events-TULV6D3G.js} +3 -3
  109. package/dist/{feedback-LXNQF7CA.js → feedback-4FTPWM6P.js} +8 -8
  110. package/dist/{ffmpeg-BYHON577.js → ffmpeg-MP7ZVYLZ.js} +9 -9
  111. package/dist/{figma-EIQ35PS6.js → figma-B7LFWYKO.js} +3 -3
  112. package/dist/fontLocalizeCli.js +12 -12
  113. package/dist/{get-CC2YL7QO.js → get-F7W4AQ2V.js} +2 -2
  114. package/dist/{gpuPolicy-VFSACUQV.js → gpuPolicy-NSNVDFXC.js} +9 -9
  115. package/dist/{grade-compare-TUQL7IV2.js → grade-compare-6QXDTLS2.js} +29 -29
  116. package/dist/{gsapParserAcorn-7NCK2LVT.js → gsapParserAcorn-SKODXC2N.js} +2 -2
  117. package/dist/{help-ROROO5K6.js → help-RJAA4O74.js} +1 -1
  118. package/dist/{history-PNELYLXX.js → history-OOQBDVV5.js} +6 -6
  119. package/dist/hyperframe.manifest.json +1 -1
  120. package/dist/hyperframe.runtime.iife.js +67 -67
  121. package/dist/hyperframes-player.global.js +1 -1
  122. package/dist/{info-4FZTH6AS.js → info-5OGGYYLO.js} +9 -9
  123. package/dist/{init-I43GHHVP.js → init-DM3TJKW2.js} +68 -50
  124. package/dist/{inspect-IT4TYIZ3.js → inspect-FECQCLS5.js} +29 -29
  125. package/dist/{installer-CWOJVK5K.js → installer-WOG4VORV.js} +7 -7
  126. package/dist/{keyframes-3FBOGVD4.js → keyframes-SZHWUF5Y.js} +7 -7
  127. package/dist/{lambda-H3V32I7M.js → lambda-JG2ZRGMC.js} +9 -9
  128. package/dist/{layout-BZ5LYJ5C.js → layout-7ACF7ZKC.js} +29 -29
  129. package/dist/{lint-ZTNSGP2S.js → lint-ZCQGMILV.js} +9 -9
  130. package/dist/{lintProject-C2ONSH2U.js → lintProject-JATQKR64.js} +3 -3
  131. package/dist/{list-XM4GWJYY.js → list-MH2X3Y2T.js} +2 -2
  132. package/dist/{login-H6HYKMWX.js → login-CK46WX5U.js} +4 -4
  133. package/dist/{manager-TWVDSJXO.js → manager-GXTPNJVW.js} +10 -10
  134. package/dist/{media-treatment-N7MY5NXB.js → media-treatment-EH2AT34Z.js} +15 -15
  135. package/dist/{models-WEMIMYBD.js → models-D5XP2UK6.js} +13 -13
  136. package/dist/{motionShot-RHICTVPU.js → motionShot-UR7D3SRQ.js} +16 -16
  137. package/dist/{normalize-4UDISLPL.js → normalize-3AYFSN2F.js} +1 -1
  138. package/dist/{normalize-audio-ZFUZCJE3.js → normalize-audio-635JOR5W.js} +4 -4
  139. package/dist/{open-IBJ4FL3G.js → open-6WE5WFVM.js} +14 -14
  140. package/dist/{parakeetRunner-XXO4WSLR.js → parakeetRunner-STHZPG32.js} +14 -14
  141. package/dist/{pipeline-6ML3LUVK.js → pipeline-LVEKRETU.js} +10 -10
  142. package/dist/{play-RV4NAVF5.js → play-W6TDW4FE.js} +21 -21
  143. package/dist/{preflight-R3DEFMLA.js → preflight-LIMQXLR7.js} +10 -10
  144. package/dist/{present-5TBJGE6B.js → present-RX5QX6UR.js} +17 -17
  145. package/dist/{preview-FLKZ5CXJ.js → preview-KIWTWK63.js} +20 -20
  146. package/dist/{producer-7XYSPVKH.js → producer-TSNKSCUM.js} +1 -1
  147. package/dist/{projectConfig-TBU2VWZX.js → projectConfig-NCP7JRH4.js} +9 -9
  148. package/dist/{publication-CWWUJYJ5.js → publication-5W5UZWGC.js} +4 -4
  149. package/dist/{publish-FSNE4FEB.js → publish-7WR4XJ6I.js} +11 -11
  150. package/dist/{remove-background-MDFWRWCT.js → remove-background-43JGBRWQ.js} +11 -11
  151. package/dist/{render-F4JQ553X.js → render-456HCO6O.js} +31 -31
  152. package/dist/{render-LWZKXCLY.js → render-7T44HIM6.js} +5 -5
  153. package/dist/{render-DBGQZAZT.js → render-WHMLYIOI.js} +18 -30
  154. package/dist/{render-batch-PU7NMLIJ.js → render-batch-LURBJTBV.js} +5 -5
  155. package/dist/renderSetupWorker.js +3 -3
  156. package/dist/{resolver-RUBGFXPC.js → resolver-MBIW4VC4.js} +6 -6
  157. package/dist/{sherpa-X2TBNO6P.js → sherpa-5J2AALED.js} +13 -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-WHKMWO2D.js → skills-UHOXOGGF.js} +20 -13
  262. package/dist/{skillsUpdateCheck-XQGZGJJG.js → skillsUpdateCheck-PDZIFUJE.js} +3 -3
  263. package/dist/{snapshot-M4NUUSNU.js → snapshot-PFV7ALLH.js} +462 -100
  264. package/dist/{src-4UYM2K3S.js → src-DCVSEODX.js} +8 -8
  265. package/dist/{src-6MSC62U2.js → src-XK5YIR6L.js} +11 -11
  266. package/dist/{staticProjectServer-ERDTFP45.js → staticProjectServer-4IE4G4JO.js} +17 -17
  267. package/dist/studio/assets/{hyperframes-player-DEncBxR7.js → hyperframes-player-B_skzvYC.js} +1 -1
  268. package/dist/studio/assets/{index-Cc22mgvY.js → index-CgLbvCUo.js} +1 -1
  269. package/dist/studio/assets/{index-BABXr78Q.js → index-Dn5FAk2e.js} +99 -99
  270. package/dist/studio/index.html +1 -1
  271. package/dist/{studio-api-SAKD3VKC.js → studio-api-JMWYU7KF.js} +11 -11
  272. package/dist/{studioServer-IRI7EVOM.js → studioServer-BWLSTGIG.js} +50 -49
  273. package/dist/{telemetry-F4XADVAF.js → telemetry-JCU3AV7W.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-5I444RRE.js → timeline-BCIFQYOM.js} +20 -19
  278. package/dist/{tokens-LRRYM35A.js → tokens-7IXKALMQ.js} +2 -2
  279. package/dist/{transcribe-44XDFNVU.js → transcribe-HW7HGJ4V.js} +19 -19
  280. package/dist/{transcribe-WHYHWAB2.js → transcribe-ZUQD3B32.js} +12 -12
  281. package/dist/{updateCheck-7CPH3CAZ.js → updateCheck-Z6LNEK7B.js} +2 -2
  282. package/dist/{upgrade-WW4GQ2RI.js → upgrade-JYH72JGL.js} +2 -2
  283. package/dist/{urlDownloader-OHLLP45A.js → urlDownloader-WKLFHSUH.js} +8 -8
  284. package/dist/{validate-VNCDH2LW.js → validate-QCC27YYI.js} +23 -23
  285. package/dist/{video-5OHTLL2D.js → video-EZ7ONY6L.js} +9 -9
  286. package/dist/warmCheck.js +36 -36
  287. package/package.json +1 -1
  288. package/dist/audio-KGK44XE4.js +0 -1153
  289. package/dist/chunk-R2SQBPMH.js +0 -3805
  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-7HJALUIO.js";
28
+ } from "./chunk-4XFFJAWS.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-VDM3FEDP.js";
34
- import "./chunk-FGJEUV4S.js";
36
+ } from "./chunk-DQZMJS2P.js";
37
+ import "./chunk-K6KBOORN.js";
35
38
  import "./chunk-X7MTZ4T2.js";
36
39
  import {
37
40
  resolveProject
38
- } from "./chunk-LRGYPXHF.js";
41
+ } from "./chunk-HZ7M2MZA.js";
39
42
  import {
40
43
  serveStaticProjectHtml
41
- } from "./chunk-4KKKBEP3.js";
42
- import "./chunk-YWNJTZGN.js";
44
+ } from "./chunk-ZVBEGBII.js";
45
+ import "./chunk-R6IGQ5NO.js";
43
46
  import "./chunk-X7MTZ4T2.js";
44
47
  import "./chunk-XUHOIDKG.js";
45
- import "./chunk-3QVMJ7GX.js";
46
- import "./chunk-ZRHO6YMM.js";
48
+ import "./chunk-2PZZMAME.js";
49
+ import "./chunk-WHMFW5ET.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-SN7KI5JA.js";
59
+ import "./chunk-5ZMIVTHN.js";
57
60
  import "./chunk-ALVXFXUG.js";
58
61
  import {
59
62
  DEFAULT_ZOOM_SCALE,
@@ -62,44 +65,46 @@ import {
62
65
  parseZoomTarget,
63
66
  resolveCropRegion,
64
67
  runFfmpegOnce,
65
- seekCompositionTimeline
66
- } from "./chunk-ICJEAFPD.js";
68
+ seekCompositionTimelineOnly,
69
+ settleCompositionSeek
70
+ } from "./chunk-U4BMRIE6.js";
67
71
  import "./chunk-FH7WODJH.js";
68
- import "./chunk-KN46TGHV.js";
72
+ import "./chunk-UAJCTEK2.js";
69
73
  import "./chunk-MOEGCMKI.js";
70
- import "./chunk-ES426XRJ.js";
74
+ import "./chunk-7AUMTARQ.js";
71
75
  import "./chunk-DKPNNYBZ.js";
72
- import "./chunk-PSKEKWVU.js";
76
+ import "./chunk-DTXDQ6OP.js";
73
77
  import "./chunk-K2FLNJVB.js";
74
78
  import "./chunk-442JNXRM.js";
75
79
  import "./chunk-EHSBBBR2.js";
76
80
  import "./chunk-DQBY7FRR.js";
77
81
  import {
78
82
  resolveLocalBrowserGpuMode
79
- } from "./chunk-UNNXFKVG.js";
80
- import "./chunk-PFYWVUHA.js";
83
+ } from "./chunk-RVZNSKRH.js";
84
+ import "./chunk-RPUIMFHF.js";
81
85
  import "./chunk-XT7X6QCP.js";
82
86
  import "./chunk-4YYJOFKS.js";
83
87
  import "./chunk-HVW7ABXD.js";
84
- import "./chunk-ZOCHDXDG.js";
88
+ import "./chunk-Y2M6XSNK.js";
85
89
  import {
86
90
  runWithConcurrencyLimit
87
91
  } from "./chunk-JHOCK3DG.js";
88
- import "./chunk-LLC4CS6I.js";
89
- import "./chunk-7MB2ZAQF.js";
92
+ import "./chunk-ID5AJ4L3.js";
93
+ import "./chunk-YRAJDFIN.js";
90
94
  import "./chunk-AWDFDFI3.js";
91
95
  import "./chunk-P6LJ4Y32.js";
92
96
  import "./chunk-GMECXFAN.js";
93
- import "./chunk-5CS4T6C6.js";
97
+ import "./chunk-7L37AB57.js";
94
98
  import "./chunk-EOVYQ3IG.js";
95
- import "./chunk-JIUTXKWA.js";
96
- import "./chunk-UKOTVJFV.js";
99
+ import "./chunk-77QM6YRQ.js";
100
+ import "./chunk-6IOBAOU7.js";
97
101
  import "./chunk-VWG7OQKU.js";
98
102
  import {
99
103
  defineCommand
100
104
  } from "./chunk-S3S4TGEH.js";
101
105
  import "./chunk-54HJ63Q2.js";
102
106
  import {
107
+ CliRuntimeError,
103
108
  failCommand
104
109
  } from "./chunk-JVBRJMOP.js";
105
110
  import {
@@ -108,23 +113,26 @@ import {
108
113
  import "./chunk-KLIKCX6Q.js";
109
114
  import "./chunk-EVMY7IPL.js";
110
115
  import "./chunk-7FMUTGOH.js";
111
- import "./chunk-RGXVEYHI.js";
116
+ import "./chunk-7M7TMM4S.js";
112
117
  import "./chunk-IZCW2NKT.js";
113
118
  import {
114
119
  createRuntimeStartTimeResolver,
115
120
  isClipVisibleAt,
116
121
  isInClipWindow
117
- } from "./chunk-J3QM7AHP.js";
122
+ } from "./chunk-RNZEQTWZ.js";
118
123
  import "./chunk-RQVDTPGD.js";
119
- import "./chunk-YALM6XN4.js";
124
+ import "./chunk-BRTDLVK3.js";
120
125
  import {
121
126
  readElementRateSpec,
122
127
  sourceTimeAt,
123
128
  timeAtSourceTime
124
- } from "./chunk-R2SQBPMH.js";
129
+ } from "./chunk-UOZX5W5T.js";
125
130
  import "./chunk-VC76R6W2.js";
126
131
  import "./chunk-UJKQWGFF.js";
127
- import "./chunk-YMS5FOMC.js";
132
+ import {
133
+ topLevelElements,
134
+ trackKindOf
135
+ } from "./chunk-B2A56S5C.js";
128
136
  import "./chunk-OFQWTKPW.js";
129
137
  import "./chunk-DPVXBVLL.js";
130
138
  import "./chunk-3GXQPCJC.js";
@@ -142,23 +150,26 @@ import "./chunk-XT6MXC7A.js";
142
150
  import "./chunk-4UMPIPCQ.js";
143
151
  import {
144
152
  lintProject
145
- } from "./chunk-ADDHW2FS.js";
153
+ } from "./chunk-Q447CX26.js";
146
154
  import "./chunk-VAS6HQX3.js";
147
155
  import "./chunk-6BNTGNGD.js";
148
- import "./chunk-OYLO6TYK.js";
156
+ import "./chunk-RUOG4VB7.js";
149
157
  import "./chunk-MMQ52NIZ.js";
150
158
  import "./chunk-BSQ7XANM.js";
151
159
  import {
152
160
  parseHTML
153
161
  } from "./chunk-SDYDNHEE.js";
154
162
  import "./chunk-IXLUTE6N.js";
155
- import "./chunk-TYXH3EKP.js";
163
+ import "./chunk-I5JRXMCV.js";
156
164
  import "./chunk-W2LGJSEN.js";
165
+ import {
166
+ withMeta
167
+ } from "./chunk-FRLTKTOR.js";
157
168
  import "./chunk-K4WIOZFP.js";
158
169
  import "./chunk-XUXGIA7H.js";
159
170
  import "./chunk-WERLJGIC.js";
160
171
  import "./chunk-ISN5GRYI.js";
161
- import "./chunk-4N4P3ZWL.js";
172
+ import "./chunk-LDW3KVGK.js";
162
173
  import {
163
174
  normalizeErrorMessage
164
175
  } from "./chunk-KNU5COPB.js";
@@ -182,6 +193,15 @@ function sampleSnapshotRange([start, end], frames, duration, exclusiveEnd = fals
182
193
  const last = exclusiveEnd || end === duration ? end - Math.min(1e-3, (end - start) / (frames * 2)) : end;
183
194
  return Array.from({ length: frames }, (_, i) => start + (last - start) * i / (frames - 1));
184
195
  }
196
+ function snapshotStartResolver(document2, durations) {
197
+ return createRuntimeStartTimeResolver({
198
+ documentRef: document2,
199
+ timelineRegistry: Object.fromEntries(
200
+ Object.entries(durations).map(([key, value]) => [key, { duration: () => value }])
201
+ ),
202
+ includeAuthoredTimingAttrs: true
203
+ });
204
+ }
185
205
  function resolveSnapshotCompositionRange(html, durations, id, duration) {
186
206
  const { document: document2 } = parseHTML(html);
187
207
  const matches = Array.from(document2.querySelectorAll("[data-composition-id]")).filter(
@@ -191,14 +211,7 @@ function resolveSnapshotCompositionRange(html, durations, id, duration) {
191
211
  throw new Error(
192
212
  `Composition ID ${JSON.stringify(id)} matched ${matches.length} elements; expected exactly one`
193
213
  );
194
- const registry = Object.fromEntries(
195
- Object.entries(durations).map(([key, value]) => [key, { duration: () => value }])
196
- );
197
- const resolver = createRuntimeStartTimeResolver({
198
- documentRef: document2,
199
- timelineRegistry: registry,
200
- includeAuthoredTimingAttrs: true
201
- });
214
+ const resolver = snapshotStartResolver(document2, durations);
202
215
  let start = 0, end = duration;
203
216
  for (let el = matches[0]; el; el = el.parentElement) {
204
217
  if (!el.hasAttribute("data-composition-id") && !el.hasAttribute("data-start")) continue;
@@ -219,6 +232,208 @@ import { randomUUID } from "crypto";
219
232
  import { existsSync, mkdtempSync, readFileSync, mkdirSync, rmSync, writeFileSync } from "fs";
220
233
  import { tmpdir } from "os";
221
234
  import { resolve, join, relative, isAbsolute, basename, posix } from "path";
235
+
236
+ // src/commands/snapshotReview.ts
237
+ var REVIEW_SETTLE_SEC = 0.3;
238
+ var REVIEW_MID_GAP_SEC = 0.5;
239
+ var REVIEW_SEAM_OFFSETS = [-0.2, -0.1, 0, 0.1, 0.2];
240
+ var REVIEW_PHONE_WIDTH = 360;
241
+ var SAME_TIME_SEC = 0.05;
242
+ var EVEN_BEATS = 5;
243
+ function roundMs(value) {
244
+ return Math.round(value * 1e3) / 1e3;
245
+ }
246
+ function sceneId(el, index) {
247
+ return el.id || el.getAttribute("data-composition-id") || el.getAttribute("data-layout-name") || `${el.tagName.toLowerCase()}-${index + 1}`;
248
+ }
249
+ function resolveReviewTimeline(html, durations, duration, compositionId) {
250
+ const bounds = compositionId ? resolveSnapshotCompositionRange(html, durations, compositionId, duration) : [0, duration];
251
+ const { document: document2 } = parseHTML(html);
252
+ const target = compositionId ? Array.from(document2.querySelectorAll("[data-composition-id]")).find(
253
+ (el) => el.getAttribute("data-composition-id") === compositionId
254
+ ) : document2.querySelector("[data-composition-id]");
255
+ if (!target) {
256
+ return { window: bounds, scenes: [], beats: [], notes: ["No composition root found."] };
257
+ }
258
+ const resolver = snapshotStartResolver(document2, durations);
259
+ const span = (el) => {
260
+ const start = resolver.resolveStartForElement(el, 0);
261
+ if (!Number.isFinite(start)) return null;
262
+ const length = resolver.resolveDurationForElement(el);
263
+ const from = Math.max(bounds[0], start);
264
+ const to = Math.min(bounds[1], length != null ? start + length : bounds[1]);
265
+ return to > from ? [roundMs(from), roundMs(to)] : null;
266
+ };
267
+ const scenes = [];
268
+ const rows = topLevelElements(toDomNode(target)).filter((node) => {
269
+ const kind = trackKindOf(node).kind;
270
+ return kind === "video" || kind === "graphics";
271
+ });
272
+ rows.forEach((node, index) => {
273
+ const interval = span(node.el);
274
+ if (interval)
275
+ scenes.push({ id: sceneId(node.el, index), start: interval[0], end: interval[1] });
276
+ });
277
+ const beats = [];
278
+ const notes = [];
279
+ for (const el of Array.from(target.querySelectorAll("[data-beat]"))) {
280
+ const name = el.getAttribute("data-beat")?.trim() || sceneId(el, beats.length);
281
+ const timed = el.hasAttribute("data-start") || el.hasAttribute("data-composition-id");
282
+ const interval = timed ? span(el) : null;
283
+ if (!interval) {
284
+ notes.push(`data-beat="${name}" was skipped: give it a data-start so the beat has a time.`);
285
+ continue;
286
+ }
287
+ beats.push({ time: settledTime(interval[0], interval[1]), label: `beat:${name}` });
288
+ }
289
+ scenes.sort((a, b) => a.start - b.start || a.end - b.end);
290
+ return { window: bounds, scenes, beats, notes };
291
+ }
292
+ function sceneBeats(scene) {
293
+ const settled = settledTime(scene.start, scene.end);
294
+ const middle = roundMs((scene.start + scene.end) / 2);
295
+ const beats = [{ time: settled, label: scene.id }];
296
+ return middle - settled >= REVIEW_MID_GAP_SEC ? [...beats, { time: middle, label: `${scene.id} mid` }] : beats;
297
+ }
298
+ function settledTime(start, end) {
299
+ return roundMs(Math.min(start + REVIEW_SETTLE_SEC, (start + end) / 2));
300
+ }
301
+ function planReview(timeline, lastFrame) {
302
+ const { window: bounds, scenes } = timeline;
303
+ const notes = [...timeline.notes];
304
+ const clamp = (time) => roundMs(Math.min(Math.max(time, bounds[0]), lastFrame));
305
+ const distinctStarts = mergeBeats(
306
+ scenes.map((scene) => ({ time: scene.start, label: scene.id }))
307
+ );
308
+ const beats = mergeBeats([
309
+ ...distinctStarts.length >= 2 ? scenes.flatMap(sceneBeats) : evenBeats(bounds, notes),
310
+ ...timeline.beats
311
+ ]).map((beat) => ({ ...beat, time: clamp(beat.time) }));
312
+ const seams = seamTimes(scenes, bounds).map((time) => ({
313
+ time,
314
+ ...seamSides(scenes, time),
315
+ times: REVIEW_SEAM_OFFSETS.map((offset) => clamp(time + offset))
316
+ }));
317
+ return { window: bounds, scenes, beats, seams, notes };
318
+ }
319
+ function evenBeats(bounds, notes) {
320
+ notes.push(
321
+ `Fewer than two scene starts; ${EVEN_BEATS} beats are spread evenly instead. Time scenes as sub-composition hosts or clips to review each one.`
322
+ );
323
+ const [start, end] = bounds;
324
+ return Array.from({ length: EVEN_BEATS }, (_, index) => {
325
+ const time = roundMs(start + (index + 0.5) / EVEN_BEATS * (end - start));
326
+ return { time, label: `t=${time}` };
327
+ });
328
+ }
329
+ function mergeBeats(beats) {
330
+ const merged = [];
331
+ for (const beat of [...beats].sort((a, b) => a.time - b.time)) {
332
+ const same = merged.find((entry) => Math.abs(entry.time - beat.time) < SAME_TIME_SEC);
333
+ if (same) same.label = `${same.label} + ${beat.label}`;
334
+ else merged.push({ ...beat });
335
+ }
336
+ return merged;
337
+ }
338
+ function seamTimes(scenes, bounds) {
339
+ const inside = (time) => time > bounds[0] + SAME_TIME_SEC && time < bounds[1] - SAME_TIME_SEC;
340
+ const starts = scenes.map((scene) => scene.start).filter(inside);
341
+ const ends = scenes.map((scene) => scene.end).filter(inside).filter((end) => !starts.some((start) => Math.abs(start - end) < SAME_TIME_SEC));
342
+ const seams = [];
343
+ for (const time of [...starts, ...ends].sort((a, b) => a - b)) {
344
+ if (!seams.some((seam) => Math.abs(seam - time) < SAME_TIME_SEC)) seams.push(time);
345
+ }
346
+ return seams;
347
+ }
348
+ function seamSides(scenes, time) {
349
+ 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];
350
+ const after = scenes.filter((scene) => Math.abs(scene.start - time) < SAME_TIME_SEC).sort((a, b) => a.end - b.end)[0];
351
+ const next = after ?? scenes.find((scene) => scene.start > time);
352
+ return { from: before?.id ?? null, to: next?.id ?? null };
353
+ }
354
+ function pad(index) {
355
+ return String(index).padStart(2, "0");
356
+ }
357
+ function stamp(time) {
358
+ return `${Number(time.toFixed(3))}s`;
359
+ }
360
+ var SEAM_TILE_LABELS = ["cut -0.2", "-0.1", "cut", "+0.1", "+0.2"];
361
+ function reviewFrames(plan) {
362
+ const beats = plan.beats.map((beat, index) => ({
363
+ role: "beat",
364
+ index: index + 1,
365
+ time: beat.time,
366
+ label: beat.label,
367
+ file: `beat-${pad(index + 1)}-at-${stamp(beat.time)}.png`
368
+ }));
369
+ const seams = plan.seams.flatMap(
370
+ (seam, index) => seam.times.map((time, slot) => ({
371
+ role: "seam",
372
+ index: index + 1,
373
+ time,
374
+ label: slot === 2 ? `cut ${seam.from ?? "start"} -> ${seam.to ?? "end"}` : SEAM_TILE_LABELS[slot] ?? "",
375
+ file: `seam-${pad(index + 1)}-${slot + 1}-at-${stamp(time)}.png`
376
+ }))
377
+ );
378
+ return [...beats, ...seams];
379
+ }
380
+ var BEAT_SHEET_PAGE = 16;
381
+ async function writeReviewSheets(dir, frames, written) {
382
+ const { createContactSheet } = await import("./contactSheet-CAGMKI3E.js");
383
+ const { join: join2 } = await import("path");
384
+ const captured = frames.flatMap((frame) => {
385
+ const path = written(frame);
386
+ return path ? [{ frame, path }] : [];
387
+ });
388
+ const beats = captured.filter((entry) => entry.frame.role === "beat");
389
+ const beatLabel = ({ frame }) => `${stamp(frame.time)} ${frame.label}`;
390
+ const pages = async (name, cellWidth, colsFor) => {
391
+ const sheets = [];
392
+ for (let start = 0; start < beats.length; start += BEAT_SHEET_PAGE) {
393
+ const page = beats.slice(start, start + BEAT_SHEET_PAGE);
394
+ const suffix = beats.length > BEAT_SHEET_PAGE ? `-${start / BEAT_SHEET_PAGE + 1}` : "";
395
+ const sheet = await createContactSheet(
396
+ page.map((entry) => entry.path),
397
+ join2(dir, `${name}${suffix}.jpg`),
398
+ {
399
+ cols: colsFor(page.length),
400
+ maxImages: page.length,
401
+ cellWidth,
402
+ labelMode: "custom",
403
+ labels: page.map(beatLabel)
404
+ }
405
+ );
406
+ if (sheet) sheets.push(sheet);
407
+ }
408
+ return sheets;
409
+ };
410
+ const seams = [];
411
+ const seamNumbers = [
412
+ ...new Set(captured.filter((e) => e.frame.role === "seam").map((e) => e.frame.index))
413
+ ];
414
+ for (const index of seamNumbers) {
415
+ const strip = captured.filter((e) => e.frame.role === "seam" && e.frame.index === index);
416
+ const sheet = await createContactSheet(
417
+ strip.map((entry) => entry.path),
418
+ join2(dir, `review-seam-${pad(index)}.jpg`),
419
+ {
420
+ cols: strip.length,
421
+ maxImages: strip.length,
422
+ cellWidth: 384,
423
+ labelMode: "custom",
424
+ labels: strip.map(({ frame }) => `${stamp(frame.time)} ${frame.label}`)
425
+ }
426
+ );
427
+ if (sheet) seams.push(sheet);
428
+ }
429
+ return {
430
+ beats: await pages("review-beats", 480, (count) => count <= 3 ? count : count <= 9 ? 3 : 4),
431
+ seams,
432
+ phone: await pages("review-phone", REVIEW_PHONE_WIDTH, (count) => Math.min(4, count))
433
+ };
434
+ }
435
+
436
+ // src/commands/snapshot.ts
222
437
  function orbitStageSource() {
223
438
  return `function(cam) {
224
439
  var root = document.querySelector("[data-composition-id]")
@@ -479,7 +694,8 @@ function serveCapturePage(projectDir, html, proxy) {
479
694
  return serveStaticProjectHtml(projectDir, html, void 0, [], proxy === true);
480
695
  }
481
696
  async function captureSnapshots(projectDir, opts) {
482
- const { bundleWithLocalizedFonts } = await import("./bundleWithLocalizedFonts-2EGPA5ZO.js");
697
+ const log = opts.log ?? ((message) => console.log(message));
698
+ const { bundleWithLocalizedFonts } = await import("./bundleWithLocalizedFonts-TT32MZBO.js");
483
699
  const capture = await captureArtifactRevision(projectDir).catch(warnSnapshotsNotRecorded);
484
700
  const invocation = randomUUID();
485
701
  const artifacts = [];
@@ -487,6 +703,9 @@ async function captureSnapshots(projectDir, opts) {
487
703
  const warnings = [];
488
704
  let completed = false;
489
705
  const numFrames = opts.frames ?? 5;
706
+ let review;
707
+ let reviewList;
708
+ const reviewPaths = /* @__PURE__ */ new Map();
490
709
  const html = await bundleWithLocalizedFonts(projectDir);
491
710
  const server = await serveCapturePage(projectDir, html, opts.autoProxy);
492
711
  const savedPaths = [];
@@ -518,10 +737,10 @@ async function captureSnapshots(projectDir, opts) {
518
737
  const parts = [`${fontReport.loaded.length} loaded`];
519
738
  if (fontReport.errored.length > 0) parts.push(`${fontReport.errored.length} failed`);
520
739
  if (fontReport.unused.length > 0) parts.push(`${fontReport.unused.length} unused`);
521
- console.log(`
740
+ log(`
522
741
  ${c.dim("Fonts:")} ${parts.join(", ")}`);
523
742
  if (fontReport.errored.length > 0) {
524
- console.log(` ${c.error("Fonts FAILED:")} ${fontReport.errored.join(", ")}`);
743
+ log(` ${c.error("Fonts FAILED:")} ${fontReport.errored.join(", ")}`);
525
744
  }
526
745
  }
527
746
  if (fontReport.errored.length)
@@ -537,19 +756,20 @@ async function captureSnapshots(projectDir, opts) {
537
756
  return 0;
538
757
  });
539
758
  if (duration <= 0 && !opts.at?.length) {
540
- return [];
759
+ return { paths: [] };
541
760
  }
761
+ const readTiming = () => page.evaluate(() => ({
762
+ html: document.documentElement.outerHTML,
763
+ durations: Object.fromEntries(
764
+ Object.entries(window.__timelines ?? {}).map(([id, timeline]) => [
765
+ id,
766
+ Number(timeline.duration?.())
767
+ ])
768
+ )
769
+ }));
542
770
  let range = opts.range;
543
- if (opts.composition) {
544
- const timing = await page.evaluate(() => ({
545
- html: document.documentElement.outerHTML,
546
- durations: Object.fromEntries(
547
- Object.entries(window.__timelines ?? {}).map(([id, timeline]) => [
548
- id,
549
- Number(timeline.duration?.())
550
- ])
551
- )
552
- }));
771
+ if (opts.composition && !opts.review) {
772
+ const timing = await readTiming();
553
773
  range = resolveSnapshotCompositionRange(
554
774
  timing.html,
555
775
  timing.durations,
@@ -557,7 +777,18 @@ async function captureSnapshots(projectDir, opts) {
557
777
  duration
558
778
  );
559
779
  }
560
- const { times: positions, appendedTail } = range ? {
780
+ if (opts.review) {
781
+ const timing = await readTiming();
782
+ const timeline = resolveReviewTimeline(
783
+ timing.html,
784
+ timing.durations,
785
+ duration,
786
+ opts.composition
787
+ );
788
+ review = planReview(timeline, Math.min(timeline.window[1], duration) - 1 / 30);
789
+ reviewList = reviewFrames(review);
790
+ }
791
+ const { times: positions, appendedTail } = reviewList ? { times: reviewList.map((frame) => frame.time), appendedTail: false } : range ? {
561
792
  times: sampleSnapshotRange(range, numFrames, duration, !!opts.composition),
562
793
  appendedTail: false
563
794
  } : computeSnapshotTimes(duration, {
@@ -566,11 +797,15 @@ async function captureSnapshots(projectDir, opts) {
566
797
  includeEnd: opts.includeEnd
567
798
  });
568
799
  if (range)
569
- console.log(
800
+ log(
570
801
  ` Sampling ${positions.length} frames within ${range[0]}\u2013${range[1]}s${opts.composition ? ` (composition ${opts.composition})` : ""}`
571
802
  );
803
+ if (review)
804
+ log(
805
+ ` 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})` : ""}`
806
+ );
572
807
  if (appendedTail) {
573
- console.log(
808
+ log(
574
809
  ` ${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.`)}`
575
810
  );
576
811
  }
@@ -592,7 +827,7 @@ async function captureSnapshots(projectDir, opts) {
592
827
  let syncVideoFrameVisibility = null;
593
828
  let extractMediaMetadata = null;
594
829
  try {
595
- const engine = await import("./src-4UYM2K3S.js");
830
+ const engine = await import("./src-DCVSEODX.js");
596
831
  injectVideoFramesBatch = engine.injectVideoFramesBatch;
597
832
  syncVideoFrameVisibility = engine.syncVideoFrameVisibility;
598
833
  extractMediaMetadata = engine.extractMediaMetadata;
@@ -617,7 +852,8 @@ async function captureSnapshots(projectDir, opts) {
617
852
  }
618
853
  for (let i = 0; i < positions.length; i++) {
619
854
  const time = positions[i];
620
- await seekCompositionTimeline(page, time, { exactTime: true });
855
+ const seekOptions = { exactTime: true };
856
+ await seekCompositionTimelineOnly(page, time, seekOptions);
621
857
  if (cameraExpr) await page.evaluate(cameraExpr);
622
858
  if (injectVideoFramesBatch && syncVideoFrameVisibility) {
623
859
  const candidates = await page.evaluate(() => {
@@ -727,10 +963,12 @@ async function captureSnapshots(projectDir, opts) {
727
963
  );
728
964
  }
729
965
  }
966
+ await settleCompositionSeek(page, seekOptions);
730
967
  await recaptureSnapshotComposite(page);
731
968
  const timeLabel = formatSnapshotTimestamp(time);
732
969
  const slot = opts.keep ? `at-${timeLabel}` : `${String(i).padStart(2, "0")}-at-${timeLabel}`;
733
- const filename = opts.keep ? `${slot}.png` : `frame-${slot}.png`;
970
+ const reviewFrame = reviewList?.[i];
971
+ const filename = reviewFrame ? reviewFrame.file : opts.keep ? `${slot}.png` : `frame-${slot}.png`;
734
972
  const framePath = join(snapshotDir, filename);
735
973
  let crop;
736
974
  if (opts.zoom) {
@@ -769,9 +1007,11 @@ async function captureSnapshots(projectDir, opts) {
769
1007
  zoom: opts.zoom,
770
1008
  zoomScale: opts.zoom ? opts.zoomScale ?? DEFAULT_ZOOM_SCALE : 1,
771
1009
  angle: opts.angle,
772
- browserGpuMode: opts.browserGpuMode
1010
+ browserGpuMode: opts.browserGpuMode,
1011
+ ...reviewFrame ? { review: { role: reviewFrame.role, label: reviewFrame.label } } : {}
773
1012
  }
774
1013
  });
1014
+ if (reviewFrame) reviewPaths.set(reviewFrame, framePath);
775
1015
  if (opts.against) {
776
1016
  const refPng = await extractVideoFrameToBuffer(opts.against, time);
777
1017
  if (!refPng) {
@@ -807,20 +1047,32 @@ async function captureSnapshots(projectDir, opts) {
807
1047
  artifacts,
808
1048
  opts.zoom,
809
1049
  warnings,
810
- completed
1050
+ completed,
1051
+ log
811
1052
  ) : false;
812
1053
  if (!recorded) {
813
1054
  const paths = [...replaced, ...artifacts.map((artifact) => artifact.image)];
814
1055
  if (paths.length > 0) await forgetRecordedArtifacts(projectDir, paths).catch(() => void 0);
815
1056
  }
816
1057
  }
817
- return savedPaths;
1058
+ return {
1059
+ paths: savedPaths,
1060
+ ...review && reviewList ? {
1061
+ review: {
1062
+ plan: review,
1063
+ frames: reviewList.flatMap((frame) => {
1064
+ const path = reviewPaths.get(frame);
1065
+ return path ? [{ ...frame, path }] : [];
1066
+ })
1067
+ }
1068
+ } : {}
1069
+ };
818
1070
  }
819
1071
  function warnSnapshotsNotRecorded(error) {
820
1072
  console.warn(` ${c.warn("\u26A0")} Snapshots were not recorded: ${normalizeErrorMessage(error)}`);
821
1073
  return void 0;
822
1074
  }
823
- async function recordSnapshotArtifacts(projectDir, capture, artifacts, zoom, warnings, completed) {
1075
+ async function recordSnapshotArtifacts(projectDir, capture, artifacts, zoom, warnings, completed, log) {
824
1076
  const records = await recordArtifacts(
825
1077
  projectDir,
826
1078
  capture,
@@ -833,10 +1085,65 @@ async function recordSnapshotArtifacts(projectDir, capture, artifacts, zoom, war
833
1085
  ).catch(warnSnapshotsNotRecorded);
834
1086
  const record = records?.[0];
835
1087
  if (!record) return false;
836
- console.log(` Revision: ${record.revision ?? "none"} (${record.attribution})`);
837
- console.log(` State: ${join(projectDir, ".hyperframes", "video-state.json")}`);
1088
+ log(` Revision: ${record.revision ?? "none"} (${record.attribution})`);
1089
+ log(` State: ${join(projectDir, ".hyperframes", "video-state.json")}`);
838
1090
  return true;
839
1091
  }
1092
+ function snapshotEnvelope(projectDir, outputDir, captured, extra) {
1093
+ const review = captured.review;
1094
+ const frames = review ? review.frames.map((frame) => ({
1095
+ path: frame.path,
1096
+ time: frame.time,
1097
+ role: frame.role,
1098
+ label: frame.label
1099
+ })) : captured.paths.map((path) => ({ path: resolve(projectDir, path), time: timeFromName(path) }));
1100
+ return {
1101
+ ok: true,
1102
+ outputDir,
1103
+ frames,
1104
+ contactSheets: extra.contactSheets.map((sheet) => resolve(sheet)),
1105
+ ...extra.describedPath ? { descriptions: extra.describedPath } : {},
1106
+ ...review ? {
1107
+ review: {
1108
+ window: review.plan.window,
1109
+ scenes: review.plan.scenes,
1110
+ beats: review.plan.beats.map((beat, index) => ({
1111
+ ...beat,
1112
+ path: review.frames.find(
1113
+ (frame) => frame.role === "beat" && frame.index === index + 1
1114
+ )?.path
1115
+ })),
1116
+ seams: review.plan.seams.map((seam, index) => ({
1117
+ ...seam,
1118
+ paths: review.frames.filter((frame) => frame.role === "seam" && frame.index === index + 1).map((frame) => frame.path),
1119
+ sheet: extra.reviewSheets?.seams[index]
1120
+ })),
1121
+ sheets: extra.reviewSheets ?? { beats: [], seams: [], phone: [] },
1122
+ notes: review.plan.notes
1123
+ }
1124
+ } : {}
1125
+ };
1126
+ }
1127
+ function timeFromName(path) {
1128
+ const match = /at-([\d.]+)s\.png$/.exec(path);
1129
+ return match ? Number(match[1]) : null;
1130
+ }
1131
+ function printReview(review, sheets, log) {
1132
+ const beats = review.plan.beats.map(
1133
+ (beat) => `${formatSnapshotTimestamp(beat.time)} ${beat.label}`
1134
+ );
1135
+ log(` Beat sheet (settled frame per scene): ${sheets.beats.join(", ") || "none"}`);
1136
+ log(` ${c.dim(beats.join(" \xB7 "))}`);
1137
+ review.plan.seams.forEach((seam, index) => {
1138
+ const sides = `${seam.from ?? "start"} -> ${seam.to ?? "end"}`;
1139
+ const strip = sheets.seams[index] ?? "not written";
1140
+ log(` Seam ${index + 1} at ${formatSnapshotTimestamp(seam.time)} (${sides}): ${strip}`);
1141
+ });
1142
+ log(
1143
+ ` Phone sheet (beats at ${REVIEW_PHONE_WIDTH} px wide): ${sheets.phone.join(", ") || "none"}`
1144
+ );
1145
+ for (const note of review.plan.notes) log(` ${c.dim(note)}`);
1146
+ }
840
1147
  var snapshot_default = defineCommand({
841
1148
  meta: {
842
1149
  name: "snapshot",
@@ -902,6 +1209,16 @@ var snapshot_default = defineCommand({
902
1209
  type: "string",
903
1210
  description: "Reference video (e.g. footage being rebuilt): also save its frame at each captured time and a render|reference pair sheet"
904
1211
  },
1212
+ review: {
1213
+ type: "boolean",
1214
+ 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.",
1215
+ default: false
1216
+ },
1217
+ json: {
1218
+ type: "boolean",
1219
+ description: "Print one JSON envelope (paths, times, sheets; the review plan under --review)",
1220
+ default: false
1221
+ },
905
1222
  describe: {
906
1223
  type: "string",
907
1224
  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."
@@ -918,8 +1235,22 @@ var snapshot_default = defineCommand({
918
1235
  }
919
1236
  },
920
1237
  async run({ args }) {
1238
+ const asJson = args.json === true;
1239
+ const log = (message) => asJson ? console.error(message) : console.log(message);
1240
+ const fail = (message) => {
1241
+ if (asJson) console.log(JSON.stringify(withMeta({ ok: false, error: message }), null, 2));
1242
+ else log(message);
1243
+ failCommand();
1244
+ };
921
1245
  if ([args.at, args.range, args.composition].filter((value) => value !== void 0).length > 1)
922
1246
  throw new Error("Use only one of --at, --range, or --composition");
1247
+ const reviewMode = args.review === true;
1248
+ if (reviewMode) {
1249
+ const clash = ["at", "range", "zoom", "keep", "against"].find(
1250
+ (flag) => args[flag] !== void 0 && args[flag] !== false
1251
+ );
1252
+ if (clash) throw new Error(`--review picks its own frames; drop --${clash}`);
1253
+ }
923
1254
  const frames = parseInt(args.frames, 10) || 5;
924
1255
  const range = args.range === void 0 ? void 0 : parseSnapshotRange(String(args.range));
925
1256
  const composition = args.composition === void 0 ? void 0 : String(args.composition).trim();
@@ -928,28 +1259,32 @@ var snapshot_default = defineCommand({
928
1259
  const lintResult = await lintProject(project.dir);
929
1260
  if (hasDefinitiveEntryMismatch(lintResult)) {
930
1261
  const candidate = definitiveEntryMismatchComposition(lintResult);
931
- console.log("");
1262
+ log("");
932
1263
  for (const line of formatLintFindings(lintResult, { errorsFirst: true })) {
933
- console.log(line);
1264
+ log(line);
934
1265
  }
935
- console.log("");
936
- console.log(c.error(" Aborting snapshot because the default index.html entry is blank."));
1266
+ log("");
1267
+ log(c.error(" Aborting snapshot because the default index.html entry is blank."));
937
1268
  if (candidate && posix.basename(candidate) === "index.html") {
938
1269
  const candidateDir = posix.dirname(candidate);
939
1270
  const target = `<project>/${candidateDir}`;
940
- console.log(
1271
+ log(
941
1272
  c.dim(
942
1273
  ` 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.`
943
1274
  )
944
1275
  );
945
1276
  } else if (candidate) {
946
- console.log(
1277
+ log(
947
1278
  c.dim(
948
1279
  ` Move or mount ${candidate} as a project index.html before snapshotting; snapshot accepts project directories, not individual HTML files.`
949
1280
  )
950
1281
  );
951
1282
  }
952
- console.log("");
1283
+ log("");
1284
+ if (asJson) {
1285
+ const error = "The default index.html entry is blank; see the lint findings on stderr.";
1286
+ console.log(JSON.stringify(withMeta({ ok: false, error }), null, 2));
1287
+ }
953
1288
  failCommand();
954
1289
  }
955
1290
  const timeout = parseInt(args.timeout, 10) || 5e3;
@@ -960,20 +1295,18 @@ var snapshot_default = defineCommand({
960
1295
  const zoomTarget = args.zoom ? parseZoomTarget(String(args.zoom)) : void 0;
961
1296
  const zoomScale = parseZoomScale(args["zoom-scale"]);
962
1297
  const against = args.against ? resolve(String(args.against)) : void 0;
963
- if (against && !existsSync(against)) {
964
- console.log(`${c.error("\u2717")} --against video not found: ${against}`);
965
- failCommand();
966
- }
967
- const label = atTimestamps ? `${atTimestamps.length} frames at [${atTimestamps.map(formatSnapshotTimestamp).join(", ")}]` : `${frames} frames`;
1298
+ if (against && !existsSync(against))
1299
+ fail(`${c.error("\u2717")} --against video not found: ${against}`);
1300
+ const label = reviewMode ? "review frames (beats, seams, phone)" : atTimestamps ? `${atTimestamps.length} frames at [${atTimestamps.map(formatSnapshotTimestamp).join(", ")}]` : `${frames} frames`;
968
1301
  const angleLabel = camera && (camera.yaw !== 0 || camera.pitch !== 0) ? ` ${c.dim(`(angle yaw ${camera.yaw}\xB0 pitch ${camera.pitch}\xB0)`)}` : "";
969
- console.log(`${c.accent("\u25C6")} Capturing ${label} from ${c.accent(project.name)}${angleLabel}`);
1302
+ log(`${c.accent("\u25C6")} Capturing ${label} from ${c.accent(project.name)}${angleLabel}`);
970
1303
  let managedSnapshotDir;
971
1304
  try {
972
1305
  const snapshotDir = args.output ? resolve(String(args.output)) : artifactDirectory(project.dir, "snapshots");
973
1306
  if (!args.output) managedSnapshotDir = snapshotDir;
974
1307
  if (args.output) retainSnapshotOutput(project.dir, snapshotDir);
975
1308
  await withProjectLock(project.dir, "snapshot", async () => {
976
- const paths = await captureSnapshots(project.dir, {
1309
+ const captured = await captureSnapshots(project.dir, {
977
1310
  frames,
978
1311
  range,
979
1312
  composition,
@@ -987,53 +1320,71 @@ var snapshot_default = defineCommand({
987
1320
  autoProxy: args.proxy,
988
1321
  browserGpuMode: resolveLocalBrowserGpuMode(args["browser-gpu"]),
989
1322
  against,
990
- keep: args.keep === true
1323
+ keep: args.keep === true,
1324
+ review: reviewMode,
1325
+ log
991
1326
  });
1327
+ const paths = captured.paths;
992
1328
  reportArtifacts(
993
1329
  "snapshot",
994
1330
  "frame",
995
1331
  paths.map((path) => resolve(project.dir, path))
996
1332
  );
997
1333
  if (paths.length === 0) {
998
- console.log(
999
- `
1000
- ${c.error("\u2717")} Could not determine composition duration \u2014 no frames captured`
1001
- );
1002
- failCommand();
1334
+ fail(`
1335
+ ${c.error("\u2717")} Could not determine composition duration \u2014 no frames captured`);
1003
1336
  }
1004
- console.log(`
1337
+ log(`
1005
1338
  ${c.success("\u25C7")} ${paths.length} snapshots saved to ${snapshotDir}`);
1006
1339
  for (const p of paths) {
1007
- console.log(` ${p}`);
1340
+ log(` ${p}`);
1008
1341
  }
1009
1342
  if (against) {
1010
- console.log(
1343
+ log(
1011
1344
  ` ${c.dim("ref-*.png + pair-*.jpg")} beside each frame (reference frame, render | reference sheet)`
1012
1345
  );
1013
1346
  }
1347
+ let contactSheets = [];
1348
+ let reviewSheets;
1014
1349
  try {
1015
1350
  const { createSnapshotContactSheet } = await import("./contactSheet-CAGMKI3E.js");
1016
- const sheets = args.keep === true ? [] : await createSnapshotContactSheet(
1017
- snapshotDir,
1018
- join(snapshotDir, "contact-sheet.jpg")
1019
- );
1020
- reportArtifacts("snapshot", "contact-sheet", sheets);
1021
- for (const sheet of sheets) {
1022
- console.log(` ${resolve(sheet)} (grid view for AI review)`);
1351
+ if (captured.review) {
1352
+ const written = new Map(
1353
+ captured.review.frames.map((frame) => [frame.file, frame.path])
1354
+ );
1355
+ reviewSheets = await writeReviewSheets(
1356
+ snapshotDir,
1357
+ captured.review.frames,
1358
+ (frame) => written.get(frame.file) ?? null
1359
+ );
1360
+ contactSheets = [...reviewSheets.beats, ...reviewSheets.seams, ...reviewSheets.phone];
1361
+ printReview(captured.review, reviewSheets, log);
1362
+ } else if (args.keep !== true) {
1363
+ contactSheets = await createSnapshotContactSheet(
1364
+ snapshotDir,
1365
+ join(snapshotDir, "contact-sheet.jpg")
1366
+ );
1367
+ }
1368
+ reportArtifacts("snapshot", "contact-sheet", contactSheets);
1369
+ if (!captured.review) {
1370
+ for (const sheet of contactSheets) {
1371
+ log(` ${resolve(sheet)} (grid view for AI review)`);
1372
+ }
1023
1373
  }
1024
1374
  } catch (err) {
1025
1375
  console.warn(
1026
1376
  ` ${c.warn("\u26A0")} Contact sheet skipped: ${normalizeErrorMessage(err)}. Individual snapshot PNGs are still available.`
1027
1377
  );
1028
1378
  }
1379
+ let describedPath;
1029
1380
  if (describeArg !== null) {
1030
1381
  try {
1031
1382
  const geminiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
1032
1383
  if (!geminiKey) {
1033
1384
  if (args.describe !== void 0)
1034
- console.log(` ${c.dim("--describe: GEMINI_API_KEY not set, skipping")}`);
1385
+ log(` ${c.dim("--describe: GEMINI_API_KEY not set, skipping")}`);
1035
1386
  } else if (paths.length > 0) {
1036
- console.log(` ${c.dim("Describing frames with Gemini vision...")}`);
1387
+ log(` ${c.dim("Describing frames with Gemini vision...")}`);
1037
1388
  const { GoogleGenAI } = await loadOptionalPackage("@google/genai", "--describe");
1038
1389
  const ai = new GoogleGenAI({ apiKey: geminiKey });
1039
1390
  const model = process.env.HYPERFRAMES_GEMINI_MODEL || "gemini-3.1-flash-lite-preview";
@@ -1101,17 +1452,28 @@ ${c.success("\u25C7")} ${paths.length} snapshots saved to ${snapshotDir}`);
1101
1452
  }
1102
1453
  const descPath = join(snapshotDir, "descriptions.md");
1103
1454
  writeFileSync(descPath, descriptions.join("\n"));
1104
- console.log(` ${c.dim("descriptions.md")} (Gemini frame analysis)`);
1455
+ describedPath = descPath;
1456
+ log(` ${c.dim("descriptions.md")} (Gemini frame analysis)`);
1105
1457
  }
1106
1458
  } catch (descErr) {
1107
1459
  const msg = normalizeErrorMessage(descErr);
1108
- console.log(` ${c.dim(`--describe failed: ${msg}`)}`);
1460
+ log(` ${c.dim(`--describe failed: ${msg}`)}`);
1109
1461
  }
1110
1462
  }
1463
+ if (asJson) {
1464
+ const envelope = snapshotEnvelope(project.dir, snapshotDir, captured, {
1465
+ contactSheets,
1466
+ reviewSheets,
1467
+ describedPath
1468
+ });
1469
+ console.log(JSON.stringify(withMeta(envelope), null, 2));
1470
+ }
1111
1471
  });
1112
1472
  } catch (err) {
1473
+ if (err instanceof CliRuntimeError && err.result.presented) throw err;
1113
1474
  const msg = normalizeErrorMessage(err);
1114
- console.error(`
1475
+ if (asJson) console.log(JSON.stringify(withMeta({ ok: false, error: msg }), null, 2));
1476
+ else console.error(`
1115
1477
  ${c.error("\u2717")} Snapshot failed: ${msg}`);
1116
1478
  failCommand();
1117
1479
  } finally {