@biunow/agent-biuart 0.0.0-stage → 0.1.1

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 (377) hide show
  1. package/bin/server.mjs +575 -0
  2. package/data/styles.json +533 -0
  3. package/engine/audit_clip.py +46 -0
  4. package/engine/clip.html +24 -0
  5. package/engine/clip.js +104 -0
  6. package/engine/clip_schema.json +19 -0
  7. package/engine/clips/t1_3b1b.js +203 -0
  8. package/engine/clips/t2_keynote_ui.js +347 -0
  9. package/engine/clips/t3_finance_chart.js +206 -0
  10. package/engine/clips/y1_kurzgesagt.js +183 -0
  11. package/engine/clips/y2_vox.js +150 -0
  12. package/engine/clips/y3_whiteboard.js +209 -0
  13. package/engine/clips/y4_storytime.js +194 -0
  14. package/engine/clips/y5_kinetic_type.js +134 -0
  15. package/engine/compare.py +19 -0
  16. package/engine/demos/_shared/hero/c1_0.png +0 -0
  17. package/engine/demos/_shared/hero/c1_1.png +0 -0
  18. package/engine/demos/_shared/hero/c1_2.png +0 -0
  19. package/engine/demos/_shared/hero/c3_line.png +0 -0
  20. package/engine/demos/_shared/hero.js +96 -0
  21. package/engine/demos/long_scroll/eras.js +23 -0
  22. package/engine/demos/long_scroll/frames/8bit_act/01.png +0 -0
  23. package/engine/demos/long_scroll/frames/8bit_act/02.png +0 -0
  24. package/engine/demos/long_scroll/frames/8bit_act/03.png +0 -0
  25. package/engine/demos/long_scroll/frames/8bit_act/04.png +0 -0
  26. package/engine/demos/long_scroll/frames/8bit_act/05.png +0 -0
  27. package/engine/demos/long_scroll/frames/8bit_act/06.png +0 -0
  28. package/engine/demos/long_scroll/frames/8bit_act/07.png +0 -0
  29. package/engine/demos/long_scroll/frames/8bit_act/08.png +0 -0
  30. package/engine/demos/long_scroll/frames/8bit_act/meta.json +69 -0
  31. package/engine/demos/long_scroll/frames/8bit_walk/01.png +0 -0
  32. package/engine/demos/long_scroll/frames/8bit_walk/02.png +0 -0
  33. package/engine/demos/long_scroll/frames/8bit_walk/03.png +0 -0
  34. package/engine/demos/long_scroll/frames/8bit_walk/04.png +0 -0
  35. package/engine/demos/long_scroll/frames/8bit_walk/05.png +0 -0
  36. package/engine/demos/long_scroll/frames/8bit_walk/06.png +0 -0
  37. package/engine/demos/long_scroll/frames/8bit_walk/07.png +0 -0
  38. package/engine/demos/long_scroll/frames/8bit_walk/08.png +0 -0
  39. package/engine/demos/long_scroll/frames/8bit_walk/meta.json +69 -0
  40. package/engine/demos/long_scroll/frames/egypt_dodge/01.png +0 -0
  41. package/engine/demos/long_scroll/frames/egypt_dodge/02.png +0 -0
  42. package/engine/demos/long_scroll/frames/egypt_dodge/03.png +0 -0
  43. package/engine/demos/long_scroll/frames/egypt_dodge/04.png +0 -0
  44. package/engine/demos/long_scroll/frames/egypt_dodge/05.png +0 -0
  45. package/engine/demos/long_scroll/frames/egypt_dodge/06.png +0 -0
  46. package/engine/demos/long_scroll/frames/egypt_dodge/07.png +0 -0
  47. package/engine/demos/long_scroll/frames/egypt_dodge/08.png +0 -0
  48. package/engine/demos/long_scroll/frames/egypt_dodge/meta.json +69 -0
  49. package/engine/demos/long_scroll/frames/egypt_walk8/01.png +0 -0
  50. package/engine/demos/long_scroll/frames/egypt_walk8/02.png +0 -0
  51. package/engine/demos/long_scroll/frames/egypt_walk8/03.png +0 -0
  52. package/engine/demos/long_scroll/frames/egypt_walk8/04.png +0 -0
  53. package/engine/demos/long_scroll/frames/egypt_walk8/05.png +0 -0
  54. package/engine/demos/long_scroll/frames/egypt_walk8/06.png +0 -0
  55. package/engine/demos/long_scroll/frames/egypt_walk8/07.png +0 -0
  56. package/engine/demos/long_scroll/frames/egypt_walk8/08.png +0 -0
  57. package/engine/demos/long_scroll/frames/egypt_walk8/meta.json +69 -0
  58. package/engine/demos/long_scroll/frames/monet_throw/01.png +0 -0
  59. package/engine/demos/long_scroll/frames/monet_throw/02.png +0 -0
  60. package/engine/demos/long_scroll/frames/monet_throw/03.png +0 -0
  61. package/engine/demos/long_scroll/frames/monet_throw/04.png +0 -0
  62. package/engine/demos/long_scroll/frames/monet_throw/05.png +0 -0
  63. package/engine/demos/long_scroll/frames/monet_throw/06.png +0 -0
  64. package/engine/demos/long_scroll/frames/monet_throw/07.png +0 -0
  65. package/engine/demos/long_scroll/frames/monet_throw/08.png +0 -0
  66. package/engine/demos/long_scroll/frames/monet_throw/meta.json +71 -0
  67. package/engine/demos/long_scroll/frames/monet_walk/01.png +0 -0
  68. package/engine/demos/long_scroll/frames/monet_walk/02.png +0 -0
  69. package/engine/demos/long_scroll/frames/monet_walk/03.png +0 -0
  70. package/engine/demos/long_scroll/frames/monet_walk/04.png +0 -0
  71. package/engine/demos/long_scroll/frames/monet_walk/05.png +0 -0
  72. package/engine/demos/long_scroll/frames/monet_walk/06.png +0 -0
  73. package/engine/demos/long_scroll/frames/monet_walk/07.png +0 -0
  74. package/engine/demos/long_scroll/frames/monet_walk/08.png +0 -0
  75. package/engine/demos/long_scroll/frames/monet_walk/meta.json +71 -0
  76. package/engine/demos/long_scroll/lib/acts.js +87 -0
  77. package/engine/demos/long_scroll/lib/crossing.js +463 -0
  78. package/engine/demos/long_scroll/lib/world.js +143 -0
  79. package/engine/demos/long_scroll/lib/xing2_ding.js +42 -0
  80. package/engine/demos/long_scroll/lib/xing2_jia.js +114 -0
  81. package/engine/demos/long_scroll/lib/xing2_yi.js +133 -0
  82. package/engine/demos/long_scroll/segments/02_egypt.js +234 -0
  83. package/engine/demos/long_scroll/segments/14_8bit.js +118 -0
  84. package/engine/demos/long_scroll/segments/28_monet.js +86 -0
  85. package/engine/demos/t1_3b1b/eras.js +11 -0
  86. package/engine/demos/t1_3b1b/t1_s1.js +101 -0
  87. package/engine/demos/t1_3b1b/t1_s2.js +74 -0
  88. package/engine/demos/t1_3b1b/t1_s3.js +65 -0
  89. package/engine/demos/t2_keynote_ui/eras.js +10 -0
  90. package/engine/demos/t2_keynote_ui/t2_s1.js +57 -0
  91. package/engine/demos/t2_keynote_ui/t2_s2.js +70 -0
  92. package/engine/demos/t2_keynote_ui/t2_s3.js +61 -0
  93. package/engine/demos/t3_finance_chart/eras.js +11 -0
  94. package/engine/demos/t3_finance_chart/t3_s1.js +48 -0
  95. package/engine/demos/t3_finance_chart/t3_s2.js +42 -0
  96. package/engine/demos/t3_finance_chart/t3_s3.js +51 -0
  97. package/engine/demos/y1_kurzgesagt/eras.js +10 -0
  98. package/engine/demos/y1_kurzgesagt/kurzgesagt.js +347 -0
  99. package/engine/demos/y2_vox/eras.js +10 -0
  100. package/engine/demos/y2_vox/vox.js +240 -0
  101. package/engine/demos/y3_whiteboard/eras.js +10 -0
  102. package/engine/demos/y3_whiteboard/whiteboard.js +162 -0
  103. package/engine/demos/y4_storytime/eras.js +14 -0
  104. package/engine/demos/y4_storytime/s1_desk.js +18 -0
  105. package/engine/demos/y4_storytime/s2_screen.js +82 -0
  106. package/engine/demos/y4_storytime/s3_react.js +39 -0
  107. package/engine/demos/y4_storytime/s4_wide.js +16 -0
  108. package/engine/demos/y4_storytime/s5_close.js +18 -0
  109. package/engine/demos/y4_storytime/story.js +25 -0
  110. package/engine/demos/y4_storytime/vo_env.js +2 -0
  111. package/engine/demos/y4_storytime/world.js +73 -0
  112. package/engine/demos/y5_kinetic_type/eras.js +16 -0
  113. package/engine/demos/y5_kinetic_type/k1_title.js +38 -0
  114. package/engine/demos/y5_kinetic_type/k2_complete.js +55 -0
  115. package/engine/demos/y5_kinetic_type/k3_chat.js +47 -0
  116. package/engine/demos/y5_kinetic_type/k4_agent.js +49 -0
  117. package/engine/demos/y5_kinetic_type/kinetic.js +52 -0
  118. package/engine/engine.js +277 -0
  119. package/engine/eras.js +60 -0
  120. package/engine/eras_gallery.js +115 -0
  121. package/engine/eras_style_studies.js +20 -0
  122. package/engine/examples/assets/K/347/272/277/345/233/276_/346/250/252/345/261/217.png +0 -0
  123. package/engine/examples/assets//347/273/210/347/253/257_for/345/276/252/347/216/257/346/261/202/345/222/214.png +0 -0
  124. package/engine/examples/assets//347/273/210/347/253/257_sum/346/261/202/345/222/214.png +0 -0
  125. package/engine/examples/assets//350/220/245/346/224/266/345/233/276_/347/253/226/345/261/217.png +0 -0
  126. package/engine/examples/assets//350/264/242/347/273/217/345/233/276/350/241/250_/346/250/252/345/261/217.png +0 -0
  127. package/engine/examples/t1_3b1b.json +19 -0
  128. package/engine/examples/t2_fullbleed_pulse.json +15 -0
  129. package/engine/examples/t2_keynote_ui.json +15 -0
  130. package/engine/examples/t3_finance_chart.json +29 -0
  131. package/engine/examples/y1_kurzgesagt.json +16 -0
  132. package/engine/examples/y2_vox.json +14 -0
  133. package/engine/examples/y3_whiteboard.json +17 -0
  134. package/engine/examples/y4_storytime.json +52 -0
  135. package/engine/examples/y5_kinetic_type.json +14 -0
  136. package/engine/index.html +42 -0
  137. package/engine/lib/THREE_LICENSE.txt +21 -0
  138. package/engine/lib/brush.js +149 -0
  139. package/engine/lib/camera.js +99 -0
  140. package/engine/lib/chart.js +152 -0
  141. package/engine/lib/collage.js +97 -0
  142. package/engine/lib/diagram.js +264 -0
  143. package/engine/lib/finish.js +70 -0
  144. package/engine/lib/fonts/AlfaSlabOne-400.woff +0 -0
  145. package/engine/lib/fonts/Anton-400.woff +0 -0
  146. package/engine/lib/fonts/ArchivoBlack-400.woff +0 -0
  147. package/engine/lib/fonts/Bangers-400.woff +0 -0
  148. package/engine/lib/fonts/Caveat-600.woff +0 -0
  149. package/engine/lib/fonts/Cinzel-400.woff +0 -0
  150. package/engine/lib/fonts/Cinzel-700.woff +0 -0
  151. package/engine/lib/fonts/CormorantGaramond-500.woff +0 -0
  152. package/engine/lib/fonts/CormorantGaramond-500i.woff +0 -0
  153. package/engine/lib/fonts/CormorantGaramond-700.woff +0 -0
  154. package/engine/lib/fonts/EBGaramond-400.woff +0 -0
  155. package/engine/lib/fonts/EBGaramond-400i.woff +0 -0
  156. package/engine/lib/fonts/EBGaramond-600.woff +0 -0
  157. package/engine/lib/fonts/Exo2-800i.woff +0 -0
  158. package/engine/lib/fonts/GochiHand-400.woff +0 -0
  159. package/engine/lib/fonts/IMFellEnglish-400.woff +0 -0
  160. package/engine/lib/fonts/IMFellEnglish-400i.woff +0 -0
  161. package/engine/lib/fonts/Inter-var.woff +0 -0
  162. package/engine/lib/fonts/JosefinSans-700.woff +0 -0
  163. package/engine/lib/fonts/Kalam-700.woff +0 -0
  164. package/engine/lib/fonts/LICENSES.md +40 -0
  165. package/engine/lib/fonts/LXGWWenKai-500.woff +0 -0
  166. package/engine/lib/fonts/LibreBaskerville-400.woff +0 -0
  167. package/engine/lib/fonts/LibreBaskerville-700.woff +0 -0
  168. package/engine/lib/fonts/LilitaOne-400.woff +0 -0
  169. package/engine/lib/fonts/Marcellus-400.woff +0 -0
  170. package/engine/lib/fonts/NotoSansSC-500.woff +0 -0
  171. package/engine/lib/fonts/NotoSansSC-700.woff +0 -0
  172. package/engine/lib/fonts/NotoSansSC-800.woff +0 -0
  173. package/engine/lib/fonts/NotoSansSC-900.woff +0 -0
  174. package/engine/lib/fonts/NotoSansSC-sub.woff +0 -0
  175. package/engine/lib/fonts/NotoSerifJP-600.woff +0 -0
  176. package/engine/lib/fonts/NotoSerifSC-sub.woff +0 -0
  177. package/engine/lib/fonts/OFL.txt +94 -0
  178. package/engine/lib/fonts/PlayfairDisplay-400i.woff +0 -0
  179. package/engine/lib/fonts/PlayfairDisplay-700.woff +0 -0
  180. package/engine/lib/fonts/PlayfairDisplay-700i.woff +0 -0
  181. package/engine/lib/fonts/Poppins-600.woff +0 -0
  182. package/engine/lib/fonts/Poppins-700.woff +0 -0
  183. package/engine/lib/fonts/Poppins-800.woff +0 -0
  184. package/engine/lib/fonts/PressStart2P-400.woff +0 -0
  185. package/engine/lib/fonts/Righteous-400.woff +0 -0
  186. package/engine/lib/fonts/RobotoCondensed-var.woff +0 -0
  187. package/engine/lib/fonts/Rye-400.woff +0 -0
  188. package/engine/lib/fonts/UnifrakturMaguntia-400.woff +0 -0
  189. package/engine/lib/fonts/cmunrm.woff +0 -0
  190. package/engine/lib/fonts/cmunti.woff +0 -0
  191. package/engine/lib/fonts/index.json +1 -0
  192. package/engine/lib/fonts.js +56 -0
  193. package/engine/lib/kit.js +91 -0
  194. package/engine/lib/media.js +118 -0
  195. package/engine/lib/medium.js +258 -0
  196. package/engine/lib/motion.js +111 -0
  197. package/engine/lib/paint.js +200 -0
  198. package/engine/lib/palettes.js +57 -0
  199. package/engine/lib/post.js +108 -0
  200. package/engine/lib/render.js +189 -0
  201. package/engine/lib/rig.js +390 -0
  202. package/engine/lib/rig_huashu.js +171 -0
  203. package/engine/lib/three.min.js +7 -0
  204. package/engine/lib/toon.js +186 -0
  205. package/engine/lib/typo.js +136 -0
  206. package/engine/lib/ui.js +141 -0
  207. package/engine/lib/util.js +92 -0
  208. package/engine/render.py +157 -0
  209. package/engine/scenes/01_cave.js +364 -0
  210. package/engine/scenes/02_egypt.js +524 -0
  211. package/engine/scenes/03_greek.js +448 -0
  212. package/engine/scenes/04_roman.js +555 -0
  213. package/engine/scenes/05_gothic.js +522 -0
  214. package/engine/scenes/06_renaissance.js +338 -0
  215. package/engine/scenes/08_impressionism.js +206 -0
  216. package/engine/scenes/09_postimp.js +183 -0
  217. package/engine/scenes/10_nouveau.js +334 -0
  218. package/engine/scenes/11_cubism.js +265 -0
  219. package/engine/scenes/12_bauhaus.js +214 -0
  220. package/engine/scenes/13_pop.js +269 -0
  221. package/engine/scenes/14_8bit.js +238 -0
  222. package/engine/scenes/15_raytrace.js +300 -0
  223. package/engine/scenes/16_2026.js +565 -0
  224. package/engine/scenes/17_ink.js +273 -0
  225. package/engine/scenes/18_klimt.js +227 -0
  226. package/engine/scenes/19_munch.js +153 -0
  227. package/engine/scenes/20_dunhuang.js +215 -0
  228. package/engine/scenes/21_kusama.js +124 -0
  229. package/engine/scenes/22_constructivism.js +205 -0
  230. package/engine/scenes/23_dali.js +221 -0
  231. package/engine/scenes/24_hopper.js +150 -0
  232. package/engine/scenes/25_ghibli.js +251 -0
  233. package/engine/scenes/26_vaporwave.js +191 -0
  234. package/engine/scenes/27_kirby.js +255 -0
  235. package/engine/scenes/28_monet.js +176 -0
  236. package/engine/scenes/29_seurat.js +104 -0
  237. package/engine/scenes/30_matisse.js +151 -0
  238. package/engine/scenes/31_haring.js +109 -0
  239. package/engine/scenes/32_rembrandt.js +217 -0
  240. package/engine/scenes/33_rubberhose.js +219 -0
  241. package/engine/scenes/34_shadowpuppet.js +195 -0
  242. package/engine/scenes/35_shinkai.js +197 -0
  243. package/engine/scenes/36_picasso_blue.js +123 -0
  244. package/engine/scenes/38_giotto.js +133 -0
  245. package/engine/scenes/39_piero.js +53 -0
  246. package/engine/scenes/40_raphael.js +96 -0
  247. package/engine/scenes/41_michelangelo.js +112 -0
  248. package/engine/scenes/42_guardi.js +70 -0
  249. package/engine/scenes/43_lorenzetti.js +145 -0
  250. package/engine/scenes/44_cortona.js +86 -0
  251. package/engine/scenes/45_renoir.js +86 -0
  252. package/engine/scenes/46_pissarro.js +82 -0
  253. package/engine/scenes/47_sisley.js +94 -0
  254. package/engine/scenes/48_gauguin.js +107 -0
  255. package/engine/scenes/49_chagall.js +60 -0
  256. package/engine/scenes/50_ma_yuan.js +131 -0
  257. package/engine/scenes/51_persian_miniature.js +149 -0
  258. package/engine/scenes/52_south_park.js +105 -0
  259. package/engine/scenes/53_pixar.js +60 -0
  260. package/engine/scenes/54_ghibli_lab.js +68 -0
  261. package/engine/scenes/55_dunhuang.js +161 -0
  262. package/engine/scenes/index.js +28 -0
  263. package/engine/spec_assets.py +76 -0
  264. package/engine/transitions.js +929 -0
  265. package/engine//346/227/266/344/273/243/344/275/234/350/200/205/350/247/204/350/214/203.md +49 -0
  266. package/lib/host.mjs +754 -0
  267. package/lib/render-bridge.mjs +543 -0
  268. package/lib/render-cli.mjs +98 -0
  269. package/lib/storyboard-lint.mjs +54 -0
  270. package/manifest.json +52 -0
  271. package/package.json +8 -4
  272. package/runtime/package.json +8 -0
  273. package/skills/biuart/SKILL.md +154 -0
  274. package/skills/biuart/references/01-/346/213/206/350/247/243.md +55 -0
  275. package/skills/biuart/references/02-/346/234/272/345/210/266.md +41 -0
  276. package/skills/biuart/references/03-/344/270/200/345/270/247/345/205/210/350/241/214/345/233/233/346/235/241/350/267/257/347/272/277.md +39 -0
  277. package/skills/biuart/references/04-/347/272/257/344/273/243/347/240/201/347/273/230/345/210/266.md +92 -0
  278. package/skills/biuart/references/05-/350/212/202/345/245/217/344/270/216/351/205/215/344/271/220.md +19 -0
  279. package/skills/biuart/references/06-/345/217/243/346/222/255/351/251/261/345/212/250/347/232/204/350/211/272/346/234/257/347/237/255/347/211/207.md +31 -0
  280. package/skills/biuart/references/07-/346/255/243/351/235/242/347/273/217/351/252/214.md +196 -0
  281. package/skills/biuart/references/08-/351/243/216/346/240/274/344/275/234/350/200/205/350/247/204/350/214/203.md +61 -0
  282. package/skills/biuart/references/09-/350/247/206/351/242/221/345/212/250/347/224/273/350/257/255/346/263/225.md +382 -0
  283. package/skills/biuart/references/10-/350/247/222/350/211/262.md +34 -0
  284. package/skills/biuart/references/11-/351/225/277/345/215/267/347/251/277/350/266/212/347/211/207.md +43 -0
  285. package/skills/biuart/references/12-/345/217/243/346/222/255/346/225/264/347/211/207/344/270/216/347/273/217/351/252/214/345/233/236/346/265/201.md +25 -0
  286. package/skills/biuart/references/13-/345/217/243/346/222/255/344/270/216/350/257/255/351/237/263/345/244/215/345/210/273.md +7 -0
  287. package/skills/biuart/references/15-/350/267/250/345/234/272/346/231/257/351/243/216/346/240/274/346/217/220/347/202/274.md +48 -0
  288. package/skills/biuart/references/16-/351/243/216/346/240/274/346/274/224/347/244/27295/345/210/206/345/215/217/350/256/256.md +103 -0
  289. package/skills/biuart/references/about-author.md +99 -0
  290. package/skills/biuart/references/capabilities.md +91 -0
  291. package/skills/biuart/references/compatibility.md +34 -0
  292. package/skills/biuart/references/images.md +97 -0
  293. package/skills/biuart/references/voice-model-review.md +18 -0
  294. package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/t1_3b1b.md +122 -0
  295. package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/t2_keynote_ui.md +117 -0
  296. package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/t3_finance_chart.md +116 -0
  297. package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y1_kurzgesagt.md +193 -0
  298. package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y2_vox.md +195 -0
  299. package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y3_whiteboard.md +190 -0
  300. package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y4_storytime.md +166 -0
  301. package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y5_kinetic_type.md +158 -0
  302. package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y6_presenter_explainer.md +24 -0
  303. package/skills/biuart/references//345/217/243/346/222/255API.md +125 -0
  304. package/skills/biuart/references//350/211/262/346/235/277.md +38 -0
  305. package/skills/biuart/references//350/247/222/350/211/262/347/262/276/347/273/206/345/272/246.md +61 -0
  306. package/skills/biuart/references//351/225/234/345/244/264/350/241/250/346/250/241/346/235/277.md +80 -0
  307. package/skills/biuart/references//351/225/234/345/244/264/350/257/255/350/250/200.md +73 -0
  308. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/01_cave.md +46 -0
  309. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/02_egypt.md +64 -0
  310. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/03_greek.md +50 -0
  311. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/04_roman.md +99 -0
  312. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/05_gothic.md +32 -0
  313. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/06_renaissance.md +41 -0
  314. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/08_impressionism.md +34 -0
  315. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/09_postimp.md +17 -0
  316. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/10_nouveau.md +31 -0
  317. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/11_cubism.md +30 -0
  318. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/12_bauhaus.md +30 -0
  319. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/13_pop.md +29 -0
  320. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/14_8bit.md +27 -0
  321. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/15_raytrace.md +32 -0
  322. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/16_2026.md +55 -0
  323. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/17_ink.md +50 -0
  324. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/18_klimt.md +37 -0
  325. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/19_munch.md +42 -0
  326. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/20_dunhuang.md +49 -0
  327. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/21_kusama.md +32 -0
  328. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/22_constructivism.md +35 -0
  329. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/23_dali.md +29 -0
  330. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/24_hopper.md +30 -0
  331. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/25_ghibli.md +55 -0
  332. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/26_vaporwave.md +27 -0
  333. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/27_kirby.md +30 -0
  334. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/28_monet.md +39 -0
  335. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/29_seurat.md +31 -0
  336. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/30_matisse.md +26 -0
  337. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/31_haring.md +22 -0
  338. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/32_rembrandt.md +29 -0
  339. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/33_rubberhose.md +31 -0
  340. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/34_shadowpuppet.md +40 -0
  341. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/35_shinkai.md +34 -0
  342. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/36_picasso_blue.md +25 -0
  343. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/37_rick_morty.md +48 -0
  344. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/38_giotto.md +67 -0
  345. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/39_piero.md +59 -0
  346. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/40_raphael.md +65 -0
  347. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/41_michelangelo.md +65 -0
  348. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/42_guardi.md +65 -0
  349. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/43_lorenzetti.md +69 -0
  350. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/44_cortona.md +66 -0
  351. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/45_renoir.md +66 -0
  352. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/46_pissarro.md +66 -0
  353. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/47_sisley.md +67 -0
  354. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/48_gauguin.md +66 -0
  355. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/49_chagall.md +59 -0
  356. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/50_ma_yuan.md +68 -0
  357. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/51_persian_miniature.md +66 -0
  358. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/52_south_park.md +65 -0
  359. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/53_pixar.md +61 -0
  360. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/55_dunhuang.md +41 -0
  361. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/INDEX.md +89 -0
  362. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/_/350/275/254/345/234/272_/350/277/201/347/247/273/346/265/213/350/257/225.md +53 -0
  363. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/_/351/207/215/345/201/232/350/277/233/345/272/246.md +37 -0
  364. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/_/351/237/263/350/275/250_/350/211/272/346/234/257/345/217/262/351/200/237/351/200/232.md +135 -0
  365. package/studio/app.js +380 -0
  366. package/studio/assert-panel-permit.mjs +67 -0
  367. package/studio/composer.css +465 -0
  368. package/studio/composer.js +480 -0
  369. package/studio/index.html +97 -0
  370. package/studio/optimize-prompt.mjs +84 -0
  371. package/studio/optimize-prompt.test.mjs +47 -0
  372. package/studio/recipes.mjs +87 -0
  373. package/studio/select-menu.js +450 -0
  374. package/studio/styles.css +71 -0
  375. package/studio/to-english-prompt.mjs +149 -0
  376. package/studio/to-english-prompt.test.mjs +72 -0
  377. package/README.md +0 -3
