@vibeframe/cli 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (420) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-lint.log +21 -0
  3. package/.turbo/turbo-test.log +689 -0
  4. package/dist/agent/adapters/claude.d.ts +15 -0
  5. package/dist/agent/adapters/claude.d.ts.map +1 -0
  6. package/dist/agent/adapters/claude.js +119 -0
  7. package/dist/agent/adapters/claude.js.map +1 -0
  8. package/dist/agent/adapters/gemini.d.ts +15 -0
  9. package/dist/agent/adapters/gemini.d.ts.map +1 -0
  10. package/dist/agent/adapters/gemini.js +132 -0
  11. package/dist/agent/adapters/gemini.js.map +1 -0
  12. package/dist/agent/adapters/index.d.ts +27 -0
  13. package/dist/agent/adapters/index.d.ts.map +1 -0
  14. package/dist/agent/adapters/index.js +38 -0
  15. package/dist/agent/adapters/index.js.map +1 -0
  16. package/dist/agent/adapters/ollama.d.ts +20 -0
  17. package/dist/agent/adapters/ollama.d.ts.map +1 -0
  18. package/dist/agent/adapters/ollama.js +186 -0
  19. package/dist/agent/adapters/ollama.js.map +1 -0
  20. package/dist/agent/adapters/openai.d.ts +15 -0
  21. package/dist/agent/adapters/openai.d.ts.map +1 -0
  22. package/dist/agent/adapters/openai.js +92 -0
  23. package/dist/agent/adapters/openai.js.map +1 -0
  24. package/dist/agent/adapters/xai.d.ts +15 -0
  25. package/dist/agent/adapters/xai.d.ts.map +1 -0
  26. package/dist/agent/adapters/xai.js +95 -0
  27. package/dist/agent/adapters/xai.js.map +1 -0
  28. package/dist/agent/index.d.ts +69 -0
  29. package/dist/agent/index.d.ts.map +1 -0
  30. package/dist/agent/index.js +180 -0
  31. package/dist/agent/index.js.map +1 -0
  32. package/dist/agent/memory/index.d.ts +70 -0
  33. package/dist/agent/memory/index.d.ts.map +1 -0
  34. package/dist/agent/memory/index.js +132 -0
  35. package/dist/agent/memory/index.js.map +1 -0
  36. package/dist/agent/prompts/system.d.ts +6 -0
  37. package/dist/agent/prompts/system.d.ts.map +1 -0
  38. package/dist/agent/prompts/system.js +103 -0
  39. package/dist/agent/prompts/system.js.map +1 -0
  40. package/dist/agent/tools/ai-editing.d.ts +15 -0
  41. package/dist/agent/tools/ai-editing.d.ts.map +1 -0
  42. package/dist/agent/tools/ai-editing.js +763 -0
  43. package/dist/agent/tools/ai-editing.js.map +1 -0
  44. package/dist/agent/tools/ai-generation.d.ts +13 -0
  45. package/dist/agent/tools/ai-generation.d.ts.map +1 -0
  46. package/dist/agent/tools/ai-generation.js +973 -0
  47. package/dist/agent/tools/ai-generation.js.map +1 -0
  48. package/dist/agent/tools/ai-pipeline.d.ts +14 -0
  49. package/dist/agent/tools/ai-pipeline.d.ts.map +1 -0
  50. package/dist/agent/tools/ai-pipeline.js +961 -0
  51. package/dist/agent/tools/ai-pipeline.js.map +1 -0
  52. package/dist/agent/tools/ai.d.ts +13 -0
  53. package/dist/agent/tools/ai.d.ts.map +1 -0
  54. package/dist/agent/tools/ai.js +19 -0
  55. package/dist/agent/tools/ai.js.map +1 -0
  56. package/dist/agent/tools/batch.d.ts +6 -0
  57. package/dist/agent/tools/batch.d.ts.map +1 -0
  58. package/dist/agent/tools/batch.js +383 -0
  59. package/dist/agent/tools/batch.js.map +1 -0
  60. package/dist/agent/tools/e2e.test.d.ts +26 -0
  61. package/dist/agent/tools/e2e.test.d.ts.map +1 -0
  62. package/dist/agent/tools/e2e.test.js +397 -0
  63. package/dist/agent/tools/e2e.test.js.map +1 -0
  64. package/dist/agent/tools/export.d.ts +6 -0
  65. package/dist/agent/tools/export.d.ts.map +1 -0
  66. package/dist/agent/tools/export.js +171 -0
  67. package/dist/agent/tools/export.js.map +1 -0
  68. package/dist/agent/tools/filesystem.d.ts +6 -0
  69. package/dist/agent/tools/filesystem.d.ts.map +1 -0
  70. package/dist/agent/tools/filesystem.js +212 -0
  71. package/dist/agent/tools/filesystem.js.map +1 -0
  72. package/dist/agent/tools/index.d.ts +65 -0
  73. package/dist/agent/tools/index.d.ts.map +1 -0
  74. package/dist/agent/tools/index.js +120 -0
  75. package/dist/agent/tools/index.js.map +1 -0
  76. package/dist/agent/tools/integration.test.d.ts +11 -0
  77. package/dist/agent/tools/integration.test.d.ts.map +1 -0
  78. package/dist/agent/tools/integration.test.js +659 -0
  79. package/dist/agent/tools/integration.test.js.map +1 -0
  80. package/dist/agent/tools/media.d.ts +6 -0
  81. package/dist/agent/tools/media.d.ts.map +1 -0
  82. package/dist/agent/tools/media.js +616 -0
  83. package/dist/agent/tools/media.js.map +1 -0
  84. package/dist/agent/tools/project.d.ts +6 -0
  85. package/dist/agent/tools/project.d.ts.map +1 -0
  86. package/dist/agent/tools/project.js +284 -0
  87. package/dist/agent/tools/project.js.map +1 -0
  88. package/dist/agent/tools/timeline.d.ts +6 -0
  89. package/dist/agent/tools/timeline.d.ts.map +1 -0
  90. package/dist/agent/tools/timeline.js +873 -0
  91. package/dist/agent/tools/timeline.js.map +1 -0
  92. package/dist/agent/types.d.ts +59 -0
  93. package/dist/agent/types.d.ts.map +1 -0
  94. package/dist/agent/types.js +5 -0
  95. package/dist/agent/types.js.map +1 -0
  96. package/dist/commands/agent.d.ts +21 -0
  97. package/dist/commands/agent.d.ts.map +1 -0
  98. package/dist/commands/agent.js +290 -0
  99. package/dist/commands/agent.js.map +1 -0
  100. package/dist/commands/ai-analyze.d.ts +106 -0
  101. package/dist/commands/ai-analyze.d.ts.map +1 -0
  102. package/dist/commands/ai-analyze.js +327 -0
  103. package/dist/commands/ai-analyze.js.map +1 -0
  104. package/dist/commands/ai-animated-caption.d.ts +64 -0
  105. package/dist/commands/ai-animated-caption.d.ts.map +1 -0
  106. package/dist/commands/ai-animated-caption.js +272 -0
  107. package/dist/commands/ai-animated-caption.js.map +1 -0
  108. package/dist/commands/ai-audio.d.ts +20 -0
  109. package/dist/commands/ai-audio.d.ts.map +1 -0
  110. package/dist/commands/ai-audio.js +808 -0
  111. package/dist/commands/ai-audio.js.map +1 -0
  112. package/dist/commands/ai-broll.d.ts +15 -0
  113. package/dist/commands/ai-broll.d.ts.map +1 -0
  114. package/dist/commands/ai-broll.js +406 -0
  115. package/dist/commands/ai-broll.js.map +1 -0
  116. package/dist/commands/ai-edit-cli.d.ts +14 -0
  117. package/dist/commands/ai-edit-cli.d.ts.map +1 -0
  118. package/dist/commands/ai-edit-cli.js +579 -0
  119. package/dist/commands/ai-edit-cli.js.map +1 -0
  120. package/dist/commands/ai-edit.d.ts +398 -0
  121. package/dist/commands/ai-edit.d.ts.map +1 -0
  122. package/dist/commands/ai-edit.js +1019 -0
  123. package/dist/commands/ai-edit.js.map +1 -0
  124. package/dist/commands/ai-fill-gaps.d.ts +14 -0
  125. package/dist/commands/ai-fill-gaps.d.ts.map +1 -0
  126. package/dist/commands/ai-fill-gaps.js +451 -0
  127. package/dist/commands/ai-fill-gaps.js.map +1 -0
  128. package/dist/commands/ai-helpers.d.ts +20 -0
  129. package/dist/commands/ai-helpers.d.ts.map +1 -0
  130. package/dist/commands/ai-helpers.js +59 -0
  131. package/dist/commands/ai-helpers.js.map +1 -0
  132. package/dist/commands/ai-highlights.d.ts +127 -0
  133. package/dist/commands/ai-highlights.d.ts.map +1 -0
  134. package/dist/commands/ai-highlights.js +1026 -0
  135. package/dist/commands/ai-highlights.js.map +1 -0
  136. package/dist/commands/ai-image.d.ts +34 -0
  137. package/dist/commands/ai-image.d.ts.map +1 -0
  138. package/dist/commands/ai-image.js +653 -0
  139. package/dist/commands/ai-image.js.map +1 -0
  140. package/dist/commands/ai-motion.d.ts +50 -0
  141. package/dist/commands/ai-motion.d.ts.map +1 -0
  142. package/dist/commands/ai-motion.js +271 -0
  143. package/dist/commands/ai-motion.js.map +1 -0
  144. package/dist/commands/ai-narrate.d.ts +66 -0
  145. package/dist/commands/ai-narrate.d.ts.map +1 -0
  146. package/dist/commands/ai-narrate.js +329 -0
  147. package/dist/commands/ai-narrate.js.map +1 -0
  148. package/dist/commands/ai-review.d.ts +57 -0
  149. package/dist/commands/ai-review.d.ts.map +1 -0
  150. package/dist/commands/ai-review.js +251 -0
  151. package/dist/commands/ai-review.js.map +1 -0
  152. package/dist/commands/ai-script-pipeline-cli.d.ts +9 -0
  153. package/dist/commands/ai-script-pipeline-cli.d.ts.map +1 -0
  154. package/dist/commands/ai-script-pipeline-cli.js +1494 -0
  155. package/dist/commands/ai-script-pipeline-cli.js.map +1 -0
  156. package/dist/commands/ai-script-pipeline.d.ts +259 -0
  157. package/dist/commands/ai-script-pipeline.d.ts.map +1 -0
  158. package/dist/commands/ai-script-pipeline.js +1027 -0
  159. package/dist/commands/ai-script-pipeline.js.map +1 -0
  160. package/dist/commands/ai-suggest-edit.d.ts +14 -0
  161. package/dist/commands/ai-suggest-edit.d.ts.map +1 -0
  162. package/dist/commands/ai-suggest-edit.js +220 -0
  163. package/dist/commands/ai-suggest-edit.js.map +1 -0
  164. package/dist/commands/ai-video-fx.d.ts +14 -0
  165. package/dist/commands/ai-video-fx.d.ts.map +1 -0
  166. package/dist/commands/ai-video-fx.js +395 -0
  167. package/dist/commands/ai-video-fx.js.map +1 -0
  168. package/dist/commands/ai-video.d.ts +15 -0
  169. package/dist/commands/ai-video.d.ts.map +1 -0
  170. package/dist/commands/ai-video.js +785 -0
  171. package/dist/commands/ai-video.js.map +1 -0
  172. package/dist/commands/ai-viral.d.ts +15 -0
  173. package/dist/commands/ai-viral.d.ts.map +1 -0
  174. package/dist/commands/ai-viral.js +519 -0
  175. package/dist/commands/ai-viral.js.map +1 -0
  176. package/dist/commands/ai-visual-fx.d.ts +14 -0
  177. package/dist/commands/ai-visual-fx.d.ts.map +1 -0
  178. package/dist/commands/ai-visual-fx.js +505 -0
  179. package/dist/commands/ai-visual-fx.js.map +1 -0
  180. package/dist/commands/ai.d.ts +38 -0
  181. package/dist/commands/ai.d.ts.map +1 -0
  182. package/dist/commands/ai.js +225 -0
  183. package/dist/commands/ai.js.map +1 -0
  184. package/dist/commands/ai.test.d.ts +2 -0
  185. package/dist/commands/ai.test.d.ts.map +1 -0
  186. package/dist/commands/ai.test.js +554 -0
  187. package/dist/commands/ai.test.js.map +1 -0
  188. package/dist/commands/analyze.d.ts +16 -0
  189. package/dist/commands/analyze.d.ts.map +1 -0
  190. package/dist/commands/analyze.js +247 -0
  191. package/dist/commands/analyze.js.map +1 -0
  192. package/dist/commands/audio.d.ts +18 -0
  193. package/dist/commands/audio.d.ts.map +1 -0
  194. package/dist/commands/audio.js +539 -0
  195. package/dist/commands/audio.js.map +1 -0
  196. package/dist/commands/batch.d.ts +3 -0
  197. package/dist/commands/batch.d.ts.map +1 -0
  198. package/dist/commands/batch.js +366 -0
  199. package/dist/commands/batch.js.map +1 -0
  200. package/dist/commands/batch.test.d.ts +2 -0
  201. package/dist/commands/batch.test.d.ts.map +1 -0
  202. package/dist/commands/batch.test.js +203 -0
  203. package/dist/commands/batch.test.js.map +1 -0
  204. package/dist/commands/detect.d.ts +3 -0
  205. package/dist/commands/detect.d.ts.map +1 -0
  206. package/dist/commands/detect.js +273 -0
  207. package/dist/commands/detect.js.map +1 -0
  208. package/dist/commands/doctor.d.ts +6 -0
  209. package/dist/commands/doctor.d.ts.map +1 -0
  210. package/dist/commands/doctor.js +191 -0
  211. package/dist/commands/doctor.js.map +1 -0
  212. package/dist/commands/edit-cmd.d.ts +26 -0
  213. package/dist/commands/edit-cmd.d.ts.map +1 -0
  214. package/dist/commands/edit-cmd.js +870 -0
  215. package/dist/commands/edit-cmd.js.map +1 -0
  216. package/dist/commands/export.d.ts +39 -0
  217. package/dist/commands/export.d.ts.map +1 -0
  218. package/dist/commands/export.js +730 -0
  219. package/dist/commands/export.js.map +1 -0
  220. package/dist/commands/generate.d.ts +25 -0
  221. package/dist/commands/generate.d.ts.map +1 -0
  222. package/dist/commands/generate.js +1885 -0
  223. package/dist/commands/generate.js.map +1 -0
  224. package/dist/commands/media.d.ts +3 -0
  225. package/dist/commands/media.d.ts.map +1 -0
  226. package/dist/commands/media.js +165 -0
  227. package/dist/commands/media.js.map +1 -0
  228. package/dist/commands/output.d.ts +45 -0
  229. package/dist/commands/output.d.ts.map +1 -0
  230. package/dist/commands/output.js +122 -0
  231. package/dist/commands/output.js.map +1 -0
  232. package/dist/commands/pipeline.d.ts +19 -0
  233. package/dist/commands/pipeline.d.ts.map +1 -0
  234. package/dist/commands/pipeline.js +345 -0
  235. package/dist/commands/pipeline.js.map +1 -0
  236. package/dist/commands/project.d.ts +3 -0
  237. package/dist/commands/project.d.ts.map +1 -0
  238. package/dist/commands/project.js +139 -0
  239. package/dist/commands/project.js.map +1 -0
  240. package/dist/commands/project.test.d.ts +2 -0
  241. package/dist/commands/project.test.d.ts.map +1 -0
  242. package/dist/commands/project.test.js +105 -0
  243. package/dist/commands/project.test.js.map +1 -0
  244. package/dist/commands/sanitize.d.ts +21 -0
  245. package/dist/commands/sanitize.d.ts.map +1 -0
  246. package/dist/commands/sanitize.js +56 -0
  247. package/dist/commands/sanitize.js.map +1 -0
  248. package/dist/commands/schema.d.ts +11 -0
  249. package/dist/commands/schema.d.ts.map +1 -0
  250. package/dist/commands/schema.js +101 -0
  251. package/dist/commands/schema.js.map +1 -0
  252. package/dist/commands/setup.d.ts +6 -0
  253. package/dist/commands/setup.d.ts.map +1 -0
  254. package/dist/commands/setup.js +440 -0
  255. package/dist/commands/setup.js.map +1 -0
  256. package/dist/commands/timeline.d.ts +3 -0
  257. package/dist/commands/timeline.d.ts.map +1 -0
  258. package/dist/commands/timeline.js +469 -0
  259. package/dist/commands/timeline.js.map +1 -0
  260. package/dist/commands/timeline.test.d.ts +2 -0
  261. package/dist/commands/timeline.test.d.ts.map +1 -0
  262. package/dist/commands/timeline.test.js +320 -0
  263. package/dist/commands/timeline.test.js.map +1 -0
  264. package/dist/commands/validate.d.ts +32 -0
  265. package/dist/commands/validate.d.ts.map +1 -0
  266. package/dist/commands/validate.js +63 -0
  267. package/dist/commands/validate.js.map +1 -0
  268. package/dist/config/config.test.d.ts +2 -0
  269. package/dist/config/config.test.d.ts.map +1 -0
  270. package/dist/config/config.test.js +164 -0
  271. package/dist/config/config.test.js.map +1 -0
  272. package/dist/config/index.d.ts +35 -0
  273. package/dist/config/index.d.ts.map +1 -0
  274. package/dist/config/index.js +101 -0
  275. package/dist/config/index.js.map +1 -0
  276. package/dist/config/schema.d.ts +43 -0
  277. package/dist/config/schema.d.ts.map +1 -0
  278. package/dist/config/schema.js +42 -0
  279. package/dist/config/schema.js.map +1 -0
  280. package/dist/engine/index.d.ts +3 -0
  281. package/dist/engine/index.d.ts.map +1 -0
  282. package/dist/engine/index.js +2 -0
  283. package/dist/engine/index.js.map +1 -0
  284. package/dist/engine/project.d.ts +84 -0
  285. package/dist/engine/project.d.ts.map +1 -0
  286. package/dist/engine/project.js +355 -0
  287. package/dist/engine/project.js.map +1 -0
  288. package/dist/engine/project.test.d.ts +2 -0
  289. package/dist/engine/project.test.d.ts.map +1 -0
  290. package/dist/engine/project.test.js +599 -0
  291. package/dist/engine/project.test.js.map +1 -0
  292. package/dist/index.d.ts +7 -0
  293. package/dist/index.d.ts.map +1 -0
  294. package/dist/index.js +131 -0
  295. package/dist/index.js.map +1 -0
  296. package/dist/utils/api-key.d.ts +36 -0
  297. package/dist/utils/api-key.d.ts.map +1 -0
  298. package/dist/utils/api-key.js +211 -0
  299. package/dist/utils/api-key.js.map +1 -0
  300. package/dist/utils/api-key.test.d.ts +2 -0
  301. package/dist/utils/api-key.test.d.ts.map +1 -0
  302. package/dist/utils/api-key.test.js +35 -0
  303. package/dist/utils/api-key.test.js.map +1 -0
  304. package/dist/utils/audio.d.ts +23 -0
  305. package/dist/utils/audio.d.ts.map +1 -0
  306. package/dist/utils/audio.js +79 -0
  307. package/dist/utils/audio.js.map +1 -0
  308. package/dist/utils/exec-safe.d.ts +22 -0
  309. package/dist/utils/exec-safe.d.ts.map +1 -0
  310. package/dist/utils/exec-safe.js +62 -0
  311. package/dist/utils/exec-safe.js.map +1 -0
  312. package/dist/utils/first-run.d.ts +13 -0
  313. package/dist/utils/first-run.d.ts.map +1 -0
  314. package/dist/utils/first-run.js +48 -0
  315. package/dist/utils/first-run.js.map +1 -0
  316. package/dist/utils/provider-resolver.d.ts +15 -0
  317. package/dist/utils/provider-resolver.d.ts.map +1 -0
  318. package/dist/utils/provider-resolver.js +42 -0
  319. package/dist/utils/provider-resolver.js.map +1 -0
  320. package/dist/utils/remotion.d.ts +210 -0
  321. package/dist/utils/remotion.d.ts.map +1 -0
  322. package/dist/utils/remotion.js +731 -0
  323. package/dist/utils/remotion.js.map +1 -0
  324. package/dist/utils/subtitle.d.ts +65 -0
  325. package/dist/utils/subtitle.d.ts.map +1 -0
  326. package/dist/utils/subtitle.js +135 -0
  327. package/dist/utils/subtitle.js.map +1 -0
  328. package/dist/utils/subtitle.test.d.ts +2 -0
  329. package/dist/utils/subtitle.test.d.ts.map +1 -0
  330. package/dist/utils/subtitle.test.js +175 -0
  331. package/dist/utils/subtitle.test.js.map +1 -0
  332. package/dist/utils/tty.d.ts +45 -0
  333. package/dist/utils/tty.d.ts.map +1 -0
  334. package/dist/utils/tty.js +172 -0
  335. package/dist/utils/tty.js.map +1 -0
  336. package/package.json +102 -0
  337. package/src/agent/adapters/claude.ts +143 -0
  338. package/src/agent/adapters/gemini.ts +159 -0
  339. package/src/agent/adapters/index.ts +61 -0
  340. package/src/agent/adapters/ollama.ts +231 -0
  341. package/src/agent/adapters/openai.ts +116 -0
  342. package/src/agent/adapters/xai.ts +119 -0
  343. package/src/agent/index.ts +251 -0
  344. package/src/agent/memory/index.ts +151 -0
  345. package/src/agent/prompts/system.ts +106 -0
  346. package/src/agent/tools/ai-editing.ts +845 -0
  347. package/src/agent/tools/ai-generation.ts +1073 -0
  348. package/src/agent/tools/ai-pipeline.ts +1055 -0
  349. package/src/agent/tools/ai.ts +21 -0
  350. package/src/agent/tools/batch.ts +429 -0
  351. package/src/agent/tools/e2e.test.ts +545 -0
  352. package/src/agent/tools/export.ts +184 -0
  353. package/src/agent/tools/filesystem.ts +237 -0
  354. package/src/agent/tools/index.ts +150 -0
  355. package/src/agent/tools/integration.test.ts +775 -0
  356. package/src/agent/tools/media.ts +697 -0
  357. package/src/agent/tools/project.ts +313 -0
  358. package/src/agent/tools/timeline.ts +951 -0
  359. package/src/agent/types.ts +68 -0
  360. package/src/commands/agent.ts +340 -0
  361. package/src/commands/ai-analyze.ts +429 -0
  362. package/src/commands/ai-animated-caption.ts +390 -0
  363. package/src/commands/ai-audio.ts +941 -0
  364. package/src/commands/ai-broll.ts +490 -0
  365. package/src/commands/ai-edit-cli.ts +658 -0
  366. package/src/commands/ai-edit.ts +1542 -0
  367. package/src/commands/ai-fill-gaps.ts +566 -0
  368. package/src/commands/ai-helpers.ts +65 -0
  369. package/src/commands/ai-highlights.ts +1303 -0
  370. package/src/commands/ai-image.ts +761 -0
  371. package/src/commands/ai-motion.ts +347 -0
  372. package/src/commands/ai-narrate.ts +451 -0
  373. package/src/commands/ai-review.ts +309 -0
  374. package/src/commands/ai-script-pipeline-cli.ts +1710 -0
  375. package/src/commands/ai-script-pipeline.ts +1365 -0
  376. package/src/commands/ai-suggest-edit.ts +264 -0
  377. package/src/commands/ai-video-fx.ts +445 -0
  378. package/src/commands/ai-video.ts +915 -0
  379. package/src/commands/ai-viral.ts +595 -0
  380. package/src/commands/ai-visual-fx.ts +601 -0
  381. package/src/commands/ai.test.ts +627 -0
  382. package/src/commands/ai.ts +307 -0
  383. package/src/commands/analyze.ts +282 -0
  384. package/src/commands/audio.ts +644 -0
  385. package/src/commands/batch.test.ts +279 -0
  386. package/src/commands/batch.ts +440 -0
  387. package/src/commands/detect.ts +329 -0
  388. package/src/commands/doctor.ts +237 -0
  389. package/src/commands/edit-cmd.ts +1014 -0
  390. package/src/commands/export.ts +918 -0
  391. package/src/commands/generate.ts +2146 -0
  392. package/src/commands/media.ts +177 -0
  393. package/src/commands/output.ts +142 -0
  394. package/src/commands/pipeline.ts +398 -0
  395. package/src/commands/project.test.ts +127 -0
  396. package/src/commands/project.ts +149 -0
  397. package/src/commands/sanitize.ts +60 -0
  398. package/src/commands/schema.ts +130 -0
  399. package/src/commands/setup.ts +509 -0
  400. package/src/commands/timeline.test.ts +499 -0
  401. package/src/commands/timeline.ts +529 -0
  402. package/src/commands/validate.ts +77 -0
  403. package/src/config/config.test.ts +197 -0
  404. package/src/config/index.ts +125 -0
  405. package/src/config/schema.ts +82 -0
  406. package/src/engine/index.ts +2 -0
  407. package/src/engine/project.test.ts +702 -0
  408. package/src/engine/project.ts +439 -0
  409. package/src/index.ts +146 -0
  410. package/src/utils/api-key.test.ts +41 -0
  411. package/src/utils/api-key.ts +247 -0
  412. package/src/utils/audio.ts +83 -0
  413. package/src/utils/exec-safe.ts +75 -0
  414. package/src/utils/first-run.ts +52 -0
  415. package/src/utils/provider-resolver.ts +56 -0
  416. package/src/utils/remotion.ts +951 -0
  417. package/src/utils/subtitle.test.ts +227 -0
  418. package/src/utils/subtitle.ts +169 -0
  419. package/src/utils/tty.ts +196 -0
  420. package/tsconfig.json +20 -0
