hyperframes 0.8.150 → 0.8.152

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 (193) hide show
  1. package/dist/{add-3ZIFEN2U.js → add-SA33REHW.js} +14 -14
  2. package/dist/{animatedGifPrep-77LPFLKJ.js → animatedGifPrep-3YXJQZ7L.js} +9 -9
  3. package/dist/api.js +19 -19
  4. package/dist/{asset-EYSZEHM6.js → asset-PUVPTWPD.js} +2 -2
  5. package/dist/{assetCataloger-KX3HD7ZI.js → assetCataloger-VWE2PLBX.js} +4 -4
  6. package/dist/{auth-L6IWNRXY.js → auth-PS3M6AI7.js} +1 -1
  7. package/dist/{autoUpdate-2O3HZB65.js → autoUpdate-CMO4SHWU.js} +2 -2
  8. package/dist/{backgroundChecks-BH2LMJYE.js → backgroundChecks-EDZ2A55J.js} +3 -3
  9. package/dist/backgroundChecksWorker.js +3 -3
  10. package/dist/{batchRender-ZBKAYLWU.js → batchRender-7PLJISCN.js} +5 -5
  11. package/dist/{beats-IVLF2566.js → beats-2MDSIOE7.js} +15 -15
  12. package/dist/{benchmark-SYZFW2G7.js → benchmark-KBBXAPI4.js} +10 -10
  13. package/dist/{browser-OSW3L25S.js → browser-OMWKZRSW.js} +5 -5
  14. package/dist/{bundleWithLocalizedFonts-I4CWOXLK.js → bundleWithLocalizedFonts-76DMCRIW.js} +2 -2
  15. package/dist/{capture-AZOQ57TM.js → capture-LRUUXH5Q.js} +2 -2
  16. package/dist/{capture-V5ICGSVJ.js → capture-YGDC7GN3.js} +13 -13
  17. package/dist/{catalog-VBJOA2Y5.js → catalog-SMEBKEVO.js} +14 -14
  18. package/dist/{catch-up-TKSEDNJK.js → catch-up-OMILX3WE.js} +4 -4
  19. package/dist/{check-3HFISKDB.js → check-NUZ62LIW.js} +288 -64
  20. package/dist/{checkBrowser-MXBDNZJB.js → checkBrowser-MSTVEL4V.js} +181 -38
  21. package/dist/{chunk-73RBX2E7.js → chunk-3DZBQ7MH.js} +3 -3
  22. package/dist/{chunk-6QB7F2II.js → chunk-3E4OCOC5.js} +1 -1
  23. package/dist/{chunk-OQ7XMM3S.js → chunk-3HPJM5IQ.js} +1 -1
  24. package/dist/{chunk-UJB7356Y.js → chunk-3LO7BBJX.js} +3 -3
  25. package/dist/{chunk-APDLWWFG.js → chunk-3VGU2SH7.js} +1 -1
  26. package/dist/{chunk-DUUFDLC4.js → chunk-4MLYMDQ4.js} +1 -1
  27. package/dist/{chunk-AXPNVC3N.js → chunk-4UMKAUZI.js} +1 -1
  28. package/dist/{chunk-XPQSL5VS.js → chunk-54DMWFQZ.js} +8 -1
  29. package/dist/chunk-57GN6KWT.js +179 -0
  30. package/dist/{chunk-374QQGBQ.js → chunk-5CS4T6C6.js} +63 -12
  31. package/dist/{chunk-PCWOSS7G.js → chunk-5ERVRJMV.js} +3 -3
  32. package/dist/{chunk-TZPZE5PS.js → chunk-5VMSUW4Z.js} +6 -3
  33. package/dist/{chunk-M6UDJ2E7.js → chunk-6Z4YJ5Q3.js} +3 -3
  34. package/dist/{chunk-FJWEYVEM.js → chunk-7FYFEXD2.js} +658 -198
  35. package/dist/{chunk-UQYQ7TBA.js → chunk-7NPSXBIN.js} +2 -2
  36. package/dist/chunk-7TASSBN2.js +215 -0
  37. package/dist/{chunk-F2RLIE7P.js → chunk-ANS56TPS.js} +11 -11
  38. package/dist/{chunk-DOVJRUFM.js → chunk-BQK3J7XO.js} +1 -1
  39. package/dist/{chunk-FPAT5HTI.js → chunk-BYJYQWEX.js} +10 -10
  40. package/dist/{chunk-XXPYZBZ3.js → chunk-C6TPGUWD.js} +2 -2
  41. package/dist/{chunk-XQMEGSHG.js → chunk-D6RJXQLD.js} +1 -1
  42. package/dist/{chunk-RW7NLGPD.js → chunk-DFXK7KUO.js} +25 -17
  43. package/dist/{chunk-6SFPQ2RZ.js → chunk-E6EYZ33P.js} +2 -2
  44. package/dist/{chunk-AKYQGZLU.js → chunk-EVTHG34H.js} +2 -2
  45. package/dist/{chunk-5NAYNDBT.js → chunk-EXH6L2XZ.js} +6 -2
  46. package/dist/{chunk-AIJS74LG.js → chunk-FFDC5OWV.js} +8 -2
  47. package/dist/{chunk-4WFNTWKZ.js → chunk-FNN5QINS.js} +2 -2
  48. package/dist/{chunk-DMKI64EZ.js → chunk-FSZAFCU7.js} +1 -1
  49. package/dist/{chunk-S5F26DUL.js → chunk-GQETH4PE.js} +3 -3
  50. package/dist/{chunk-3UAIVF4L.js → chunk-H2SGM2V2.js} +1 -1
  51. package/dist/{chunk-O4DC5CHF.js → chunk-H7FXBK3Q.js} +1 -1
  52. package/dist/{chunk-UVVI2NJQ.js → chunk-HBCRRCTX.js} +109 -31
  53. package/dist/{chunk-NFQY7EIC.js → chunk-IBYC3VUA.js} +1 -1
  54. package/dist/{chunk-2KAL22B5.js → chunk-IEDT4EXE.js} +1 -1
  55. package/dist/{chunk-I6DATIHC.js → chunk-IZIRIDJH.js} +2 -2
  56. package/dist/{chunk-Z6WZDGCS.js → chunk-IZKXUYZE.js} +2 -2
  57. package/dist/chunk-KCFF4ENE.js +24 -0
  58. package/dist/{chunk-6KWA3XPS.js → chunk-KZ3L3W4Q.js} +1 -1
  59. package/dist/{chunk-7IEGPJ4P.js → chunk-LBT4AMSF.js} +1 -1
  60. package/dist/{chunk-7JTFPUMA.js → chunk-LECTCGGS.js} +1 -1
  61. package/dist/{chunk-7E4VHPKJ.js → chunk-LVPSWUS6.js} +5 -5
  62. package/dist/{chunk-6NPFJ255.js → chunk-MNHPG2UO.js} +1 -1
  63. package/dist/{chunk-FN6KAS7A.js → chunk-NHAG2KCE.js} +7 -7
  64. package/dist/{chunk-423DJVX2.js → chunk-PKO2EWO7.js} +1 -1
  65. package/dist/{chunk-ZNHAJCEA.js → chunk-PRXAOZWZ.js} +28 -13
  66. package/dist/{chunk-VQ7YIURZ.js → chunk-PTXZDSW5.js} +5 -5
  67. package/dist/{chunk-XANZ4R4P.js → chunk-QECEOV6W.js} +3 -3
  68. package/dist/{chunk-BLRJKBKP.js → chunk-QESSGUKX.js} +1 -1
  69. package/dist/{chunk-WLIUULZQ.js → chunk-RN5I2DP5.js} +29 -15
  70. package/dist/chunk-S3RCK56N.js +3795 -0
  71. package/dist/{chunk-UAIUVPZE.js → chunk-SWCSL6WS.js} +6 -6
  72. package/dist/{chunk-2PSPARWP.js → chunk-TYXH3EKP.js} +4 -0
  73. package/dist/{chunk-FMNVTKX2.js → chunk-U3BCDVZO.js} +1 -1
  74. package/dist/{chunk-LW5VFBSY.js → chunk-VS732BUG.js} +1 -1
  75. package/dist/{chunk-LFNQDTSH.js → chunk-VYKPBRCB.js} +63 -12
  76. package/dist/{chunk-M5B42RL5.js → chunk-WUUOFMHU.js} +3 -3
  77. package/dist/{chunk-A6ZO4BX7.js → chunk-XEFFJRYX.js} +2 -2
  78. package/dist/{chunk-EL5QVJEG.js → chunk-XINGD6MS.js} +4 -4
  79. package/dist/chunk-XSEGMQYA.js +30 -0
  80. package/dist/{chunk-CZQLCBWY.js → chunk-YG2Y55KF.js} +1 -1
  81. package/dist/{chunk-B3XLIBFL.js → chunk-YIJMVFEJ.js} +1 -1
  82. package/dist/{chunk-JDB65PPT.js → chunk-YOEGUBBY.js} +13 -12
  83. package/dist/{chunk-AFRXL75S.js → chunk-YYEYOFHW.js} +1 -1
  84. package/dist/{chunk-R5A7454P.js → chunk-ZTM3RMAD.js} +2 -2
  85. package/dist/{clean-7HU7S7DT.js → clean-F75G45BI.js} +56 -35
  86. package/dist/cli.js +53 -53
  87. package/dist/{cloud-HAXHBO25.js → cloud-KVJAOUWH.js} +4 -4
  88. package/dist/{cloudrun-A3PIJ6DX.js → cloudrun-Q7Q7O4GY.js} +6 -6
  89. package/dist/{compare-6IPSQMSM.js → compare-DGUJ4QWU.js} +30 -30
  90. package/dist/{compiler-UQP2VXN6.js → compiler-NCN6PTVH.js} +6 -6
  91. package/dist/{component-FF3AU24M.js → component-PSYGT3JV.js} +3 -3
  92. package/dist/{compositions-JSH4MZXB.js → compositions-J3GAETIC.js} +13 -13
  93. package/dist/{delete-G763SW64.js → delete-MR7LRGRP.js} +2 -2
  94. package/dist/{deterministicFonts-C5JQLCTS.js → deterministicFonts-EOMDR6ZF.js} +3 -3
  95. package/dist/{dist-HUMFCJ6T.js → dist-2YYJYYPY.js} +3 -3
  96. package/dist/{dist-Z7Q3JS7K.js → dist-3MHXG6EJ.js} +4 -4
  97. package/dist/{doctor-WZB6WMBY.js → doctor-JOUGR5UK.js} +15 -15
  98. package/dist/{events-T4YFQC5G.js → events-63BKDWVO.js} +3 -3
  99. package/dist/{events-MUWJJCC7.js → events-FZFBJCIH.js} +2 -2
  100. package/dist/{feedback-AUTHQNN2.js → feedback-QLKWUDXR.js} +8 -8
  101. package/dist/{ffmpeg-TZB5WAYE.js → ffmpeg-MJFMBBSF.js} +2 -2
  102. package/dist/{figma-NBG6WBI7.js → figma-YJ7HGVQC.js} +3 -3
  103. package/dist/fontLocalizeCli.js +12 -12
  104. package/dist/{get-OXBJLKLV.js → get-54MUUXMR.js} +2 -2
  105. package/dist/{gpuPolicy-HBCCEQUS.js → gpuPolicy-5OVNR75D.js} +9 -9
  106. package/dist/{grade-compare-2LX4FDH7.js → grade-compare-7IDUF673.js} +32 -32
  107. package/dist/{gsapParserAcorn-VXZ3ZIF2.js → gsapParserAcorn-7NCK2LVT.js} +2 -2
  108. package/dist/{help-LJI46EDO.js → help-GQFYO5LU.js} +1 -1
  109. package/dist/{history-MFKAQEHU.js → history-UI3O7CSO.js} +77 -205
  110. package/dist/hyperframe-runtime.js +54 -54
  111. package/dist/hyperframe.manifest.json +1 -1
  112. package/dist/hyperframe.runtime.iife.js +54 -54
  113. package/dist/hyperframes-player.global.js +3 -3
  114. package/dist/{info-D6Y5O2QE.js → info-VQV3APEX.js} +9 -9
  115. package/dist/{init-47NZPEBI.js → init-2UBFV5D7.js} +31 -31
  116. package/dist/{inspect-U5UXJK5D.js → inspect-36ACJ3CO.js} +31 -31
  117. package/dist/{installer-AW7VFVTQ.js → installer-GDUZDJG4.js} +7 -7
  118. package/dist/{keyframes-5G334NWB.js → keyframes-GPBU34BG.js} +227 -82
  119. package/dist/{lambda-GASGXN3A.js → lambda-CBJVLTHY.js} +7 -7
  120. package/dist/{layout-OXAQFLJS.js → layout-2IHFV6EI.js} +31 -31
  121. package/dist/{lint-F36QFSNF.js → lint-IR6IYCEO.js} +9 -9
  122. package/dist/{lintProject-SSEXWCH6.js → lintProject-XQWXJYSC.js} +3 -3
  123. package/dist/{list-TR6QCI53.js → list-UAGKWARH.js} +2 -2
  124. package/dist/{login-6LA2RWTC.js → login-2GYCNCZF.js} +4 -4
  125. package/dist/{manager-ES2UFT4X.js → manager-X44GKUEL.js} +3 -3
  126. package/dist/{manager-GWU5J6RJ.js → manager-ZLJD3EAY.js} +2 -2
  127. package/dist/{media-treatment-BOHYDWVY.js → media-treatment-Z553F7XI.js} +14 -14
  128. package/dist/{models-6GW5Q3IW.js → models-6AZTNNSO.js} +15 -15
  129. package/dist/{motionShot-O7AOMPUN.js → motionShot-ARRL7WXT.js} +29 -16
  130. package/dist/{normalize-audio-UO5B4O76.js → normalize-audio-CME4NUB6.js} +6 -6
  131. package/dist/{open-JV2EHVKM.js → open-HU2VXUFD.js} +4 -4
  132. package/dist/{parakeetRunner-23DZUF6G.js → parakeetRunner-X5MKFMCB.js} +16 -16
  133. package/dist/{pipeline-KTICSIK7.js → pipeline-MNFTEBJU.js} +11 -11
  134. package/dist/{play-CMUYPXBC.js → play-2BBJDDAZ.js} +24 -24
  135. package/dist/{preflight-JOD4OXGC.js → preflight-JYOX64YP.js} +4 -4
  136. package/dist/{present-ZDT5FM2H.js → present-OE2WL2NY.js} +20 -20
  137. package/dist/{preview-ASHLGKDD.js → preview-RYSCCAWJ.js} +21 -21
  138. package/dist/{producer-FFH2KUAZ.js → producer-SQWNRO66.js} +1 -1
  139. package/dist/{projectConfig-FHIAOPKW.js → projectConfig-B55Z7YKQ.js} +9 -9
  140. package/dist/{publication-WO5ZXURT.js → publication-MDZCOBJP.js} +4 -4
  141. package/dist/{publish-QWO2W7FB.js → publish-AILV36BO.js} +12 -12
  142. package/dist/{remove-background-BO5MQR25.js → remove-background-6F72LKHG.js} +12 -12
  143. package/dist/{render-XTJKGT3G.js → render-2MZKU3L5.js} +10 -10
  144. package/dist/{render-SDHFXL3Y.js → render-5EREH6YD.js} +95 -37
  145. package/dist/{render-HS3YOLSD.js → render-6GH5FSXU.js} +5 -5
  146. package/dist/{render-batch-J64AJHQY.js → render-batch-UMBUQWWS.js} +5 -5
  147. package/dist/renderSetupWorker.js +6 -6
  148. package/dist/{resolver-IHNWMDJA.js → resolver-KO6WWO5P.js} +6 -6
  149. package/dist/{sherpa-FXBBYMDR.js → sherpa-DADGYWNR.js} +15 -15
  150. package/dist/skills/hyperframes/references/frame-worker-core.md +11 -9
  151. package/dist/skills/hyperframes/references/subagent-dispatch.md +1 -1
  152. package/dist/skills/hyperframes-cli/SKILL.md +3 -0
  153. package/dist/skills/hyperframes-cli/references/lint-validate-inspect.md +5 -1
  154. package/dist/skills/media-use/audio/scripts/lib/tts.cleanup.test.mjs +175 -0
  155. package/dist/skills/media-use/audio/scripts/lib/tts.mjs +24 -19
  156. package/dist/skills/media-use/references/grading.md +1 -2
  157. package/dist/skills/media-use/references/meta.md +17 -17
  158. package/dist/skills/media-use/references/operations.md +6 -6
  159. package/dist/skills/media-use/references/setup-providers.md +3 -4
  160. package/dist/skills/media-use/scripts/lib/cutlist.mjs +9 -2
  161. package/dist/skills/media-use/scripts/lib/cutlist.test.mjs +12 -0
  162. package/dist/skills/media-use/scripts/lib/telemetry.mjs +4 -1
  163. package/dist/skills/media-use/scripts/lib/telemetry.test.mjs +20 -0
  164. package/dist/skills/media-use/scripts/transcript-cut.mjs +23 -4
  165. package/dist/skills/media-use/scripts/transcript-cut.test.mjs +224 -0
  166. package/dist/{skills-ALLSN7TB.js → skills-QLKSNU4Y.js} +5 -5
  167. package/dist/{skillsUpdateCheck-EIT5NWM6.js → skillsUpdateCheck-Z2QQGF2W.js} +3 -3
  168. package/dist/{snapshot-Y3I76OIZ.js → snapshot-D5BBFXOS.js} +342 -154
  169. package/dist/{src-7V6LBC56.js → src-AVQBAECC.js} +10 -8
  170. package/dist/{src-S5IZVJUX.js → src-V7263E47.js} +11 -11
  171. package/dist/{staticProjectServer-QLWWGHZE.js → staticProjectServer-HNWSVEWN.js} +20 -20
  172. package/dist/studio/assets/{hyperframes-player-kup62bDR.js → hyperframes-player-0BY7-PK7.js} +1 -1
  173. package/dist/studio/assets/{index-B8BBYY8L.js → index-COrWdzlj.js} +1 -1
  174. package/dist/studio/assets/{index-CS_m0TkX.js → index-p-VXwuJ2.js} +132 -132
  175. package/dist/studio/assets/{index-DQ5jGodF.js → index-rmD5k_ES.js} +1 -1
  176. package/dist/studio/index.html +1 -1
  177. package/dist/{studio-api-UBFLZ3DA.js → studio-api-UXKTDWLT.js} +30 -16
  178. package/dist/{studioServer-REOVCIFP.js → studioServer-JOVR24O3.js} +51 -51
  179. package/dist/{synthesize-22B7L5GX.js → synthesize-MBDVUSH3.js} +2 -1
  180. package/dist/{telemetry-C3GDIXHO.js → telemetry-UW7XAM5W.js} +3 -3
  181. package/dist/{timeline-H42EGLMT.js → timeline-JSGTZ3AN.js} +73 -20
  182. package/dist/{tokens-2K7ISTKV.js → tokens-ADDBGTQ7.js} +2 -2
  183. package/dist/{transcribe-NUPASAUP.js → transcribe-FEHOK626.js} +14 -14
  184. package/dist/{transcribe-DVM7UFZZ.js → transcribe-P6MJGSZJ.js} +20 -20
  185. package/dist/{tts-QSAQZVEZ.js → tts-POI553RG.js} +1 -1
  186. package/dist/{updateCheck-3RWFSEZD.js → updateCheck-IBWMLXQ4.js} +2 -2
  187. package/dist/{upgrade-YKKL36N7.js → upgrade-NHZ7AYPO.js} +2 -2
  188. package/dist/{urlDownloader-6JNJYLPN.js → urlDownloader-ULDRBWS5.js} +8 -8
  189. package/dist/{validate-Z7KQT7GO.js → validate-NVWNXNBV.js} +25 -25
  190. package/dist/{video-EXL2JDSL.js → video-74DRD2DN.js} +9 -9
  191. package/package.json +1 -1
  192. package/dist/chunk-CEND6BLF.js +0 -3795
  193. package/dist/chunk-WBLXWOYK.js +0 -15