package/lib/host.mjs ADDED
@@ -0,0 +1,754 @@
1
+ /**
2
+ * Minimal HTTP host shared by agent packages.
3
+ * Env: PANEL_PORT, PANEL_WORKSPACE, PANEL_AGENT_ID, ENGINE_ROOT,
4
+ * PANEL_CALLBACK_URL, PANEL_CALLBACK_TOKEN, PANEL_CAN_EXPORT, PANEL_PRODUCT
5
+ */
6
+ import fs from 'node:fs';
7
+ import http from 'node:http';
8
+ import path from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { spawn } from 'node:child_process';
11
+ import crypto from 'node:crypto';
12
+ import { optimizePrompt } from '../studio/optimize-prompt.mjs';
13
+
14
+ export function env() {
15
+ return {
16
+ port: Number(process.env.PANEL_PORT || 0),
17
+ workspace: process.env.PANEL_WORKSPACE || path.join(process.cwd(), 'workspace'),
18
+ agentId: process.env.PANEL_AGENT_ID || 'agent',
19
+ version: process.env.PANEL_AGENT_VERSION || '0.0.0',
20
+ engineRoot: process.env.ENGINE_ROOT || (fs.existsSync(path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'engine'))
21
+ ? path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'engine')
22
+ : ''),
23
+ runtime: process.env.PANEL_RUNTIME || '',
24
+ callbackUrl: process.env.PANEL_CALLBACK_URL || '',
25
+ callbackToken: process.env.PANEL_CALLBACK_TOKEN || '',
26
+ canExport: process.env.PANEL_CAN_EXPORT !== '0',
27
+ member: process.env.PANEL_MEMBER === '1',
28
+ product: process.env.PANEL_PRODUCT || '',
29
+ };
30
+ }
31
+
32
+ export function ensureDir(dir) {
33
+ fs.mkdirSync(dir, { recursive: true });
34
+ return dir;
35
+ }
36
+
37
+ export function readJson(file, fallback = null) {
38
+ try {
39
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
40
+ } catch {
41
+ return fallback;
42
+ }
43
+ }
44
+
45
+ export function writeJson(file, data) {
46
+ ensureDir(path.dirname(file));
47
+ fs.writeFileSync(file, JSON.stringify(data, null, 2));
48
+ }
49
+
50
+ /** 旧任务只存了短标题时,从工程 brief 补回完整需求,方便点选回填。 */
51
+ function fillFromBrief(job) {
52
+ if (!job || job.prompt || job.brief) return job;
53
+ const dirs = [];
54
+ if (job.projectPath) dirs.push(job.projectPath);
55
+ const artifact = String(job.artifact || '');
56
+ if (artifact && !artifact.toLowerCase().endsWith('.mp4')) dirs.push(artifact);
57
+ for (const dir of dirs) {
58
+ const data = readJson(path.join(dir, 'brief.json'), null);
59
+ if (!data || typeof data !== 'object') continue;
60
+ const prompt = String(data.prompt || data.brief || '').trim();
61
+ if (!prompt) continue;
62
+ job.prompt = prompt;
63
+ if (job.look == null && data.look != null) job.look = data.look;
64
+ if (!job.size && data.size) job.size = data.size;
65
+ if (job.pace == null && data.pace != null) job.pace = data.pace;
66
+ if (!job.style && data.style) job.style = data.style;
67
+ if (!job.styleName && data.styleName) job.styleName = data.styleName;
68
+ if (job.storyboard == null && typeof data.storyboard === 'boolean') job.storyboard = data.storyboard;
69
+ if (!('durationSec' in job) && (data.durationSec != null || data.durationMode === 'auto')) {
70
+ job.durationSec = data.durationSec ?? null;
71
+ }
72
+ if (!job.screen && data.screen) job.screen = data.screen;
73
+ if (!job.styleId && data.styleId) job.styleId = data.styleId;
74
+ if (!job.urls && Array.isArray(data.urls)) job.urls = data.urls;
75
+ break;
76
+ }
77
+ return job;
78
+ }
79
+
80
+ export function sendJson(res, status, body) {
81
+ const data = JSON.stringify(body);
82
+ res.writeHead(status, {
83
+ 'content-type': 'application/json; charset=utf-8',
84
+ 'content-length': Buffer.byteLength(data),
85
+ 'cache-control': 'no-store',
86
+ 'access-control-allow-origin': '*',
87
+ });
88
+ res.end(data);
89
+ }
90
+
91
+ export async function readBody(req, limit = 32 * 1024 * 1024) {
92
+ const chunks = [];
93
+ let size = 0;
94
+ for await (const chunk of req) {
95
+ size += chunk.length;
96
+ if (size > limit) throw Object.assign(new Error('body too large'), { status: 413 });
97
+ chunks.push(chunk);
98
+ }
99
+ return Buffer.concat(chunks);
100
+ }
101
+
102
+ export async function readJsonBody(req) {
103
+ // 20MB 原文件 base64 后约 27MB,留余量给文案和字段
104
+ let buf;
105
+ try {
106
+ buf = await readBody(req, 30 * 1024 * 1024);
107
+ } catch (error) {
108
+ if (error.status === 413) {
109
+ throw Object.assign(new Error('附件超过 20MB,请压缩或裁短后再上传'), { status: 413 });
110
+ }
111
+ throw error;
112
+ }
113
+ if (!buf.length) return {};
114
+ return JSON.parse(buf.toString('utf8'));
115
+ }
116
+
117
+ export function mime(file) {
118
+ if (file.endsWith('.html')) return 'text/html; charset=utf-8';
119
+ if (file.endsWith('.js')) return 'text/javascript; charset=utf-8';
120
+ if (file.endsWith('.css')) return 'text/css; charset=utf-8';
121
+ if (file.endsWith('.json')) return 'application/json; charset=utf-8';
122
+ if (file.endsWith('.svg')) return 'image/svg+xml';
123
+ if (file.endsWith('.png')) return 'image/png';
124
+ if (file.endsWith('.jpg') || file.endsWith('.jpeg')) return 'image/jpeg';
125
+ if (file.endsWith('.webp')) return 'image/webp';
126
+ if (file.endsWith('.mp4')) return 'video/mp4';
127
+ if (file.endsWith('.md')) return 'text/markdown; charset=utf-8';
128
+ return 'application/octet-stream';
129
+ }
130
+
131
+ export function serveStatic(res, file) {
132
+ if (!fs.existsSync(file) || fs.statSync(file).isDirectory()) {
133
+ res.writeHead(404, { 'content-type': 'text/plain', 'access-control-allow-origin': '*' });
134
+ res.end('not found');
135
+ return;
136
+ }
137
+ res.writeHead(200, {
138
+ 'content-type': mime(file),
139
+ 'cache-control': 'no-store',
140
+ 'access-control-allow-origin': '*',
141
+ });
142
+ fs.createReadStream(file).pipe(res);
143
+ }
144
+
145
+ /** 把本机成片按 Range 流给浏览器,播放器才能拖动进度。 */
146
+ export function serveLocalFile(req, res, file) {
147
+ if (!file || !fs.existsSync(file) || !fs.statSync(file).isFile()) {
148
+ res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8', 'access-control-allow-origin': '*' });
149
+ res.end('not found');
150
+ return;
151
+ }
152
+ const size = fs.statSync(file).size;
153
+ const type = mime(file);
154
+ const common = {
155
+ 'content-type': type,
156
+ 'accept-ranges': 'bytes',
157
+ 'cache-control': 'no-store',
158
+ 'access-control-allow-origin': '*',
159
+ };
160
+ const range = String(req.headers.range || '');
161
+ const match = /^bytes=(\d*)-(\d*)$/.exec(range);
162
+ if (match) {
163
+ const hasStart = match[1] !== '';
164
+ const hasEnd = match[2] !== '';
165
+ let start = hasStart ? Number(match[1]) : 0;
166
+ let end = hasEnd ? Number(match[2]) : size - 1;
167
+ if (!hasStart && hasEnd) {
168
+ const suffix = Number(match[2]);
169
+ start = Number.isFinite(suffix) && suffix > 0 ? Math.max(0, size - suffix) : size;
170
+ end = size - 1;
171
+ }
172
+ if (!Number.isFinite(start) || !Number.isFinite(end) || start < 0 || start >= size || end < start) {
173
+ res.writeHead(416, { ...common, 'content-range': `bytes */${size}` });
174
+ res.end();
175
+ return;
176
+ }
177
+ end = Math.min(end, size - 1);
178
+ res.writeHead(206, {
179
+ ...common,
180
+ 'content-length': end - start + 1,
181
+ 'content-range': `bytes ${start}-${end}/${size}`,
182
+ });
183
+ fs.createReadStream(file, { start, end }).pipe(res);
184
+ return;
185
+ }
186
+ res.writeHead(200, { ...common, 'content-length': size });
187
+ fs.createReadStream(file).pipe(res);
188
+ }
189
+
190
+ export class JobStore {
191
+ constructor(workspace) {
192
+ this.dir = ensureDir(path.join(workspace, 'jobs'));
193
+ this.file = path.join(this.dir, 'index.json');
194
+ this.jobs = readJson(this.file, { jobs: [] }).jobs;
195
+ }
196
+
197
+ save() {
198
+ writeJson(this.file, { jobs: this.jobs });
199
+ }
200
+
201
+ list() {
202
+ return [...this.jobs].sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1));
203
+ }
204
+
205
+ get(id) {
206
+ return this.jobs.find((j) => j.id === id) || null;
207
+ }
208
+
209
+ create(partial) {
210
+ const job = {
211
+ id: crypto.randomUUID(),
212
+ status: 'queued',
213
+ progress: 0,
214
+ message: 'queued',
215
+ createdAt: new Date().toISOString(),
216
+ ...partial,
217
+ };
218
+ this.jobs.unshift(job);
219
+ this.save();
220
+ return job;
221
+ }
222
+
223
+ update(id, patch) {
224
+ const job = this.get(id);
225
+ if (!job) return null;
226
+ Object.assign(job, patch);
227
+ this.save();
228
+ return job;
229
+ }
230
+
231
+ remove(id) {
232
+ const before = this.jobs.length;
233
+ this.jobs = this.jobs.filter((j) => j.id !== id);
234
+ if (this.jobs.length === before) return false;
235
+ this.save();
236
+ return true;
237
+ }
238
+ }
239
+
240
+ export async function reportProgress(cfg, payload) {
241
+ if (!cfg.callbackUrl || !cfg.callbackToken) return null;
242
+ try {
243
+ const res = await fetch(cfg.callbackUrl, {
244
+ method: 'POST',
245
+ headers: { 'content-type': 'application/json', 'x-panel-token': cfg.callbackToken },
246
+ body: JSON.stringify({ ...payload, agentId: cfg.agentId, token: cfg.callbackToken }),
247
+ });
248
+ return await res.json().catch(() => null);
249
+ } catch {
250
+ return null;
251
+ }
252
+ }
253
+
254
+ /** Ask panel host to open a DSH conversation with this Agent's skill + prompt. */
255
+ export async function requestKickoff(cfg, payload) {
256
+ if (!cfg.callbackUrl || !cfg.callbackToken) return null;
257
+ const kickoffUrl = String(cfg.callbackUrl).replace(/\/agents\/progress\/?$/, '/agents/kickoff');
258
+ try {
259
+ const res = await fetch(kickoffUrl, {
260
+ method: 'POST',
261
+ headers: { 'content-type': 'application/json', 'x-panel-token': cfg.callbackToken },
262
+ body: JSON.stringify({
263
+ ...payload,
264
+ agentId: cfg.agentId,
265
+ token: cfg.callbackToken,
266
+ }),
267
+ });
268
+ return await res.json().catch(() => null);
269
+ } catch {
270
+ return null;
271
+ }
272
+ }
273
+
274
+ /** Mint panel Run Permit into projectDir (required before DSH creation). */
275
+ export async function requestRunPermit(cfg, { projectPath, jobId }) {
276
+ if (!cfg.callbackUrl || !cfg.callbackToken) return null;
277
+ const url = String(cfg.callbackUrl).replace(/\/agents\/progress\/?$/, '/agents/run-permit');
278
+ try {
279
+ const res = await fetch(url, {
280
+ method: 'POST',
281
+ headers: { 'content-type': 'application/json', 'x-panel-token': cfg.callbackToken },
282
+ body: JSON.stringify({
283
+ agentId: cfg.agentId,
284
+ token: cfg.callbackToken,
285
+ projectPath,
286
+ jobId,
287
+ }),
288
+ });
289
+ return await res.json().catch(() => null);
290
+ } catch {
291
+ return null;
292
+ }
293
+ }
294
+
295
+ /** Seed packaged skill into project so cwd-local DSH discovery finds it. */
296
+ export function seedSkillIntoProject(projectDir, packageRoot, skillId) {
297
+ const src = path.join(packageRoot, 'skills', skillId, 'SKILL.md');
298
+ if (!projectDir || !fs.existsSync(src)) return null;
299
+ const destDir = ensureDir(path.join(projectDir, '.agents', 'skills', skillId));
300
+ const dest = path.join(destDir, 'SKILL.md');
301
+ fs.copyFileSync(src, dest);
302
+ return dest;
303
+ }
304
+
305
+ /** 分段和无声中间片不能当成片。成片改名后(ep55.mp4 → ep55-master.mp4)要能从同目录找回。 */
306
+ export function isIntermediateMp4(filePath) {
307
+ const base = path.basename(String(filePath || '')).toLowerCase();
308
+ if (base === 'merged.mp4' || /^seg[-_]\d+\.mp4$/.test(base)) return true;
309
+ if (/^draft([-.]|$)/.test(base)) return true;
310
+ const parts = String(filePath || '').split(/[/\\]/).map((part) => part.toLowerCase());
311
+ if (parts.includes('build') || parts.includes('segments')) return true;
312
+ return base === 'video.mp4' && parts.at(-2) === 'out';
313
+ }
314
+
315
+ /** 记录里的文件被删掉或改名时,在同一目录找「原名开头」的现片。 */
316
+ export function rescueMissingFilm(filePath) {
317
+ const file = String(filePath || '');
318
+ if (!file) return '';
319
+ if (fs.existsSync(file) && !isIntermediateMp4(file)) return file;
320
+ const dir = path.dirname(file);
321
+ if (!fs.existsSync(dir)) return '';
322
+ const stem = path.basename(file, path.extname(file)).toLowerCase();
323
+ if (!stem) return '';
324
+ let best = null;
325
+ let names = [];
326
+ try { names = fs.readdirSync(dir); } catch { return ''; }
327
+ for (const name of names) {
328
+ if (!name.toLowerCase().endsWith('.mp4')) continue;
329
+ if (!name.toLowerCase().startsWith(stem)) continue;
330
+ const full = path.join(dir, name);
331
+ if (isIntermediateMp4(full)) continue;
332
+ let st;
333
+ try { st = fs.statSync(full); } catch { continue; }
334
+ if (!st.isFile()) continue;
335
+ if (!best || st.mtimeMs > best.mtimeMs) best = { path: full, mtimeMs: st.mtimeMs, size: st.size };
336
+ }
337
+ return best ? best.path : '';
338
+ }
339
+
340
+ function collectMp4(rootDir, maxDepth) {
341
+ const found = [];
342
+ if (!rootDir || !fs.existsSync(rootDir)) return found;
343
+ const walk = (dir, depth) => {
344
+ if (depth > maxDepth) return;
345
+ let entries;
346
+ try {
347
+ entries = fs.readdirSync(dir, { withFileTypes: true });
348
+ } catch {
349
+ return;
350
+ }
351
+ for (const ent of entries) {
352
+ if (ent.name === 'node_modules' || ent.name === '.git' || ent.name === '.venv') continue;
353
+ const full = path.join(dir, ent.name);
354
+ if (ent.isDirectory()) walk(full, depth + 1);
355
+ else if (ent.isFile() && /\.mp4$/i.test(ent.name)) {
356
+ try {
357
+ const st = fs.statSync(full);
358
+ found.push({ path: full, mtime: st.mtimeMs, size: st.size });
359
+ } catch {
360
+ /* skip */
361
+ }
362
+ }
363
+ }
364
+ };
365
+ walk(rootDir, 0);
366
+ return found;
367
+ }
368
+
369
+ function pickFilm(found) {
370
+ if (!found.length) return null;
371
+ found.sort((a, b) => {
372
+ const ai = isIntermediateMp4(a.path) ? 1 : 0;
373
+ const bi = isIntermediateMp4(b.path) ? 1 : 0;
374
+ if (ai !== bi) return ai - bi;
375
+ return b.mtime - a.mtime;
376
+ });
377
+ const best = found[0];
378
+ if (!best || isIntermediateMp4(best.path)) return null;
379
+ return best;
380
+ }
381
+
382
+ /** Newest deliverable MP4. Root-level `<name>.mp4` wins over `out/video.mp4`. */
383
+ export function findLatestMp4(rootDir, { maxDepth = 6, sinceMs = 0 } = {}) {
384
+ const since = Number(sinceMs) || 0;
385
+ const found = collectMp4(rootDir, maxDepth).filter((hit) => !since || hit.mtime >= since);
386
+ return pickFilm(found);
387
+ }
388
+
389
+ /** roots: directory string, or { dir, sinceMs, maxDepth }. */
390
+ export function locateFilm(roots) {
391
+ const found = [];
392
+ for (const root of roots || []) {
393
+ const dir = typeof root === 'string' ? root : root?.dir;
394
+ const sinceMs = typeof root === 'string' ? 0 : Number(root?.sinceMs) || 0;
395
+ const maxDepth = typeof root === 'string' ? 6 : root?.maxDepth || 6;
396
+ found.push(...collectMp4(dir, maxDepth).filter((hit) => !sinceMs || hit.mtime >= sinceMs));
397
+ }
398
+ return pickFilm(found);
399
+ }
400
+
401
+ export function locateJobFilm(job, cfg) {
402
+ if (!job) return null;
403
+ const roots = Array.isArray(job.filmRoots) && job.filmRoots.length
404
+ ? [...job.filmRoots]
405
+ : [];
406
+ const dir = job.projectPath || (job.artifact && !String(job.artifact).toLowerCase().endsWith('.mp4') ? job.artifact : '');
407
+ if (dir && !roots.some((root) => (typeof root === 'string' ? root : root?.dir) === dir)) {
408
+ roots.unshift({ dir, sinceMs: 0 });
409
+ }
410
+ const sinceMs = Date.parse(job.createdAt);
411
+ if (cfg?.engineRoot && Number.isFinite(sinceMs)) {
412
+ const engineFilms = path.join(cfg.engineRoot, 'films');
413
+ if (!roots.some((root) => (typeof root === 'string' ? root : root?.dir) === engineFilms)) {
414
+ roots.push({ dir: engineFilms, sinceMs });
415
+ }
416
+ }
417
+ return locateFilm(roots);
418
+ }
419
+
420
+ /** Poll until an MP4 appears; updates job + panel progress. */
421
+ export function watchForFilm(cfg, jobs, jobId, {
422
+ projectPath,
423
+ title,
424
+ intervalMs = 4000,
425
+ timeoutMs = 2 * 60 * 60 * 1000,
426
+ } = {}) {
427
+ const started = Date.now();
428
+ const tick = async () => {
429
+ const job = jobs.get(jobId);
430
+ if (!job || job.status === 'failed' || job.status === 'cancelled') return;
431
+ const current = String(job.outputMp4 || '');
432
+ if (current && fs.existsSync(current) && !isIntermediateMp4(current)) return;
433
+ if (current && !fs.existsSync(current)) {
434
+ const rescued = rescueMissingFilm(current);
435
+ if (rescued) {
436
+ const message = `成片已就绪 · ${path.basename(rescued)}`;
437
+ const size = fs.statSync(rescued).size;
438
+ jobs.update(jobId, {
439
+ status: 'done',
440
+ progress: 100,
441
+ phase: 'film',
442
+ message,
443
+ outputMp4: rescued,
444
+ artifact: rescued,
445
+ filmBytes: size,
446
+ filmAt: new Date().toISOString(),
447
+ });
448
+ await reportProgress(cfg, {
449
+ taskId: jobs.get(jobId)?.panelTaskId,
450
+ title: title || jobs.get(jobId)?.title,
451
+ progress: 100,
452
+ message,
453
+ done: true,
454
+ artifact: rescued,
455
+ });
456
+ return;
457
+ }
458
+ }
459
+ const hit = locateJobFilm({ ...job, projectPath: projectPath || job.projectPath, artifact: job.artifact }, cfg);
460
+ if (hit && !isIntermediateMp4(hit.path)) {
461
+ const message = `成片已就绪 · ${path.basename(hit.path)}`;
462
+ jobs.update(jobId, {
463
+ status: 'done',
464
+ progress: 100,
465
+ phase: 'film',
466
+ message,
467
+ outputMp4: hit.path,
468
+ artifact: hit.path,
469
+ filmBytes: hit.size,
470
+ filmAt: new Date().toISOString(),
471
+ });
472
+ await reportProgress(cfg, {
473
+ taskId: jobs.get(jobId)?.panelTaskId,
474
+ title: title || jobs.get(jobId)?.title,
475
+ progress: 100,
476
+ message,
477
+ done: true,
478
+ artifact: hit.path,
479
+ });
480
+ return;
481
+ }
482
+ if (Date.now() - started > timeoutMs) {
483
+ if (hit) {
484
+ const message = `成片已就绪 · ${path.basename(hit.path)}`;
485
+ jobs.update(jobId, {
486
+ status: 'done',
487
+ progress: 100,
488
+ phase: 'film',
489
+ message,
490
+ outputMp4: hit.path,
491
+ artifact: hit.path,
492
+ filmBytes: hit.size,
493
+ filmAt: new Date().toISOString(),
494
+ });
495
+ await reportProgress(cfg, {
496
+ taskId: jobs.get(jobId)?.panelTaskId,
497
+ title: title || jobs.get(jobId)?.title,
498
+ progress: 100,
499
+ message,
500
+ done: true,
501
+ artifact: hit.path,
502
+ });
503
+ } else {
504
+ jobs.update(jobId, {
505
+ status: 'done',
506
+ progress: 100,
507
+ phase: 'stalled',
508
+ message: '等待成片超时 · 可打开工程目录或 DSH 会话查看',
509
+ });
510
+ await reportProgress(cfg, {
511
+ taskId: jobs.get(jobId)?.panelTaskId,
512
+ progress: 100,
513
+ message: '等待成片超时',
514
+ done: true,
515
+ artifact: projectPath,
516
+ });
517
+ }
518
+ return;
519
+ }
520
+ if (job.status === 'running' || job.phase === 'dsh') {
521
+ jobs.update(jobId, {
522
+ phase: 'dsh',
523
+ message: job.kickoffOk === false
524
+ ? (job.message || '等待手动开拍 / 成片')
525
+ : 'DSH 创作中 · 完成后将自动显示成片路径',
526
+ progress: Math.min(90, Math.max(Number(job.progress) || 50, 50)),
527
+ });
528
+ await reportProgress(cfg, {
529
+ taskId: jobs.get(jobId)?.panelTaskId,
530
+ progress: jobs.get(jobId)?.progress ?? 55,
531
+ message: jobs.get(jobId)?.message,
532
+ });
533
+ }
534
+ setTimeout(() => void tick(), intervalMs);
535
+ };
536
+ setTimeout(() => void tick(), intervalMs);
537
+ }
538
+
539
+ export function runCommand(command, args, opts = {}) {
540
+ return new Promise((resolve, reject) => {
541
+ const child = spawn(command, args, {
542
+ cwd: opts.cwd,
543
+ env: { ...process.env, ...(opts.env || {}) },
544
+ shell: opts.shell ?? false,
545
+ });
546
+ let stdout = '';
547
+ let stderr = '';
548
+ child.stdout?.on('data', (d) => {
549
+ stdout += d.toString();
550
+ opts.onStdout?.(d.toString());
551
+ });
552
+ child.stderr?.on('data', (d) => {
553
+ stderr += d.toString();
554
+ opts.onStderr?.(d.toString());
555
+ });
556
+ child.on('error', reject);
557
+ child.on('close', (code) => {
558
+ if (code === 0) resolve({ code, stdout, stderr });
559
+ else reject(Object.assign(new Error(stderr || `exit ${code}`), { code, stdout, stderr }));
560
+ });
561
+ if (opts.signal) {
562
+ opts.signal.addEventListener('abort', () => {
563
+ child.kill('SIGTERM');
564
+ reject(new Error('aborted'));
565
+ });
566
+ }
567
+ });
568
+ }
569
+
570
+ export function packageRoot(importMetaUrl) {
571
+ return path.resolve(path.dirname(fileURLToPath(importMetaUrl)), '..');
572
+ }
573
+
574
+ export function createAgentServer({ root, studioDir, api }) {
575
+ const cfg = env();
576
+ ensureDir(cfg.workspace);
577
+ const jobs = new JobStore(cfg.workspace);
578
+ const studio = studioDir || path.join(root, 'studio');
579
+
580
+ // 进程重启后:已有成片则收口;否则恢复 watch,避免面板/Studio 任务永远「处理中」
581
+ for (const j of jobs.list()) {
582
+ const filmMissing = j.outputMp4 && !fs.existsSync(j.outputMp4);
583
+ const filmBad = j.outputMp4 && isIntermediateMp4(j.outputMp4);
584
+ if (filmMissing || filmBad || (j.status === 'done' && !j.outputMp4)) {
585
+ const rescued = filmMissing ? rescueMissingFilm(j.outputMp4) : '';
586
+ const late = rescued
587
+ ? { path: rescued, size: fs.statSync(rescued).size }
588
+ : locateJobFilm(j, cfg);
589
+ if (late && late.path && fs.existsSync(late.path) && !isIntermediateMp4(late.path)) {
590
+ jobs.update(j.id, {
591
+ status: 'done',
592
+ progress: 100,
593
+ phase: 'film',
594
+ message: `成片已就绪 · ${path.basename(late.path)}`,
595
+ outputMp4: late.path,
596
+ artifact: late.path,
597
+ filmBytes: late.size,
598
+ filmAt: new Date().toISOString(),
599
+ });
600
+ continue;
601
+ }
602
+ if (j.status === 'done') continue;
603
+ }
604
+ const waiting = j.status === 'running' || (j.outputMp4 && isIntermediateMp4(j.outputMp4));
605
+ if (!waiting) continue;
606
+ const projectPath = j.projectPath || (j.artifact && !String(j.artifact).endsWith('.mp4') ? j.artifact : null);
607
+ const hit = locateJobFilm(j, cfg);
608
+ if (hit && !isIntermediateMp4(hit.path)) {
609
+ jobs.update(j.id, {
610
+ status: 'done',
611
+ progress: 100,
612
+ phase: 'film',
613
+ message: `成片已就绪 · ${path.basename(hit.path)}`,
614
+ outputMp4: hit.path,
615
+ artifact: hit.path,
616
+ filmBytes: hit.size,
617
+ filmAt: new Date().toISOString(),
618
+ });
619
+ void reportProgress(cfg, {
620
+ taskId: j.panelTaskId,
621
+ title: j.title,
622
+ progress: 100,
623
+ message: `成片已就绪 · ${path.basename(hit.path)}`,
624
+ done: true,
625
+ artifact: hit.path,
626
+ });
627
+ } else if (projectPath && fs.existsSync(projectPath)) {
628
+ jobs.update(j.id, {
629
+ phase: 'dsh',
630
+ message: '进程已恢复 · 继续等待成片',
631
+ progress: Math.min(90, Math.max(Number(j.progress) || 50, 50)),
632
+ });
633
+ watchForFilm(cfg, jobs, j.id, { projectPath, title: j.title });
634
+ } else {
635
+ jobs.update(j.id, {
636
+ status: 'failed',
637
+ phase: 'orphaned',
638
+ message: '进程中断 · 无法恢复监视(工程目录缺失)',
639
+ progress: Number(j.progress) || 0,
640
+ });
641
+ void reportProgress(cfg, {
642
+ taskId: j.panelTaskId,
643
+ progress: Number(j.progress) || 0,
644
+ message: '进程中断 · 任务已标记失败',
645
+ failed: true,
646
+ error: 'agent process restarted without project path',
647
+ });
648
+ }
649
+ }
650
+
651
+ const server = http.createServer(async (req, res) => {
652
+ try {
653
+ if (req.method === 'OPTIONS') {
654
+ res.writeHead(204, {
655
+ 'access-control-allow-origin': '*',
656
+ 'access-control-allow-methods': 'GET,POST,DELETE,OPTIONS',
657
+ 'access-control-allow-headers': 'content-type,x-panel-token',
658
+ });
659
+ return res.end();
660
+ }
661
+ const url = new URL(req.url || '/', 'http://local');
662
+ const pathname = url.pathname;
663
+
664
+ if (pathname === '/api/health') {
665
+ return sendJson(res, 200, {
666
+ ok: true,
667
+ agentId: cfg.agentId,
668
+ version: cfg.version,
669
+ product: cfg.product,
670
+ canExport: cfg.canExport,
671
+ member: cfg.member === true,
672
+ engineRoot: cfg.engineRoot || null,
673
+ enginePresent: !!(cfg.engineRoot && fs.existsSync(cfg.engineRoot)),
674
+ workspace: cfg.workspace,
675
+ });
676
+ }
677
+
678
+ if (pathname === '/api/jobs' && req.method === 'GET') {
679
+ return sendJson(res, 200, { jobs: jobs.list().map(fillFromBrief) });
680
+ }
681
+ if (req.method === 'GET' && /^\/api\/jobs\/[^/]+\/film$/.test(pathname)) {
682
+ const id = decodeURIComponent(pathname.slice('/api/jobs/'.length, -'/film'.length));
683
+ const job = jobs.get(id);
684
+ let file = job ? String(job.outputMp4 || '') : '';
685
+ if (job && (!file || !fs.existsSync(file) || isIntermediateMp4(file))) {
686
+ const rescued = file && !fs.existsSync(file) ? rescueMissingFilm(file) : '';
687
+ const late = rescued
688
+ ? { path: rescued, size: fs.statSync(rescued).size }
689
+ : locateJobFilm(job, cfg);
690
+ if (late && late.path && fs.existsSync(late.path) && !isIntermediateMp4(late.path)) {
691
+ jobs.update(job.id, {
692
+ status: 'done',
693
+ progress: 100,
694
+ phase: 'film',
695
+ message: `成片已就绪 · ${path.basename(late.path)}`,
696
+ outputMp4: late.path,
697
+ artifact: late.path,
698
+ filmBytes: late.size,
699
+ filmAt: new Date().toISOString(),
700
+ });
701
+ file = late.path;
702
+ }
703
+ }
704
+ if (!file || !file.toLowerCase().endsWith('.mp4') || !fs.existsSync(file)) return sendJson(res, 404, { error: '成片还没有' });
705
+ return serveLocalFile(req, res, file);
706
+ }
707
+ if (pathname.startsWith('/api/jobs/') && req.method === 'GET') {
708
+ const job = jobs.get(pathname.slice('/api/jobs/'.length));
709
+ if (!job) return sendJson(res, 404, { error: 'job not found' });
710
+ return sendJson(res, 200, { job: fillFromBrief(job) });
711
+ }
712
+ if (pathname.startsWith('/api/jobs/') && req.method === 'DELETE') {
713
+ const id = pathname.slice('/api/jobs/'.length);
714
+ if (!id) return sendJson(res, 400, { error: 'job id required' });
715
+ const ok = jobs.remove(id);
716
+ if (!ok) return sendJson(res, 404, { error: 'job not found' });
717
+ return sendJson(res, 200, { ok: true, id });
718
+ }
719
+
720
+ if (pathname === '/api/optimize-prompt' && req.method === 'POST') {
721
+ if (cfg.member !== true) {
722
+ sendJson(res, 403, { error: '会员专属,请先开通会员' });
723
+ return;
724
+ }
725
+ try {
726
+ const body = await readJsonBody(req);
727
+ sendJson(res, 200, optimizePrompt(body));
728
+ } catch (error) {
729
+ const message = error instanceof SyntaxError ? '请求无法读取' : (error.message || '无法优化提示词');
730
+ sendJson(res, error.status || 400, { error: message });
731
+ }
732
+ return;
733
+ }
734
+
735
+ if (api) {
736
+ const handled = await api({ req, res, url, pathname, cfg, jobs, root, sendJson, readJsonBody, readBody });
737
+ if (handled) return;
738
+ }
739
+
740
+ let filePath = pathname === '/' ? path.join(studio, 'index.html') : path.join(studio, pathname.replace(/^\//, ''));
741
+ if (!filePath.startsWith(studio)) return sendJson(res, 403, { error: 'forbidden' });
742
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) filePath = path.join(filePath, 'index.html');
743
+ return serveStatic(res, filePath);
744
+ } catch (error) {
745
+ sendJson(res, error.status || 500, { error: error.message || String(error) });
746
+ }
747
+ });
748
+
749
+ if (!cfg.port) throw new Error('PANEL_PORT is required');
750
+ server.listen(cfg.port, '127.0.0.1', () => {
751
+ console.log(`[${cfg.agentId}] studio on http://127.0.0.1:${cfg.port}`);
752
+ });
753
+ return { server, cfg, jobs };
754
+ }