@@ -0,0 +1,1027 @@
1
+ /**
2
+ * @module ai-script-pipeline
3
+ *
4
+ * Script-to-video pipeline and scene regeneration execute functions.
5
+ *
6
+ * CLI commands: script-to-video, regenerate-scene
7
+ *
8
+ * Execute functions:
9
+ * executeScriptToVideo - Full pipeline: storyboard -> TTS -> images -> videos -> project
10
+ * executeRegenerateScene - Re-generate specific scene(s) in an existing project
11
+ *
12
+ * Also exports shared helpers: uploadToImgbb, extendVideoToTarget,
13
+ * generateVideoWithRetryKling, generateVideoWithRetryRunway, generateVideoWithRetryVeo, waitForVideoWithRetry
14
+ *
15
+ * @dependencies Claude (storyboard), ElevenLabs (TTS), OpenAI/Gemini (images),
16
+ * Kling/Runway (video), FFmpeg (assembly/extension)
17
+ */
18
+ import { readFile, writeFile, mkdir, unlink, rename } from "node:fs/promises";
19
+ import { resolve, basename, extname } from "node:path";
20
+ import { existsSync } from "node:fs";
21
+ import chalk from "chalk";
22
+ import { GeminiProvider, OpenAIProvider, OpenAIImageProvider, ClaudeProvider, ElevenLabsProvider, KlingProvider, RunwayProvider, GrokProvider, } from "@vibeframe/ai-providers";
23
+ import { getApiKey } from "../utils/api-key.js";
24
+ import { getApiKeyFromConfig } from "../config/index.js";
25
+ import { Project } from "../engine/index.js";
26
+ import { getAudioDuration, getVideoDuration, extendVideoNaturally } from "../utils/audio.js";
27
+ import { applyTextOverlays } from "./ai-edit.js";
28
+ import { executeReview } from "./ai-review.js";
29
+ import { execSafe } from "../utils/exec-safe.js";
30
+ import { downloadVideo } from "./ai-helpers.js";
31
+ /** Default retry count for video generation API calls. */
32
+ export const DEFAULT_VIDEO_RETRIES = 2;
33
+ /** Delay between retries in milliseconds. */
34
+ export const RETRY_DELAY_MS = 5000;
35
+ /**
36
+ * Sleep helper
37
+ */
38
+ export function sleep(ms) {
39
+ return new Promise((resolve) => setTimeout(resolve, ms));
40
+ }
41
+ /**
42
+ * Upload image to ImgBB and return the URL
43
+ * Used for Kling v2.5/v2.6 image-to-video which requires URL (not base64)
44
+ */
45
+ export async function uploadToImgbb(imageBuffer, apiKey) {
46
+ try {
47
+ const base64Image = imageBuffer.toString("base64");
48
+ const formData = new URLSearchParams();
49
+ formData.append("key", apiKey);
50
+ formData.append("image", base64Image);
51
+ const response = await fetch("https://api.imgbb.com/1/upload", {
52
+ method: "POST",
53
+ body: formData,
54
+ });
55
+ if (!response.ok) {
56
+ return { success: false, error: `ImgBB API error (${response.status}): ${response.statusText}` };
57
+ }
58
+ const data = (await response.json());
59
+ if (data.success && data.data?.url) {
60
+ return { success: true, url: data.data.url };
61
+ }
62
+ else {
63
+ return { success: false, error: data.error?.message || "Upload failed" };
64
+ }
65
+ }
66
+ catch (err) {
67
+ return { success: false, error: String(err) };
68
+ }
69
+ }
70
+ /**
71
+ * Extend a video to target duration using Kling extend API when possible,
72
+ * with fallback to FFmpeg-based extendVideoNaturally.
73
+ *
74
+ * When the extension ratio > 1.4 and a Kling provider + videoId are available,
75
+ * uses the Kling video-extend API for natural continuation instead of freeze frames.
76
+ */
77
+ export async function extendVideoToTarget(videoPath, targetDuration, outputDir, sceneLabel, options) {
78
+ const actualDuration = await getVideoDuration(videoPath);
79
+ if (actualDuration >= targetDuration - 0.1)
80
+ return;
81
+ const ratio = targetDuration / actualDuration;
82
+ const extendedPath = resolve(outputDir, `${basename(videoPath, ".mp4")}-extended.mp4`);
83
+ // Try Kling extend API for large gaps (ratio > 1.4) where freeze frames look bad
84
+ if (ratio > 1.4 && options?.kling && options?.videoId) {
85
+ try {
86
+ options.onProgress?.(`${sceneLabel}: Extending via Kling API...`);
87
+ const extendResult = await options.kling.extendVideo(options.videoId, {
88
+ duration: "5",
89
+ });
90
+ if (extendResult.status !== "failed" && extendResult.id) {
91
+ const waitResult = await options.kling.waitForExtendCompletion(extendResult.id, (status) => {
92
+ options.onProgress?.(`${sceneLabel}: extend ${status.status}...`);
93
+ }, 600000);
94
+ if (waitResult.status === "completed" && waitResult.videoUrl) {
95
+ // Download extended video
96
+ const extendedVideoPath = resolve(outputDir, `${basename(videoPath, ".mp4")}-kling-ext.mp4`);
97
+ const buffer = await downloadVideo(waitResult.videoUrl);
98
+ await writeFile(extendedVideoPath, buffer);
99
+ // Concatenate original + extension
100
+ const concatPath = resolve(outputDir, `${basename(videoPath, ".mp4")}-concat.mp4`);
101
+ const listPath = resolve(outputDir, `${basename(videoPath, ".mp4")}-concat.txt`);
102
+ await writeFile(listPath, `file '${videoPath}'\nfile '${extendedVideoPath}'`, "utf-8");
103
+ await execSafe("ffmpeg", ["-y", "-f", "concat", "-safe", "0", "-i", listPath, "-c", "copy", concatPath]);
104
+ // Trim to exact target duration if concatenated video is longer
105
+ const concatDuration = await getVideoDuration(concatPath);
106
+ if (concatDuration > targetDuration + 0.5) {
107
+ await execSafe("ffmpeg", ["-y", "-i", concatPath, "-t", targetDuration.toFixed(2), "-c", "copy", extendedPath]);
108
+ await unlink(concatPath);
109
+ }
110
+ else {
111
+ await rename(concatPath, extendedPath);
112
+ }
113
+ // Cleanup temp files
114
+ await unlink(extendedVideoPath).catch(() => { });
115
+ await unlink(listPath).catch(() => { });
116
+ await unlink(videoPath);
117
+ await rename(extendedPath, videoPath);
118
+ return;
119
+ }
120
+ }
121
+ // If Kling extend failed, fall through to FFmpeg fallback
122
+ options.onProgress?.(`${sceneLabel}: Kling extend failed, using FFmpeg fallback...`);
123
+ }
124
+ catch {
125
+ options.onProgress?.(`${sceneLabel}: Kling extend error, using FFmpeg fallback...`);
126
+ }
127
+ }
128
+ // FFmpeg-based fallback (slowdown + frame interpolation + freeze frame)
129
+ await extendVideoNaturally(videoPath, targetDuration, extendedPath);
130
+ await unlink(videoPath);
131
+ await rename(extendedPath, videoPath);
132
+ }
133
+ /**
134
+ * Generate video with retry logic for Kling provider
135
+ * Supports image-to-video with URL (v2.5/v2.6 models)
136
+ */
137
+ export async function generateVideoWithRetryKling(kling, segment, options, maxRetries, onProgress) {
138
+ // Build detailed prompt from storyboard segment
139
+ const prompt = segment.visualStyle
140
+ ? `${segment.visuals}. Style: ${segment.visualStyle}`
141
+ : segment.visuals;
142
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
143
+ try {
144
+ const result = await kling.generateVideo(prompt, {
145
+ prompt,
146
+ // Pass reference image (base64 or URL) - KlingProvider handles v1.5 fallback for base64
147
+ referenceImage: options.referenceImage,
148
+ duration: options.duration,
149
+ aspectRatio: options.aspectRatio,
150
+ mode: "std", // Use std mode for faster generation
151
+ });
152
+ if (result.status !== "failed" && result.id) {
153
+ return {
154
+ taskId: result.id,
155
+ type: options.referenceImage ? "image2video" : "text2video",
156
+ };
157
+ }
158
+ if (attempt < maxRetries) {
159
+ onProgress?.(`⚠ Retry ${attempt + 1}/${maxRetries}...`);
160
+ await sleep(RETRY_DELAY_MS);
161
+ }
162
+ }
163
+ catch (err) {
164
+ const errMsg = err instanceof Error ? err.message : String(err);
165
+ if (attempt < maxRetries) {
166
+ onProgress?.(`⚠ Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
167
+ await sleep(RETRY_DELAY_MS);
168
+ }
169
+ else {
170
+ // Log the final error on last attempt
171
+ console.error(chalk.dim(`\n [Kling error: ${errMsg}]`));
172
+ }
173
+ }
174
+ }
175
+ return null;
176
+ }
177
+ /**
178
+ * Generate video with retry logic for Runway provider
179
+ */
180
+ export async function generateVideoWithRetryRunway(runway, segment, referenceImage, options, maxRetries, onProgress) {
181
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
182
+ try {
183
+ const result = await runway.generateVideo(segment.visuals, {
184
+ prompt: segment.visuals,
185
+ referenceImage,
186
+ duration: options.duration,
187
+ aspectRatio: options.aspectRatio,
188
+ });
189
+ if (result.status !== "failed" && result.id) {
190
+ return { taskId: result.id };
191
+ }
192
+ if (attempt < maxRetries) {
193
+ onProgress?.(`⚠ Retry ${attempt + 1}/${maxRetries}...`);
194
+ await sleep(RETRY_DELAY_MS);
195
+ }
196
+ }
197
+ catch (err) {
198
+ const errMsg = err instanceof Error ? err.message : String(err);
199
+ if (attempt < maxRetries) {
200
+ onProgress?.(`⚠ Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
201
+ await sleep(RETRY_DELAY_MS);
202
+ }
203
+ else {
204
+ console.error(chalk.dim(`\n [Runway error: ${errMsg}]`));
205
+ }
206
+ }
207
+ }
208
+ return null;
209
+ }
210
+ /**
211
+ * Generate video with retry logic for Veo (Gemini) provider
212
+ */
213
+ export async function generateVideoWithRetryVeo(gemini, segment, options, maxRetries, onProgress) {
214
+ const prompt = segment.visualStyle
215
+ ? `${segment.visuals}. Style: ${segment.visualStyle}`
216
+ : segment.visuals;
217
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
218
+ try {
219
+ const result = await gemini.generateVideo(prompt, {
220
+ prompt,
221
+ referenceImage: options.referenceImage,
222
+ duration: options.duration,
223
+ aspectRatio: options.aspectRatio,
224
+ model: "veo-3.1-fast-generate-preview",
225
+ });
226
+ if (result.status !== "failed" && result.id) {
227
+ return { operationName: result.id };
228
+ }
229
+ if (attempt < maxRetries) {
230
+ onProgress?.(`⚠ Retry ${attempt + 1}/${maxRetries}...`);
231
+ await sleep(RETRY_DELAY_MS);
232
+ }
233
+ }
234
+ catch (err) {
235
+ const errMsg = err instanceof Error ? err.message : String(err);
236
+ if (attempt < maxRetries) {
237
+ onProgress?.(`⚠ Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
238
+ await sleep(RETRY_DELAY_MS);
239
+ }
240
+ else {
241
+ console.error(chalk.dim(`\n [Veo error: ${errMsg}]`));
242
+ }
243
+ }
244
+ }
245
+ return null;
246
+ }
247
+ /**
248
+ * Wait for video completion with retry logic
249
+ */
250
+ export async function waitForVideoWithRetry(provider, taskId, providerType, maxRetries, onProgress, timeout) {
251
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
252
+ try {
253
+ let result;
254
+ if (providerType === "kling") {
255
+ result = await provider.waitForCompletion(taskId, "image2video", (status) => onProgress?.(status.status || "processing"), timeout || 600000);
256
+ }
257
+ else {
258
+ result = await provider.waitForCompletion(taskId, (status) => {
259
+ const progress = status.progress !== undefined ? `${status.progress}%` : status.status;
260
+ onProgress?.(progress || "processing");
261
+ }, timeout || 300000);
262
+ }
263
+ if (result.status === "completed" && result.videoUrl) {
264
+ return { videoUrl: result.videoUrl };
265
+ }
266
+ // If failed, try resubmitting on next attempt
267
+ if (attempt < maxRetries) {
268
+ onProgress?.(`⚠ Failed, will need resubmission...`);
269
+ return null; // Signal need for resubmission
270
+ }
271
+ }
272
+ catch (err) {
273
+ if (attempt < maxRetries) {
274
+ onProgress?.(`⚠ Error waiting, retry ${attempt + 1}/${maxRetries}...`);
275
+ await sleep(RETRY_DELAY_MS);
276
+ }
277
+ }
278
+ }
279
+ return null;
280
+ }
281
+ /**
282
+ * Execute the full script-to-video pipeline programmatically.
283
+ *
284
+ * Pipeline stages:
285
+ * 1. Generate storyboard with Claude
286
+ * 2. Generate per-scene voiceovers with ElevenLabs TTS
287
+ * 3. Generate scene images (OpenAI/Gemini)
288
+ * 4. Generate scene videos (Kling/Runway) with extension to match narration
289
+ * 4.5. Apply text overlays if present in storyboard
290
+ * 5. Assemble .vibe.json project file
291
+ * 6. Optional AI review and auto-fix (Gemini)
292
+ *
293
+ * @param options - Pipeline configuration
294
+ * @returns Result with paths to all generated assets and project file
295
+ */
296
+ export async function executeScriptToVideo(options) {
297
+ const outputDir = options.outputDir || "script-video-output";
298
+ try {
299
+ // Get storyboard provider API key
300
+ const storyboardProvider = options.storyboardProvider || "claude";
301
+ let storyboardApiKey;
302
+ if (storyboardProvider === "openai") {
303
+ storyboardApiKey = (await getApiKey("OPENAI_API_KEY", "OpenAI")) ?? undefined;
304
+ if (!storyboardApiKey) {
305
+ return { success: false, outputDir, scenes: 0, error: "OpenAI API key required for storyboard generation (--storyboard-provider openai). Run 'vibe setup' or set OPENAI_API_KEY in .env" };
306
+ }
307
+ }
308
+ else if (storyboardProvider === "gemini") {
309
+ storyboardApiKey = (await getApiKey("GOOGLE_API_KEY", "Google")) ?? undefined;
310
+ if (!storyboardApiKey) {
311
+ return { success: false, outputDir, scenes: 0, error: "Google API key required for storyboard generation (--storyboard-provider gemini). Run 'vibe setup' or set GOOGLE_API_KEY in .env" };
312
+ }
313
+ }
314
+ else {
315
+ // Default: Claude
316
+ storyboardApiKey = (await getApiKey("ANTHROPIC_API_KEY", "Anthropic")) ?? undefined;
317
+ if (!storyboardApiKey) {
318
+ return { success: false, outputDir, scenes: 0, error: "Anthropic API key required for storyboard generation. Run 'vibe setup' or set ANTHROPIC_API_KEY in .env" };
319
+ }
320
+ }
321
+ // Get image provider API key
322
+ let imageApiKey;
323
+ const imageProvider = options.imageProvider || "openai";
324
+ if (imageProvider === "openai" || imageProvider === "dalle") {
325
+ imageApiKey = (await getApiKey("OPENAI_API_KEY", "OpenAI")) ?? undefined;
326
+ if (!imageApiKey) {
327
+ return { success: false, outputDir, scenes: 0, error: "OpenAI API key required for image generation. Run 'vibe setup' or set OPENAI_API_KEY in .env" };
328
+ }
329
+ }
330
+ else if (imageProvider === "gemini") {
331
+ imageApiKey = (await getApiKey("GOOGLE_API_KEY", "Google")) ?? undefined;
332
+ if (!imageApiKey) {
333
+ return { success: false, outputDir, scenes: 0, error: "Google API key required for Gemini image generation. Run 'vibe setup' or set GOOGLE_API_KEY in .env" };
334
+ }
335
+ }
336
+ else if (imageProvider === "grok") {
337
+ imageApiKey = (await getApiKey("XAI_API_KEY", "xAI")) ?? undefined;
338
+ if (!imageApiKey) {
339
+ return { success: false, outputDir, scenes: 0, error: "xAI API key required for Grok image generation. Run 'vibe setup' or set XAI_API_KEY in .env" };
340
+ }
341
+ }
342
+ let elevenlabsApiKey;
343
+ if (!options.noVoiceover) {
344
+ elevenlabsApiKey = (await getApiKey("ELEVENLABS_API_KEY", "ElevenLabs")) ?? undefined;
345
+ if (!elevenlabsApiKey) {
346
+ return { success: false, outputDir, scenes: 0, error: "ElevenLabs API key required for voiceover (or use noVoiceover option). Run 'vibe setup' or set ELEVENLABS_API_KEY in .env" };
347
+ }
348
+ }
349
+ let videoApiKey;
350
+ if (!options.imagesOnly) {
351
+ if (options.generator === "kling") {
352
+ videoApiKey = (await getApiKey("KLING_API_KEY", "Kling")) ?? undefined;
353
+ if (!videoApiKey) {
354
+ return { success: false, outputDir, scenes: 0, error: "Kling API key required (or use imagesOnly option). Run 'vibe setup' or set KLING_API_KEY in .env" };
355
+ }
356
+ }
357
+ else if (options.generator === "veo") {
358
+ videoApiKey = (await getApiKey("GOOGLE_API_KEY", "Google")) ?? undefined;
359
+ if (!videoApiKey) {
360
+ return { success: false, outputDir, scenes: 0, error: "Google API key required for Veo video generation (or use imagesOnly option). Run 'vibe setup' or set GOOGLE_API_KEY in .env" };
361
+ }
362
+ }
363
+ else {
364
+ videoApiKey = (await getApiKey("RUNWAY_API_SECRET", "Runway")) ?? undefined;
365
+ if (!videoApiKey) {
366
+ return { success: false, outputDir, scenes: 0, error: "Runway API key required (or use imagesOnly option). Run 'vibe setup' or set RUNWAY_API_SECRET in .env" };
367
+ }
368
+ }
369
+ }
370
+ // Create output directory
371
+ const absOutputDir = resolve(process.cwd(), outputDir);
372
+ if (!existsSync(absOutputDir)) {
373
+ await mkdir(absOutputDir, { recursive: true });
374
+ }
375
+ // Step 1: Generate storyboard
376
+ let segments;
377
+ const creativityOpts = { creativity: options.creativity };
378
+ if (storyboardProvider === "openai") {
379
+ const openai = new OpenAIProvider();
380
+ await openai.initialize({ apiKey: storyboardApiKey });
381
+ segments = await openai.analyzeContent(options.script, options.duration, creativityOpts);
382
+ }
383
+ else if (storyboardProvider === "gemini") {
384
+ const gemini = new GeminiProvider();
385
+ await gemini.initialize({ apiKey: storyboardApiKey });
386
+ segments = await gemini.analyzeContent(options.script, options.duration, creativityOpts);
387
+ }
388
+ else {
389
+ const claude = new ClaudeProvider();
390
+ await claude.initialize({ apiKey: storyboardApiKey });
391
+ segments = await claude.analyzeContent(options.script, options.duration, creativityOpts);
392
+ }
393
+ if (segments.length === 0) {
394
+ return { success: false, outputDir, scenes: 0, error: "Failed to generate storyboard" };
395
+ }
396
+ // Save storyboard
397
+ const storyboardPath = resolve(absOutputDir, "storyboard.json");
398
+ await writeFile(storyboardPath, JSON.stringify(segments, null, 2), "utf-8");
399
+ const result = {
400
+ success: true,
401
+ outputDir: absOutputDir,
402
+ scenes: segments.length,
403
+ storyboardPath,
404
+ narrations: [],
405
+ narrationEntries: [],
406
+ images: [],
407
+ videos: [],
408
+ failedScenes: [],
409
+ failedNarrations: [],
410
+ };
411
+ // Step 2: Generate per-scene voiceovers with ElevenLabs
412
+ if (!options.noVoiceover && elevenlabsApiKey) {
413
+ const elevenlabs = new ElevenLabsProvider();
414
+ await elevenlabs.initialize({ apiKey: elevenlabsApiKey });
415
+ for (let i = 0; i < segments.length; i++) {
416
+ const segment = segments[i];
417
+ const narrationText = segment.narration || segment.description;
418
+ if (!narrationText) {
419
+ // No narration text for this segment - add placeholder entry
420
+ result.narrationEntries.push({
421
+ path: null,
422
+ duration: segment.duration,
423
+ segmentIndex: i,
424
+ failed: false, // Not failed, just no text
425
+ });
426
+ continue;
427
+ }
428
+ const ttsResult = await elevenlabs.textToSpeech(narrationText, {
429
+ voiceId: options.voice,
430
+ });
431
+ if (ttsResult.success && ttsResult.audioBuffer) {
432
+ const audioPath = resolve(absOutputDir, `narration-${i + 1}.mp3`);
433
+ await writeFile(audioPath, ttsResult.audioBuffer);
434
+ // Get actual audio duration
435
+ const actualDuration = await getAudioDuration(audioPath);
436
+ segment.duration = actualDuration;
437
+ // Add to both arrays for backwards compatibility
438
+ result.narrations.push(audioPath);
439
+ result.narrationEntries.push({
440
+ path: audioPath,
441
+ duration: actualDuration,
442
+ segmentIndex: i,
443
+ failed: false,
444
+ });
445
+ }
446
+ else {
447
+ // TTS failed - add placeholder entry with error info
448
+ result.narrationEntries.push({
449
+ path: null,
450
+ duration: segment.duration, // Keep original estimated duration
451
+ segmentIndex: i,
452
+ failed: true,
453
+ error: ttsResult.error || "Unknown TTS error",
454
+ });
455
+ result.failedNarrations.push(i + 1); // 1-indexed for user display
456
+ }
457
+ }
458
+ // Recalculate startTime for all segments
459
+ let currentTime = 0;
460
+ for (const segment of segments) {
461
+ segment.startTime = currentTime;
462
+ currentTime += segment.duration;
463
+ }
464
+ // Re-save storyboard with updated durations
465
+ await writeFile(storyboardPath, JSON.stringify(segments, null, 2), "utf-8");
466
+ }
467
+ // Step 3: Generate images
468
+ const dalleImageSizes = {
469
+ "16:9": "1536x1024",
470
+ "9:16": "1024x1536",
471
+ "1:1": "1024x1024",
472
+ };
473
+ let openaiImageInstance;
474
+ let geminiInstance;
475
+ let grokInstance;
476
+ if (imageProvider === "openai" || imageProvider === "dalle") {
477
+ openaiImageInstance = new OpenAIImageProvider();
478
+ await openaiImageInstance.initialize({ apiKey: imageApiKey });
479
+ }
480
+ else if (imageProvider === "gemini") {
481
+ geminiInstance = new GeminiProvider();
482
+ await geminiInstance.initialize({ apiKey: imageApiKey });
483
+ }
484
+ else if (imageProvider === "grok") {
485
+ grokInstance = new GrokProvider();
486
+ await grokInstance.initialize({ apiKey: imageApiKey });
487
+ }
488
+ const imagePaths = [];
489
+ for (let i = 0; i < segments.length; i++) {
490
+ const segment = segments[i];
491
+ const imagePrompt = segment.visualStyle
492
+ ? `${segment.visuals}. Style: ${segment.visualStyle}`
493
+ : segment.visuals;
494
+ try {
495
+ let imageBuffer;
496
+ let imageUrl;
497
+ if ((imageProvider === "openai" || imageProvider === "dalle") && openaiImageInstance) {
498
+ const imageResult = await openaiImageInstance.generateImage(imagePrompt, {
499
+ size: dalleImageSizes[options.aspectRatio || "16:9"] || "1536x1024",
500
+ quality: "standard",
501
+ });
502
+ if (imageResult.success && imageResult.images && imageResult.images.length > 0) {
503
+ // GPT Image 1.5 returns base64, DALL-E 3 returns URL
504
+ const img = imageResult.images[0];
505
+ if (img.base64) {
506
+ imageBuffer = Buffer.from(img.base64, "base64");
507
+ }
508
+ else if (img.url) {
509
+ imageUrl = img.url;
510
+ }
511
+ }
512
+ // else: imageResult.error is available but not captured
513
+ }
514
+ else if (imageProvider === "gemini" && geminiInstance) {
515
+ const imageResult = await geminiInstance.generateImage(imagePrompt, {
516
+ aspectRatio: (options.aspectRatio || "16:9"),
517
+ });
518
+ if (imageResult.success && imageResult.images?.[0]?.base64) {
519
+ imageBuffer = Buffer.from(imageResult.images[0].base64, "base64");
520
+ }
521
+ // else: imageResult.error is available but not captured
522
+ }
523
+ else if (imageProvider === "grok" && grokInstance) {
524
+ const imageResult = await grokInstance.generateImage(imagePrompt, {
525
+ aspectRatio: options.aspectRatio || "16:9",
526
+ });
527
+ if (imageResult.success && imageResult.images && imageResult.images.length > 0) {
528
+ const img = imageResult.images[0];
529
+ if (img.base64) {
530
+ imageBuffer = Buffer.from(img.base64, "base64");
531
+ }
532
+ else if (img.url) {
533
+ imageUrl = img.url;
534
+ }
535
+ }
536
+ }
537
+ const imagePath = resolve(absOutputDir, `scene-${i + 1}.png`);
538
+ if (imageBuffer) {
539
+ await writeFile(imagePath, imageBuffer);
540
+ imagePaths.push(imagePath);
541
+ result.images.push(imagePath);
542
+ }
543
+ else if (imageUrl) {
544
+ const response = await fetch(imageUrl);
545
+ const buffer = Buffer.from(await response.arrayBuffer());
546
+ await writeFile(imagePath, buffer);
547
+ imagePaths.push(imagePath);
548
+ result.images.push(imagePath);
549
+ }
550
+ else {
551
+ // Track failed scene - error details not captured (see provider imageResult.error)
552
+ // The failedScenes array tracks which scenes failed for the caller
553
+ imagePaths.push("");
554
+ }
555
+ }
556
+ catch {
557
+ imagePaths.push("");
558
+ }
559
+ // Rate limiting delay
560
+ if (i < segments.length - 1) {
561
+ await new Promise((r) => setTimeout(r, 500));
562
+ }
563
+ }
564
+ // Step 4: Generate videos (if not images-only)
565
+ const videoPaths = [];
566
+ const maxRetries = options.retries ?? DEFAULT_VIDEO_RETRIES;
567
+ if (!options.imagesOnly && videoApiKey) {
568
+ if (options.generator === "kling") {
569
+ const kling = new KlingProvider();
570
+ await kling.initialize({ apiKey: videoApiKey });
571
+ if (!kling.isConfigured()) {
572
+ return { success: false, outputDir: absOutputDir, scenes: segments.length, error: "Invalid Kling API key format. Use ACCESS_KEY:SECRET_KEY" };
573
+ }
574
+ for (let i = 0; i < segments.length; i++) {
575
+ if (!imagePaths[i]) {
576
+ videoPaths.push("");
577
+ continue;
578
+ }
579
+ const segment = segments[i];
580
+ const videoDuration = (segment.duration > 5 ? 10 : 5);
581
+ // Using text2video since Kling's image2video requires URL (not base64)
582
+ const taskResult = await generateVideoWithRetryKling(kling, segment, { duration: videoDuration, aspectRatio: (options.aspectRatio || "16:9") }, maxRetries);
583
+ if (taskResult) {
584
+ try {
585
+ const waitResult = await kling.waitForCompletion(taskResult.taskId, taskResult.type, undefined, 600000);
586
+ if (waitResult.status === "completed" && waitResult.videoUrl) {
587
+ const videoPath = resolve(absOutputDir, `scene-${i + 1}.mp4`);
588
+ const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
589
+ await writeFile(videoPath, buffer);
590
+ // Extend video to match narration duration if needed
591
+ const targetDuration = segment.duration; // Already updated to narration length
592
+ const actualVideoDuration = await getVideoDuration(videoPath);
593
+ if (actualVideoDuration < targetDuration - 0.1) {
594
+ const extendedPath = resolve(absOutputDir, `scene-${i + 1}-extended.mp4`);
595
+ await extendVideoNaturally(videoPath, targetDuration, extendedPath);
596
+ // Replace original with extended version
597
+ await unlink(videoPath);
598
+ await rename(extendedPath, videoPath);
599
+ }
600
+ videoPaths.push(videoPath);
601
+ result.videos.push(videoPath);
602
+ }
603
+ else {
604
+ videoPaths.push("");
605
+ result.failedScenes.push(i + 1);
606
+ }
607
+ }
608
+ catch {
609
+ videoPaths.push("");
610
+ result.failedScenes.push(i + 1);
611
+ }
612
+ }
613
+ else {
614
+ videoPaths.push("");
615
+ result.failedScenes.push(i + 1);
616
+ }
617
+ }
618
+ }
619
+ else if (options.generator === "veo") {
620
+ // Veo (Gemini)
621
+ const veo = new GeminiProvider();
622
+ await veo.initialize({ apiKey: videoApiKey });
623
+ for (let i = 0; i < segments.length; i++) {
624
+ if (!imagePaths[i]) {
625
+ videoPaths.push("");
626
+ continue;
627
+ }
628
+ const segment = segments[i];
629
+ const veoDuration = (segment.duration > 6 ? 8 : segment.duration > 4 ? 6 : 4);
630
+ const taskResult = await generateVideoWithRetryVeo(veo, segment, { duration: veoDuration, aspectRatio: (options.aspectRatio || "16:9") }, maxRetries);
631
+ if (taskResult) {
632
+ try {
633
+ const waitResult = await veo.waitForVideoCompletion(taskResult.operationName, undefined, 300000);
634
+ if (waitResult.status === "completed" && waitResult.videoUrl) {
635
+ const videoPath = resolve(absOutputDir, `scene-${i + 1}.mp4`);
636
+ const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
637
+ await writeFile(videoPath, buffer);
638
+ // Extend video to match narration duration if needed
639
+ const targetDuration = segment.duration;
640
+ const actualVideoDuration = await getVideoDuration(videoPath);
641
+ if (actualVideoDuration < targetDuration - 0.1) {
642
+ const extendedPath = resolve(absOutputDir, `scene-${i + 1}-extended.mp4`);
643
+ await extendVideoNaturally(videoPath, targetDuration, extendedPath);
644
+ await unlink(videoPath);
645
+ await rename(extendedPath, videoPath);
646
+ }
647
+ videoPaths.push(videoPath);
648
+ result.videos.push(videoPath);
649
+ }
650
+ else {
651
+ videoPaths.push("");
652
+ result.failedScenes.push(i + 1);
653
+ }
654
+ }
655
+ catch {
656
+ videoPaths.push("");
657
+ result.failedScenes.push(i + 1);
658
+ }
659
+ }
660
+ else {
661
+ videoPaths.push("");
662
+ result.failedScenes.push(i + 1);
663
+ }
664
+ }
665
+ }
666
+ else {
667
+ // Runway
668
+ const runway = new RunwayProvider();
669
+ await runway.initialize({ apiKey: videoApiKey });
670
+ for (let i = 0; i < segments.length; i++) {
671
+ if (!imagePaths[i]) {
672
+ videoPaths.push("");
673
+ continue;
674
+ }
675
+ const segment = segments[i];
676
+ const imageBuffer = await readFile(imagePaths[i]);
677
+ const ext = extname(imagePaths[i]).toLowerCase().slice(1);
678
+ const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
679
+ const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
680
+ const videoDuration = (segment.duration > 5 ? 10 : 5);
681
+ const aspectRatio = options.aspectRatio === "1:1" ? "16:9" : (options.aspectRatio || "16:9");
682
+ const taskResult = await generateVideoWithRetryRunway(runway, segment, referenceImage, { duration: videoDuration, aspectRatio }, maxRetries);
683
+ if (taskResult) {
684
+ try {
685
+ const waitResult = await runway.waitForCompletion(taskResult.taskId, undefined, 300000);
686
+ if (waitResult.status === "completed" && waitResult.videoUrl) {
687
+ const videoPath = resolve(absOutputDir, `scene-${i + 1}.mp4`);
688
+ const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
689
+ await writeFile(videoPath, buffer);
690
+ // Extend video to match narration duration if needed
691
+ const targetDuration = segment.duration;
692
+ const actualVideoDuration = await getVideoDuration(videoPath);
693
+ if (actualVideoDuration < targetDuration - 0.1) {
694
+ const extendedPath = resolve(absOutputDir, `scene-${i + 1}-extended.mp4`);
695
+ await extendVideoNaturally(videoPath, targetDuration, extendedPath);
696
+ await unlink(videoPath);
697
+ await rename(extendedPath, videoPath);
698
+ }
699
+ videoPaths.push(videoPath);
700
+ result.videos.push(videoPath);
701
+ }
702
+ else {
703
+ videoPaths.push("");
704
+ result.failedScenes.push(i + 1);
705
+ }
706
+ }
707
+ catch {
708
+ videoPaths.push("");
709
+ result.failedScenes.push(i + 1);
710
+ }
711
+ }
712
+ else {
713
+ videoPaths.push("");
714
+ result.failedScenes.push(i + 1);
715
+ }
716
+ }
717
+ }
718
+ }
719
+ // Step 4.5: Apply text overlays (if segments have textOverlays)
720
+ if (!options.noTextOverlay) {
721
+ for (let i = 0; i < segments.length; i++) {
722
+ const segment = segments[i];
723
+ if (segment.textOverlays && segment.textOverlays.length > 0 && videoPaths[i] && videoPaths[i] !== "") {
724
+ try {
725
+ const overlayOutput = videoPaths[i].replace(/(\.[^.]+)$/, "-overlay$1");
726
+ const overlayResult = await applyTextOverlays({
727
+ videoPath: videoPaths[i],
728
+ texts: segment.textOverlays,
729
+ outputPath: overlayOutput,
730
+ style: options.textStyle || "lower-third",
731
+ });
732
+ if (overlayResult.success && overlayResult.outputPath) {
733
+ videoPaths[i] = overlayResult.outputPath;
734
+ }
735
+ // Silent fallback: keep original on failure
736
+ }
737
+ catch {
738
+ // Silent fallback: keep original video
739
+ }
740
+ }
741
+ }
742
+ }
743
+ // Step 5: Create project file
744
+ const project = new Project("Script-to-Video Output");
745
+ project.setAspectRatio((options.aspectRatio || "16:9"));
746
+ // Clear default tracks
747
+ const defaultTracks = project.getTracks();
748
+ for (const track of defaultTracks) {
749
+ project.removeTrack(track.id);
750
+ }
751
+ const videoTrack = project.addTrack({
752
+ name: "Video",
753
+ type: "video",
754
+ order: 1,
755
+ isMuted: false,
756
+ isLocked: false,
757
+ isVisible: true,
758
+ });
759
+ const audioTrack = project.addTrack({
760
+ name: "Audio",
761
+ type: "audio",
762
+ order: 0,
763
+ isMuted: false,
764
+ isLocked: false,
765
+ isVisible: true,
766
+ });
767
+ // Add narration clips - use narrationEntries for proper segment alignment
768
+ if (result.narrationEntries && result.narrationEntries.length > 0) {
769
+ for (const entry of result.narrationEntries) {
770
+ // Skip failed or missing narrations
771
+ if (entry.failed || !entry.path)
772
+ continue;
773
+ const segment = segments[entry.segmentIndex];
774
+ const narrationDuration = await getAudioDuration(entry.path);
775
+ const audioSource = project.addSource({
776
+ name: `Narration ${entry.segmentIndex + 1}`,
777
+ url: entry.path,
778
+ type: "audio",
779
+ duration: narrationDuration,
780
+ });
781
+ project.addClip({
782
+ sourceId: audioSource.id,
783
+ trackId: audioTrack.id,
784
+ startTime: segment.startTime,
785
+ duration: narrationDuration,
786
+ sourceStartOffset: 0,
787
+ sourceEndOffset: narrationDuration,
788
+ });
789
+ }
790
+ }
791
+ // Add video/image clips
792
+ let currentTime = 0;
793
+ for (let i = 0; i < segments.length; i++) {
794
+ const segment = segments[i];
795
+ const hasVideo = videoPaths[i] && videoPaths[i] !== "";
796
+ const hasImage = imagePaths[i] && imagePaths[i] !== "";
797
+ if (!hasVideo && !hasImage) {
798
+ currentTime += segment.duration;
799
+ continue;
800
+ }
801
+ const assetPath = hasVideo ? videoPaths[i] : imagePaths[i];
802
+ const mediaType = hasVideo ? "video" : "image";
803
+ // Use actual video duration (after extension) instead of segment.duration
804
+ const actualDuration = hasVideo
805
+ ? await getVideoDuration(assetPath)
806
+ : segment.duration;
807
+ const source = project.addSource({
808
+ name: `Scene ${i + 1}`,
809
+ url: assetPath,
810
+ type: mediaType,
811
+ duration: actualDuration,
812
+ });
813
+ project.addClip({
814
+ sourceId: source.id,
815
+ trackId: videoTrack.id,
816
+ startTime: currentTime,
817
+ duration: actualDuration,
818
+ sourceStartOffset: 0,
819
+ sourceEndOffset: actualDuration,
820
+ });
821
+ currentTime += actualDuration;
822
+ }
823
+ // Save project file
824
+ const projectPath = resolve(absOutputDir, "project.vibe.json");
825
+ await writeFile(projectPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
826
+ result.projectPath = projectPath;
827
+ result.totalDuration = currentTime;
828
+ // Step 6: AI Review & Auto-fix (optional, --review flag)
829
+ if (options.review) {
830
+ try {
831
+ const storyboardFile = resolve(absOutputDir, "storyboard.json");
832
+ // Export project to temp MP4 for review (use first valid video as proxy)
833
+ const reviewTarget = videoPaths.find((p) => p && p !== "") || imagePaths.find((p) => p && p !== "");
834
+ if (reviewTarget) {
835
+ const reviewResult = await executeReview({
836
+ videoPath: reviewTarget,
837
+ storyboardPath: existsSync(storyboardFile) ? storyboardFile : undefined,
838
+ autoApply: options.reviewAutoApply,
839
+ model: "flash",
840
+ });
841
+ if (reviewResult.success) {
842
+ result.reviewFeedback = reviewResult.feedback;
843
+ result.appliedFixes = reviewResult.appliedFixes;
844
+ result.reviewedVideoPath = reviewResult.outputPath;
845
+ }
846
+ }
847
+ }
848
+ catch {
849
+ // Review is non-critical, continue with result
850
+ }
851
+ }
852
+ return result;
853
+ }
854
+ catch (error) {
855
+ return {
856
+ success: false,
857
+ outputDir,
858
+ scenes: 0,
859
+ error: error instanceof Error ? error.message : String(error),
860
+ };
861
+ }
862
+ }
863
+ /**
864
+ * Regenerate specific scene(s) in an existing script-to-video project.
865
+ *
866
+ * Reads the storyboard.json from the project directory, then regenerates
867
+ * the requested scenes using the specified video/image provider. Supports
868
+ * image-to-video via ImgBB URL upload for Kling.
869
+ *
870
+ * @param options - Scene regeneration configuration
871
+ * @returns Result with lists of regenerated and failed scene numbers
872
+ */
873
+ export async function executeRegenerateScene(options) {
874
+ const result = {
875
+ success: false,
876
+ regeneratedScenes: [],
877
+ failedScenes: [],
878
+ };
879
+ try {
880
+ const outputDir = resolve(process.cwd(), options.projectDir);
881
+ const storyboardPath = resolve(outputDir, "storyboard.json");
882
+ if (!existsSync(outputDir)) {
883
+ return { ...result, error: `Project directory not found: ${outputDir}` };
884
+ }
885
+ if (!existsSync(storyboardPath)) {
886
+ return { ...result, error: `Storyboard not found: ${storyboardPath}` };
887
+ }
888
+ const storyboardContent = await readFile(storyboardPath, "utf-8");
889
+ const segments = JSON.parse(storyboardContent);
890
+ // Validate scenes
891
+ for (const sceneNum of options.scenes) {
892
+ if (sceneNum < 1 || sceneNum > segments.length) {
893
+ return { ...result, error: `Scene ${sceneNum} does not exist. Storyboard has ${segments.length} scenes.` };
894
+ }
895
+ }
896
+ const regenerateVideo = options.videoOnly || (!options.narrationOnly && !options.imageOnly);
897
+ // Get API keys
898
+ let videoApiKey;
899
+ if (regenerateVideo) {
900
+ if (options.generator === "kling" || !options.generator) {
901
+ videoApiKey = (await getApiKey("KLING_API_KEY", "Kling")) ?? undefined;
902
+ if (!videoApiKey) {
903
+ return { ...result, error: "Kling API key required. Run 'vibe setup' or set KLING_API_KEY in .env" };
904
+ }
905
+ }
906
+ else {
907
+ videoApiKey = (await getApiKey("RUNWAY_API_SECRET", "Runway")) ?? undefined;
908
+ if (!videoApiKey) {
909
+ return { ...result, error: "Runway API key required. Run 'vibe setup' or set RUNWAY_API_SECRET in .env" };
910
+ }
911
+ }
912
+ }
913
+ // Process each scene
914
+ for (const sceneNum of options.scenes) {
915
+ const segment = segments[sceneNum - 1];
916
+ const imagePath = resolve(outputDir, `scene-${sceneNum}.png`);
917
+ const videoPath = resolve(outputDir, `scene-${sceneNum}.mp4`);
918
+ if (regenerateVideo && videoApiKey) {
919
+ if (!existsSync(imagePath)) {
920
+ result.failedScenes.push(sceneNum);
921
+ continue;
922
+ }
923
+ const imageBuffer = await readFile(imagePath);
924
+ const videoDuration = (segment.duration > 5 ? 10 : 5);
925
+ const maxRetries = options.retries ?? DEFAULT_VIDEO_RETRIES;
926
+ if (options.generator === "kling" || !options.generator) {
927
+ const kling = new KlingProvider();
928
+ await kling.initialize({ apiKey: videoApiKey });
929
+ if (!kling.isConfigured()) {
930
+ result.failedScenes.push(sceneNum);
931
+ continue;
932
+ }
933
+ // Try to use image-to-video if ImgBB key available
934
+ const imgbbApiKey = await getApiKeyFromConfig("imgbb") || process.env.IMGBB_API_KEY;
935
+ let imageUrl;
936
+ if (imgbbApiKey) {
937
+ const uploadResult = await uploadToImgbb(imageBuffer, imgbbApiKey);
938
+ if (uploadResult.success && uploadResult.url) {
939
+ imageUrl = uploadResult.url;
940
+ }
941
+ }
942
+ const taskResult = await generateVideoWithRetryKling(kling, segment, {
943
+ duration: videoDuration,
944
+ aspectRatio: (options.aspectRatio || "16:9"),
945
+ referenceImage: imageUrl,
946
+ }, maxRetries);
947
+ if (taskResult) {
948
+ try {
949
+ const waitResult = await kling.waitForCompletion(taskResult.taskId, taskResult.type, undefined, 600000);
950
+ if (waitResult.status === "completed" && waitResult.videoUrl) {
951
+ const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
952
+ await writeFile(videoPath, buffer);
953
+ // Extend video to match narration duration if needed
954
+ const targetDuration = segment.duration;
955
+ const actualVideoDuration = await getVideoDuration(videoPath);
956
+ if (actualVideoDuration < targetDuration - 0.1) {
957
+ const extendedPath = resolve(outputDir, `scene-${sceneNum}-extended.mp4`);
958
+ await extendVideoNaturally(videoPath, targetDuration, extendedPath);
959
+ await unlink(videoPath);
960
+ await rename(extendedPath, videoPath);
961
+ }
962
+ result.regeneratedScenes.push(sceneNum);
963
+ }
964
+ else {
965
+ result.failedScenes.push(sceneNum);
966
+ }
967
+ }
968
+ catch {
969
+ result.failedScenes.push(sceneNum);
970
+ }
971
+ }
972
+ else {
973
+ result.failedScenes.push(sceneNum);
974
+ }
975
+ }
976
+ else {
977
+ // Runway
978
+ const runway = new RunwayProvider();
979
+ await runway.initialize({ apiKey: videoApiKey });
980
+ const ext = extname(imagePath).toLowerCase().slice(1);
981
+ const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
982
+ const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
983
+ const aspectRatio = options.aspectRatio === "1:1" ? "16:9" : (options.aspectRatio || "16:9");
984
+ const taskResult = await generateVideoWithRetryRunway(runway, segment, referenceImage, { duration: videoDuration, aspectRatio }, maxRetries);
985
+ if (taskResult) {
986
+ try {
987
+ const waitResult = await runway.waitForCompletion(taskResult.taskId, undefined, 300000);
988
+ if (waitResult.status === "completed" && waitResult.videoUrl) {
989
+ const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
990
+ await writeFile(videoPath, buffer);
991
+ // Extend video to match narration duration if needed
992
+ const targetDuration = segment.duration;
993
+ const actualVideoDuration = await getVideoDuration(videoPath);
994
+ if (actualVideoDuration < targetDuration - 0.1) {
995
+ const extendedPath = resolve(outputDir, `scene-${sceneNum}-extended.mp4`);
996
+ await extendVideoNaturally(videoPath, targetDuration, extendedPath);
997
+ await unlink(videoPath);
998
+ await rename(extendedPath, videoPath);
999
+ }
1000
+ result.regeneratedScenes.push(sceneNum);
1001
+ }
1002
+ else {
1003
+ result.failedScenes.push(sceneNum);
1004
+ }
1005
+ }
1006
+ catch {
1007
+ result.failedScenes.push(sceneNum);
1008
+ }
1009
+ }
1010
+ else {
1011
+ result.failedScenes.push(sceneNum);
1012
+ }
1013
+ }
1014
+ }
1015
+ }
1016
+ result.success = result.failedScenes.length === 0;
1017
+ return result;
1018
+ }
1019
+ catch (error) {
1020
+ return {
1021
+ ...result,
1022
+ error: error instanceof Error ? error.message : String(error),
1023
+ };
1024
+ }
1025
+ }
1026
+ /* CLI command registration moved to ai-script-pipeline-cli.ts */
1027
+ //# sourceMappingURL=ai-script-pipeline.js.map