@@ -7,11 +7,20 @@ var __dirname = __hf_dirname(__filename);
7
7
  import {
8
8
  parseAngle
9
9
  } from "./chunk-KZI44OPA.js";
10
+ import {
11
+ pngDimensions,
12
+ reportArtifacts,
13
+ withProjectLock
14
+ } from "./chunk-57GN6KWT.js";
15
+ import {
16
+ captureArtifactRevision
17
+ } from "./chunk-KCFF4ENE.js";
18
+ import "./chunk-7TASSBN2.js";
10
19
  import {
11
20
  serveStaticProjectHtml
12
- } from "./chunk-S5F26DUL.js";
21
+ } from "./chunk-GQETH4PE.js";
13
22
  import "./chunk-XUHOIDKG.js";
14
- import "./chunk-A6ZO4BX7.js";
23
+ import "./chunk-XEFFJRYX.js";
15
24
  import "./chunk-X7MTZ4T2.js";
16
25
  import {
17
26
  DEFAULT_ZOOM_SCALE,
@@ -21,41 +30,46 @@ import {
21
30
  resolveCropRegion,
22
31
  runFfmpegOnce,
23
32
  seekCompositionTimeline
24
- } from "./chunk-UAIUVPZE.js";
33
+ } from "./chunk-SWCSL6WS.js";
25
34
  import "./chunk-FH7WODJH.js";
26
- import "./chunk-AXPNVC3N.js";
27
- import "./chunk-423DJVX2.js";
35
+ import "./chunk-4UMKAUZI.js";
36
+ import "./chunk-PKO2EWO7.js";
28
37
  import "./chunk-WPNE5NEP.js";
29
- import "./chunk-UVVI2NJQ.js";
30
- import "./chunk-ZNHAJCEA.js";
31
- import "./chunk-FJWEYVEM.js";
32
- import "./chunk-XXPYZBZ3.js";
33
- import "./chunk-EL5QVJEG.js";
34
- import "./chunk-AIJS74LG.js";
38
+ import "./chunk-HBCRRCTX.js";
39
+ import "./chunk-PRXAOZWZ.js";
40
+ import "./chunk-C6TPGUWD.js";
41
+ import {
42
+ artifactDirectory,
43
+ forgetRecordedArtifacts,
44
+ recordArtifacts
45
+ } from "./chunk-7FYFEXD2.js";
46
+ import "./chunk-XINGD6MS.js";
47
+ import "./chunk-FFDC5OWV.js";
35
48
  import {
36
49
  resolveProject
37
- } from "./chunk-NFQY7EIC.js";
50
+ } from "./chunk-IBYC3VUA.js";
38
51
  import {
39
52
  formatLintFindings
40
53
  } from "./chunk-6CSYCFQV.js";
41
54
  import "./chunk-K2FLNJVB.js";
55
+ import "./chunk-DQBY7FRR.js";
42
56
  import {
43
57
  resolveLocalBrowserGpuMode
44
- } from "./chunk-AKYQGZLU.js";
58
+ } from "./chunk-EVTHG34H.js";
45
59
  import "./chunk-442JNXRM.js";
46
- import "./chunk-XQMEGSHG.js";
60
+ import "./chunk-D6RJXQLD.js";
47
61
  import "./chunk-XT7X6QCP.js";
48
62
  import "./chunk-4YYJOFKS.js";
49
63
  import "./chunk-HVW7ABXD.js";
50
- import "./chunk-BLRJKBKP.js";
51
- import "./chunk-M6UDJ2E7.js";
52
- import "./chunk-OQ7XMM3S.js";
64
+ import "./chunk-QESSGUKX.js";
65
+ import "./chunk-6Z4YJ5Q3.js";
66
+ import "./chunk-3HPJM5IQ.js";
53
67
  import "./chunk-AWDFDFI3.js";
54
68
  import "./chunk-P6LJ4Y32.js";
55
69
  import "./chunk-GMECXFAN.js";
56
- import "./chunk-374QQGBQ.js";
57
- import "./chunk-3UAIVF4L.js";
58
- import "./chunk-6KWA3XPS.js";
70
+ import "./chunk-5CS4T6C6.js";
71
+ import "./chunk-H2SGM2V2.js";
72
+ import "./chunk-KZ3L3W4Q.js";
59
73
  import "./chunk-VWG7OQKU.js";
60
74
  import "./chunk-XBSPQ52S.js";
61
75
  import {
@@ -78,7 +92,7 @@ import "./chunk-K4WIOZFP.js";
78
92
  import "./chunk-XUXGIA7H.js";
79
93
  import "./chunk-WERLJGIC.js";
80
94
  import "./chunk-ISN5GRYI.js";
81
- import "./chunk-I6DATIHC.js";
95
+ import "./chunk-IZIRIDJH.js";
82
96
  import {
83
97
  loadOptionalPackage
84
98
  } from "./chunk-3DKN5WKL.js";
@@ -88,24 +102,25 @@ import {
88
102
  findFFmpeg,
89
103
  findFFprobe,
90
104
  getFFmpegInstallHint
91
- } from "./chunk-DUUFDLC4.js";
105
+ } from "./chunk-4MLYMDQ4.js";
92
106
  import "./chunk-AMKC3AXA.js";
93
- import "./chunk-5NAYNDBT.js";
94
- import "./chunk-WLIUULZQ.js";
107
+ import "./chunk-EXH6L2XZ.js";
108
+ import "./chunk-RN5I2DP5.js";
95
109
  import "./chunk-NE55C6S5.js";
96
110
  import "./chunk-JYVGYKXG.js";
97
111
  import {
112
+ createRuntimeStartTimeResolver,
98
113
  isClipVisibleAt,
99
114
  isInClipWindow
100
- } from "./chunk-PCWOSS7G.js";
115
+ } from "./chunk-5ERVRJMV.js";
101
116
  import {
102
117
  readElementRateSpec,
103
118
  sourceTimeAt,
104
119
  timeAtSourceTime
105
- } from "./chunk-CEND6BLF.js";
120
+ } from "./chunk-S3RCK56N.js";
106
121
  import "./chunk-VC76R6W2.js";
107
122
  import "./chunk-UJKQWGFF.js";
108
- import "./chunk-LFNQDTSH.js";
123
+ import "./chunk-VYKPBRCB.js";
109
124
  import "./chunk-OFQWTKPW.js";
110
125
  import "./chunk-DPVXBVLL.js";
111
126
  import "./chunk-T76PLPD6.js";
@@ -113,18 +128,20 @@ import "./chunk-FXMKHHPO.js";
113
128
  import "./chunk-RQVDTPGD.js";
114
129
  import {
115
130
  runCancellableProcess
116
- } from "./chunk-XPQSL5VS.js";
131
+ } from "./chunk-54DMWFQZ.js";
117
132
  import "./chunk-TWWSYAKJ.js";
118
133
  import {
119
134
  lintProject
120
- } from "./chunk-JDB65PPT.js";
135
+ } from "./chunk-YOEGUBBY.js";
121
136
  import "./chunk-IWFLMU2N.js";
122
- import "./chunk-APDLWWFG.js";
137
+ import "./chunk-3VGU2SH7.js";
123
138
  import "./chunk-VAS6HQX3.js";
124
139
  import "./chunk-3RM2ETQT.js";
125
140
  import "./chunk-BSQ7XANM.js";
126
- import "./chunk-SDYDNHEE.js";
127
- import "./chunk-2PSPARWP.js";
141
+ import {
142
+ parseHTML
143
+ } from "./chunk-SDYDNHEE.js";
144
+ import "./chunk-TYXH3EKP.js";
128
145
  import "./chunk-IXLUTE6N.js";
129
146
  import {
130
147
  normalizeErrorMessage
@@ -133,7 +150,57 @@ import "./chunk-KGPLIH7B.js";
133
150
  import "./chunk-3GXQPCJC.js";
134
151
  import "./chunk-M2WBOQ2Q.js";
135
152
 
153
+ // src/commands/snapshotSampling.ts
154
+ function parseSnapshotRange(value) {
155
+ const parts = value.split(/[,:]/);
156
+ const range = parts.map((part) => part.trim() === "" ? NaN : Number(part));
157
+ if (parts.length !== 2 || !range.every(Number.isFinite) || range[0] < 0 || range[1] <= range[0])
158
+ throw new Error("--range must be START,END in seconds, with 0 <= START < END");
159
+ return [range[0], range[1]];
160
+ }
161
+ function sampleSnapshotRange([start, end], frames, duration, exclusiveEnd = false) {
162
+ if (!Number.isInteger(frames) || frames < 1)
163
+ throw new Error("--frames must be a positive integer");
164
+ if (!Number.isFinite(duration) || duration <= 0 || !Number.isFinite(start) || !Number.isFinite(end) || start < 0 || end <= start || end > duration)
165
+ throw new Error(`Snapshot range must satisfy 0 <= START < END <= ${duration}s`);
166
+ if (frames === 1) return [(start + end) / 2];
167
+ const last = exclusiveEnd || end === duration ? end - Math.min(1e-3, (end - start) / (frames * 2)) : end;
168
+ return Array.from({ length: frames }, (_, i) => start + (last - start) * i / (frames - 1));
169
+ }
170
+ function resolveSnapshotCompositionRange(html, durations, id, duration) {
171
+ const { document: document2 } = parseHTML(html);
172
+ const matches = Array.from(document2.querySelectorAll("[data-composition-id]")).filter(
173
+ (el) => el.getAttribute("data-composition-id") === id
174
+ );
175
+ if (matches.length !== 1)
176
+ throw new Error(
177
+ `Composition ID ${JSON.stringify(id)} matched ${matches.length} elements; expected exactly one`
178
+ );
179
+ const registry = Object.fromEntries(
180
+ Object.entries(durations).map(([key, value]) => [key, { duration: () => value }])
181
+ );
182
+ const resolver = createRuntimeStartTimeResolver({
183
+ documentRef: document2,
184
+ timelineRegistry: registry,
185
+ includeAuthoredTimingAttrs: true
186
+ });
187
+ let start = 0, end = duration;
188
+ for (let el = matches[0]; el; el = el.parentElement) {
189
+ if (!el.hasAttribute("data-composition-id") && !el.hasAttribute("data-start")) continue;
190
+ const localStart = resolver.resolveStartForElement(el, 0);
191
+ const length = resolver.resolveDurationForElement(el);
192
+ if (!Number.isFinite(localStart))
193
+ throw new Error(`Cannot resolve start of composition ${JSON.stringify(id)}`);
194
+ start = Math.max(start, localStart);
195
+ if (length != null) end = Math.min(end, localStart + length);
196
+ }
197
+ if (end <= start)
198
+ throw new Error(`Composition ${JSON.stringify(id)} has no visible time interval`);
199
+ return [start, end];
200
+ }
201
+
136
202
  // src/commands/snapshot.ts
203
+ import { randomUUID } from "crypto";
137
204
  import { existsSync, mkdtempSync, readFileSync, mkdirSync, rmSync, writeFileSync } from "fs";
138
205
  import { tmpdir } from "os";
139
206
  import { resolve, join, relative, isAbsolute, basename, posix } from "path";
@@ -373,18 +440,29 @@ function computeSnapshotTimes(duration, opts) {
373
440
  return { times: times.map(round), appendedTail: false };
374
441
  }
375
442
  async function captureSnapshots(projectDir, opts) {
376
- const { bundleWithLocalizedFonts } = await import("./bundleWithLocalizedFonts-I4CWOXLK.js");
443
+ const { bundleWithLocalizedFonts } = await import("./bundleWithLocalizedFonts-76DMCRIW.js");
444
+ const capture = await captureArtifactRevision(projectDir).catch(warnSnapshotsNotRecorded);
445
+ const invocation = randomUUID();
446
+ const artifacts = [];
447
+ const replaced = [];
448
+ const warnings = [];
449
+ let completed = false;
377
450
  const numFrames = opts.frames ?? 5;
378
451
  const html = await bundleWithLocalizedFonts(projectDir);
379
452
  const server = await serveStaticProjectHtml(projectDir, html, void 0, [], opts.autoProxy);
380
453
  const savedPaths = [];
381
454
  try {
382
- const { browser: chromeBrowser, page } = await openSettledCompositionPage(html, server.url, {
455
+ const {
456
+ browser: chromeBrowser,
457
+ page,
458
+ renderReadyTimedOut
459
+ } = await openSettledCompositionPage(html, server.url, {
383
460
  renderReadyTimeoutMs: opts.timeout ?? 5e3,
384
461
  renderReadyWarningSuffix: "snapshots may be inaccurate",
385
462
  browserGpuMode: opts.browserGpuMode
386
463
  });
387
464
  try {
465
+ if (renderReadyTimedOut) warnings.push("Render readiness timed out");
388
466
  const fontReport = await page.evaluate(() => {
389
467
  const loaded = [];
390
468
  const errored = [];
@@ -407,6 +485,8 @@ async function captureSnapshots(projectDir, opts) {
407
485
  console.log(` ${c.error("Fonts FAILED:")} ${fontReport.errored.join(", ")}`);
408
486
  }
409
487
  }
488
+ if (fontReport.errored.length)
489
+ warnings.push(`Fonts failed: ${fontReport.errored.join(", ")}`);
410
490
  const duration = await page.evaluate(() => {
411
491
  const win = window;
412
492
  if (typeof win.__player?.getDuration === "function") {
@@ -420,24 +500,51 @@ async function captureSnapshots(projectDir, opts) {
420
500
  if (duration <= 0 && !opts.at?.length) {
421
501
  return [];
422
502
  }
423
- const { times: positions, appendedTail } = computeSnapshotTimes(duration, {
503
+ let range = opts.range;
504
+ if (opts.composition) {
505
+ const timing = await page.evaluate(() => ({
506
+ html: document.documentElement.outerHTML,
507
+ durations: Object.fromEntries(
508
+ Object.entries(window.__timelines ?? {}).map(([id, timeline]) => [
509
+ id,
510
+ Number(timeline.duration?.())
511
+ ])
512
+ )
513
+ }));
514
+ range = resolveSnapshotCompositionRange(
515
+ timing.html,
516
+ timing.durations,
517
+ opts.composition,
518
+ duration
519
+ );
520
+ }
521
+ const { times: positions, appendedTail } = range ? {
522
+ times: sampleSnapshotRange(range, numFrames, duration, !!opts.composition),
523
+ appendedTail: false
524
+ } : computeSnapshotTimes(duration, {
424
525
  frames: numFrames,
425
526
  at: opts.at,
426
527
  includeEnd: opts.includeEnd
427
528
  });
529
+ if (range)
530
+ console.log(
531
+ ` Sampling ${positions.length} frames within ${range[0]}\u2013${range[1]}s${opts.composition ? ` (composition ${opts.composition})` : ""}`
532
+ );
428
533
  if (appendedTail) {
429
534
  console.log(
430
535
  ` ${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.`)}`
431
536
  );
432
537
  }
433
538
  const cameraExpr = opts.angle && (opts.angle.yaw !== 0 || opts.angle.pitch !== 0) ? `(${orbitStageSource()})(${JSON.stringify(opts.angle)})` : null;
434
- const snapshotDir = opts.outputDir ?? join(projectDir, "snapshots");
539
+ const snapshotDir = opts.outputDir ?? artifactDirectory(projectDir, "snapshots");
435
540
  mkdirSync(snapshotDir, { recursive: true });
436
541
  try {
437
542
  const { readdirSync } = await import("fs");
438
543
  for (const file of readdirSync(snapshotDir)) {
439
544
  if (/\.(png|jpg|jpeg)$/i.test(file)) {
440
- rmSync(join(snapshotDir, file), { force: true });
545
+ const path = resolve(snapshotDir, file);
546
+ replaced.push(path);
547
+ rmSync(path, { force: true });
441
548
  }
442
549
  }
443
550
  } catch {
@@ -446,11 +553,12 @@ async function captureSnapshots(projectDir, opts) {
446
553
  let syncVideoFrameVisibility = null;
447
554
  let extractMediaMetadata = null;
448
555
  try {
449
- const engine = await import("./src-7V6LBC56.js");
556
+ const engine = await import("./src-AVQBAECC.js");
450
557
  injectVideoFramesBatch = engine.injectVideoFramesBatch;
451
558
  syncVideoFrameVisibility = engine.syncVideoFrameVisibility;
452
559
  extractMediaMetadata = engine.extractMediaMetadata;
453
560
  } catch {
561
+ warnings.push("Video frame extraction engine unavailable");
454
562
  console.warn(
455
563
  ` ${c.warn("\u26A0")} @hyperframes/engine unavailable \u2014 <video> elements will appear black in snapshots. Verify media via a draft render's extracted frames instead.`
456
564
  );
@@ -553,6 +661,7 @@ async function captureSnapshots(projectDir, opts) {
553
661
  }
554
662
  if (active.length > 0 && updates.length < active.length) {
555
663
  const missed = active.length - updates.length;
664
+ warnings.push(`${missed} video frames missing at ${time}s`);
556
665
  console.warn(
557
666
  ` ${c.warn("\u26A0")} ${missed}/${active.length} active <video> frame(s) could not be extracted at ${time.toFixed(1)}s \u2014 those videos will appear black/stale in this snapshot`
558
667
  );
@@ -566,6 +675,7 @@ async function captureSnapshots(projectDir, opts) {
566
675
  active.map((a) => a.id)
567
676
  );
568
677
  } catch {
678
+ warnings.push(`Video injection failed at ${time}s`);
569
679
  console.warn(
570
680
  ` ${c.warn("\u26A0")} video frame injection failed at ${time.toFixed(1)}s \u2014 <video> elements will appear black/stale in this snapshot`
571
681
  );
@@ -576,6 +686,7 @@ async function captureSnapshots(projectDir, opts) {
576
686
  const index = String(i).padStart(2, "0");
577
687
  const filename = `frame-${index}-at-${timeLabel}.png`;
578
688
  const framePath = join(snapshotDir, filename);
689
+ let crop;
579
690
  if (opts.zoom) {
580
691
  const canvas = await page.evaluate(() => ({
581
692
  width: window.innerWidth,
@@ -583,11 +694,13 @@ async function captureSnapshots(projectDir, opts) {
583
694
  }));
584
695
  const region = await resolveCropRegion(page, opts.zoom, canvas);
585
696
  if (!region) {
697
+ warnings.push(`Crop target unavailable at ${time}s`);
586
698
  console.error(
587
699
  ` ${c.warn("\u26A0")} --zoom target has no visible box at ${time.toFixed(1)}s \u2014 frame skipped`
588
700
  );
589
701
  continue;
590
702
  }
703
+ crop = region;
591
704
  const buffer = await captureRegionCrop(
592
705
  page,
593
706
  region,
@@ -597,6 +710,19 @@ async function captureSnapshots(projectDir, opts) {
597
710
  } else {
598
711
  await page.screenshot({ path: framePath, type: "png", omitBackground: true });
599
712
  }
713
+ artifacts.push({
714
+ id: `${invocation}-${i}`,
715
+ image: resolve(framePath),
716
+ time,
717
+ metadata: {
718
+ ...pngDimensions(framePath),
719
+ crop,
720
+ zoom: opts.zoom,
721
+ zoomScale: opts.zoom ? opts.zoomScale ?? DEFAULT_ZOOM_SCALE : 1,
722
+ angle: opts.angle,
723
+ browserGpuMode: opts.browserGpuMode
724
+ }
725
+ });
600
726
  if (opts.against) {
601
727
  const refPng = await extractVideoFrameToBuffer(opts.against, time);
602
728
  if (!refPng) {
@@ -620,14 +746,48 @@ async function captureSnapshots(projectDir, opts) {
620
746
  const rel = relative(projectDir, framePath);
621
747
  savedPaths.push(rel.startsWith("..") || isAbsolute(rel) ? framePath : rel);
622
748
  }
749
+ completed = true;
623
750
  } finally {
624
751
  await chromeBrowser.close();
625
752
  }
626
753
  } finally {
627
754
  await server.close();
755
+ const recorded = capture ? await recordSnapshotArtifacts(
756
+ projectDir,
757
+ capture,
758
+ artifacts,
759
+ opts.zoom,
760
+ warnings,
761
+ completed
762
+ ) : false;
763
+ if (!recorded) {
764
+ const paths = [...replaced, ...artifacts.map((artifact) => artifact.image)];
765
+ if (paths.length > 0) await forgetRecordedArtifacts(projectDir, paths).catch(() => void 0);
766
+ }
628
767
  }
629
768
  return savedPaths;
630
769
  }
770
+ function warnSnapshotsNotRecorded(error) {
771
+ console.warn(` ${c.warn("\u26A0")} Snapshots were not recorded: ${normalizeErrorMessage(error)}`);
772
+ return void 0;
773
+ }
774
+ async function recordSnapshotArtifacts(projectDir, capture, artifacts, zoom, warnings, completed) {
775
+ const records = await recordArtifacts(
776
+ projectDir,
777
+ capture,
778
+ artifacts.map((artifact) => ({
779
+ kind: "snapshot",
780
+ scope: zoom ? "crop" : "full",
781
+ ...artifact,
782
+ metadata: { ...artifact.metadata, warnings, partial: !completed || warnings.length > 0 }
783
+ }))
784
+ ).catch(warnSnapshotsNotRecorded);
785
+ const record = records?.[0];
786
+ if (!record) return false;
787
+ console.log(` Revision: ${record.revision ?? "none"} (${record.attribution})`);
788
+ console.log(` State: ${join(projectDir, ".hyperframes", "video-state.json")}`);
789
+ return true;
790
+ }
631
791
  var snapshot_default = defineCommand({
632
792
  meta: {
633
793
  name: "snapshot",
@@ -642,7 +802,7 @@ var snapshot_default = defineCommand({
642
802
  output: {
643
803
  type: "string",
644
804
  alias: "o",
645
- description: "Directory to write snapshots into (default: <project>/snapshots)"
805
+ description: "Directory to write snapshots into (default: unique directory under <project>/.hyperframes/artifacts/snapshots)"
646
806
  },
647
807
  frames: {
648
808
  type: "string",
@@ -653,6 +813,14 @@ var snapshot_default = defineCommand({
653
813
  type: "string",
654
814
  description: "Comma-separated timestamps in seconds (e.g., --at 3.0,10.5,18.0). Each is captured at that exact instant, not snapped to a frame."
655
815
  },
816
+ range: {
817
+ type: "string",
818
+ description: "Root-timeline range START,END in seconds; sample --frames evenly within it, without adding the full-video tail"
819
+ },
820
+ composition: {
821
+ type: "string",
822
+ description: "data-composition-id to sample; take --frames within its effective root-timeline interval, without adding the full-video tail"
823
+ },
656
824
  timeout: {
657
825
  type: "string",
658
826
  description: "Ms to wait for runtime to initialize (default: 5000)",
@@ -696,6 +864,12 @@ var snapshot_default = defineCommand({
696
864
  }
697
865
  },
698
866
  async run({ args }) {
867
+ if ([args.at, args.range, args.composition].filter((value) => value !== void 0).length > 1)
868
+ throw new Error("Use only one of --at, --range, or --composition");
869
+ const frames = parseInt(args.frames, 10) || 5;
870
+ const range = args.range === void 0 ? void 0 : parseSnapshotRange(String(args.range));
871
+ const composition = args.composition === void 0 ? void 0 : String(args.composition).trim();
872
+ if (composition === "") throw new Error("--composition requires a composition ID");
699
873
  const project = resolveProject(args.dir);
700
874
  const lintResult = await lintProject(project.dir);
701
875
  if (hasDefinitiveEntryMismatch(lintResult)) {
@@ -724,7 +898,6 @@ var snapshot_default = defineCommand({
724
898
  console.log("");
725
899
  failCommand();
726
900
  }
727
- const frames = parseInt(args.frames, 10) || 5;
728
901
  const timeout = parseInt(args.timeout, 10) || 5e3;
729
902
  const atTimestamps = args.at ? String(args.at).split(",").map((s) => parseFloat(s.trim())).filter((n) => !isNaN(n)) : void 0;
730
903
  const describeArg = args.describe === void 0 ? "true" : String(args.describe) === "false" ? null : String(args.describe);
@@ -740,129 +913,144 @@ var snapshot_default = defineCommand({
740
913
  const angleLabel = camera && (camera.yaw !== 0 || camera.pitch !== 0) ? ` ${c.dim(`(angle yaw ${camera.yaw}\xB0 pitch ${camera.pitch}\xB0)`)}` : "";
741
914
  console.log(`${c.accent("\u25C6")} Capturing ${label} from ${c.accent(project.name)}${angleLabel}`);
742
915
  try {
743
- const snapshotDir = args.output ? resolve(String(args.output)) : join(project.dir, "snapshots");
744
- const paths = await captureSnapshots(project.dir, {
745
- frames,
746
- timeout,
747
- at: atTimestamps,
748
- outputDir: snapshotDir,
749
- angle: camera,
750
- includeEnd: args.end !== false,
751
- zoom: zoomTarget,
752
- zoomScale,
753
- autoProxy: args.proxy,
754
- browserGpuMode: resolveLocalBrowserGpuMode(args["browser-gpu"]),
755
- against
756
- });
757
- if (paths.length === 0) {
758
- console.log(
759
- `
760
- ${c.error("\u2717")} Could not determine composition duration \u2014 no frames captured`
761
- );
762
- failCommand();
763
- }
764
- console.log(
765
- `
766
- ${c.success("\u25C7")} ${paths.length} snapshots saved to ${args.output ? snapshotDir : "snapshots/"}`
767
- );
768
- for (const p of paths) {
769
- console.log(` ${p}`);
770
- }
771
- if (against) {
772
- console.log(
773
- ` ${c.dim("ref-*.png + pair-*.jpg")} beside each frame (reference frame, render | reference sheet)`
774
- );
775
- }
776
- try {
777
- const { createSnapshotContactSheet } = await import("./contactSheet-27SRU7A4.js");
778
- const sheets = await createSnapshotContactSheet(
779
- snapshotDir,
780
- join(snapshotDir, "contact-sheet.jpg")
916
+ const snapshotDir = args.output ? resolve(String(args.output)) : artifactDirectory(project.dir, "snapshots");
917
+ await withProjectLock(project.dir, "snapshot", async () => {
918
+ const paths = await captureSnapshots(project.dir, {
919
+ frames,
920
+ range,
921
+ composition,
922
+ timeout,
923
+ at: atTimestamps,
924
+ outputDir: snapshotDir,
925
+ angle: camera,
926
+ includeEnd: args.end !== false,
927
+ zoom: zoomTarget,
928
+ zoomScale,
929
+ autoProxy: args.proxy,
930
+ browserGpuMode: resolveLocalBrowserGpuMode(args["browser-gpu"]),
931
+ against
932
+ });
933
+ reportArtifacts(
934
+ "snapshot",
935
+ "frame",
936
+ paths.map((path) => resolve(project.dir, path))
781
937
  );
782
- if (sheets.length > 0) {
783
- const label2 = sheets.length === 1 ? "contact-sheet.jpg" : `contact-sheet-1..${sheets.length}.jpg`;
784
- console.log(` ${c.dim(label2)} (grid view for AI review)`);
938
+ if (paths.length === 0) {
939
+ console.log(
940
+ `
941
+ ${c.error("\u2717")} Could not determine composition duration \u2014 no frames captured`
942
+ );
943
+ failCommand();
944
+ }
945
+ console.log(`
946
+ ${c.success("\u25C7")} ${paths.length} snapshots saved to ${snapshotDir}`);
947
+ for (const p of paths) {
948
+ console.log(` ${p}`);
949
+ }
950
+ if (against) {
951
+ console.log(
952
+ ` ${c.dim("ref-*.png + pair-*.jpg")} beside each frame (reference frame, render | reference sheet)`
953
+ );
785
954
  }
786
- } catch (err) {
787
- console.warn(
788
- ` ${c.warn("\u26A0")} Contact sheet skipped: ${normalizeErrorMessage(err)}. Individual snapshot PNGs are still available.`
789
- );
790
- }
791
- if (describeArg !== null) {
792
955
  try {
793
- const geminiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
794
- if (!geminiKey) {
795
- console.log(` ${c.dim("--describe: GEMINI_API_KEY not set, skipping")}`);
796
- } else if (paths.length > 0) {
797
- console.log(` ${c.dim("Describing frames with Gemini vision...")}`);
798
- const { GoogleGenAI } = await loadOptionalPackage("@google/genai", "--describe");
799
- const ai = new GoogleGenAI({ apiKey: geminiKey });
800
- const model = process.env.HYPERFRAMES_GEMINI_MODEL || "gemini-3.1-flash-lite-preview";
801
- const customQuestion = describeArg === "true" ? "Describe this video composition frame in 1-2 sentences. Be specific and factual: what elements are visible, what text appears, is the frame blank/black/loading, what is the composition. Flag any obvious problems." : describeArg;
802
- const descriptions = [
803
- `# Snapshot Frame Descriptions`,
804
- ``,
805
- `**Question asked:** ${customQuestion}`,
806
- ``,
807
- `Compare each description against your storyboard spec. A "black frame" or "loading screen" for a content beat is a bug.`,
808
- ``
809
- ];
810
- let sharpFn = null;
811
- try {
812
- const s = await import("sharp");
813
- sharpFn = s.default ?? s;
814
- } catch {
815
- }
816
- const results = await Promise.allSettled(
817
- paths.map(async (p) => {
818
- const filename = basename(p);
819
- const filePath = join(snapshotDir, filename);
820
- if (!existsSync(filePath)) return { filename, desc: "file not found" };
821
- const raw = readFileSync(filePath);
822
- let imageData;
823
- let mimeType = "image/png";
824
- if (sharpFn) {
825
- imageData = await sharpFn(raw).resize(960).jpeg().toBuffer();
826
- mimeType = "image/jpeg";
956
+ const { createSnapshotContactSheet } = await import("./contactSheet-27SRU7A4.js");
957
+ const sheets = await createSnapshotContactSheet(
958
+ snapshotDir,
959
+ join(snapshotDir, "contact-sheet.jpg")
960
+ );
961
+ reportArtifacts("snapshot", "contact-sheet", sheets);
962
+ for (const sheet of sheets) {
963
+ console.log(` ${resolve(sheet)} (grid view for AI review)`);
964
+ }
965
+ } catch (err) {
966
+ console.warn(
967
+ ` ${c.warn("\u26A0")} Contact sheet skipped: ${normalizeErrorMessage(err)}. Individual snapshot PNGs are still available.`
968
+ );
969
+ }
970
+ if (describeArg !== null) {
971
+ try {
972
+ const geminiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
973
+ if (!geminiKey) {
974
+ if (args.describe !== void 0)
975
+ console.log(` ${c.dim("--describe: GEMINI_API_KEY not set, skipping")}`);
976
+ } else if (paths.length > 0) {
977
+ console.log(` ${c.dim("Describing frames with Gemini vision...")}`);
978
+ const { GoogleGenAI } = await loadOptionalPackage("@google/genai", "--describe");
979
+ const ai = new GoogleGenAI({ apiKey: geminiKey });
980
+ const model = process.env.HYPERFRAMES_GEMINI_MODEL || "gemini-3.1-flash-lite-preview";
981
+ const customQuestion = describeArg === "true" ? "Describe this video composition frame in 1-2 sentences. Be specific and factual: what elements are visible, what text appears, is the frame blank/black/loading, what is the composition. Flag any obvious problems." : describeArg;
982
+ const descriptions = [
983
+ `# Snapshot Frame Descriptions`,
984
+ ``,
985
+ `**Question asked:** ${customQuestion}`,
986
+ ``,
987
+ `Compare each description against your storyboard spec. A "black frame" or "loading screen" for a content beat is a bug.`,
988
+ ``
989
+ ];
990
+ let sharpFn = null;
991
+ try {
992
+ const s = await import("sharp");
993
+ sharpFn = s.default ?? s;
994
+ } catch {
995
+ }
996
+ const results = await Promise.allSettled(
997
+ paths.map(async (p) => {
998
+ const filename = basename(p);
999
+ const filePath = join(snapshotDir, filename);
1000
+ if (!existsSync(filePath)) return { filename, desc: "file not found" };
1001
+ const raw = readFileSync(filePath);
1002
+ let imageData;
1003
+ let mimeType = "image/png";
1004
+ if (sharpFn) {
1005
+ imageData = await sharpFn(raw).resize(960).jpeg().toBuffer();
1006
+ mimeType = "image/jpeg";
1007
+ } else {
1008
+ if (raw.length > 38e5)
1009
+ return {
1010
+ filename,
1011
+ desc: "file too large for Gemini \u2014 install sharp to enable auto-resize"
1012
+ };
1013
+ imageData = raw;
1014
+ }
1015
+ const base64 = imageData.toString("base64");
1016
+ const response = await ai.models.generateContent({
1017
+ model,
1018
+ contents: [
1019
+ {
1020
+ role: "user",
1021
+ parts: [
1022
+ { inlineData: { mimeType, data: base64 } },
1023
+ { text: customQuestion }
1024
+ ]
1025
+ }
1026
+ ],
1027
+ config: { maxOutputTokens: 250 }
1028
+ });
1029
+ return { filename, desc: response.text?.trim() || "no description" };
1030
+ })
1031
+ );
1032
+ for (const result of results) {
1033
+ if (result.status === "fulfilled") {
1034
+ descriptions.push(`## ${result.value.filename}`, `${result.value.desc}`, ``);
827
1035
  } else {
828
- if (raw.length > 38e5)
829
- return {
830
- filename,
831
- desc: "file too large for Gemini \u2014 install sharp to enable auto-resize"
832
- };
833
- imageData = raw;
1036
+ const errMsg = normalizeErrorMessage(result.reason);
1037
+ descriptions.push(
1038
+ `## (error)`,
1039
+ `Gemini call failed: ${errMsg.slice(0, 120)}`,
1040
+ ``
1041
+ );
834
1042
  }
835
- const base64 = imageData.toString("base64");
836
- const response = await ai.models.generateContent({
837
- model,
838
- contents: [
839
- {
840
- role: "user",
841
- parts: [{ inlineData: { mimeType, data: base64 } }, { text: customQuestion }]
842
- }
843
- ],
844
- config: { maxOutputTokens: 250 }
845
- });
846
- return { filename, desc: response.text?.trim() || "no description" };
847
- })
848
- );
849
- for (const result of results) {
850
- if (result.status === "fulfilled") {
851
- descriptions.push(`## ${result.value.filename}`, `${result.value.desc}`, ``);
852
- } else {
853
- const errMsg = normalizeErrorMessage(result.reason);
854
- descriptions.push(`## (error)`, `Gemini call failed: ${errMsg.slice(0, 120)}`, ``);
855
1043
  }
1044
+ const descPath = join(snapshotDir, "descriptions.md");
1045
+ writeFileSync(descPath, descriptions.join("\n"));
1046
+ console.log(` ${c.dim("descriptions.md")} (Gemini frame analysis)`);
856
1047
  }
857
- const descPath = join(snapshotDir, "descriptions.md");
858
- writeFileSync(descPath, descriptions.join("\n"));
859
- console.log(` ${c.dim("descriptions.md")} (Gemini frame analysis)`);
1048
+ } catch (descErr) {
1049
+ const msg = normalizeErrorMessage(descErr);
1050
+ console.log(` ${c.dim(`--describe failed: ${msg}`)}`);
860
1051
  }
861
- } catch (descErr) {
862
- const msg = normalizeErrorMessage(descErr);
863
- console.log(` ${c.dim(`--describe failed: ${msg}`)}`);
864
1052
  }
865
- }
1053
+ });
866
1054
  } catch (err) {
867
1055
  const msg = normalizeErrorMessage(err);
868
1056
  console.error(`