@vibeframe/cli 0.54.0 → 0.55.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 (481) hide show
  1. package/dist/index.js +63452 -233
  2. package/package.json +11 -6
  3. package/dist/agent/adapters/claude.d.ts +0 -15
  4. package/dist/agent/adapters/claude.d.ts.map +0 -1
  5. package/dist/agent/adapters/claude.js +0 -119
  6. package/dist/agent/adapters/claude.js.map +0 -1
  7. package/dist/agent/adapters/gemini.d.ts +0 -15
  8. package/dist/agent/adapters/gemini.d.ts.map +0 -1
  9. package/dist/agent/adapters/gemini.js +0 -132
  10. package/dist/agent/adapters/gemini.js.map +0 -1
  11. package/dist/agent/adapters/index.d.ts +0 -28
  12. package/dist/agent/adapters/index.d.ts.map +0 -1
  13. package/dist/agent/adapters/index.js +0 -43
  14. package/dist/agent/adapters/index.js.map +0 -1
  15. package/dist/agent/adapters/ollama.d.ts +0 -20
  16. package/dist/agent/adapters/ollama.d.ts.map +0 -1
  17. package/dist/agent/adapters/ollama.js +0 -186
  18. package/dist/agent/adapters/ollama.js.map +0 -1
  19. package/dist/agent/adapters/openai.d.ts +0 -15
  20. package/dist/agent/adapters/openai.d.ts.map +0 -1
  21. package/dist/agent/adapters/openai.js +0 -92
  22. package/dist/agent/adapters/openai.js.map +0 -1
  23. package/dist/agent/adapters/openrouter.d.ts +0 -16
  24. package/dist/agent/adapters/openrouter.d.ts.map +0 -1
  25. package/dist/agent/adapters/openrouter.js +0 -100
  26. package/dist/agent/adapters/openrouter.js.map +0 -1
  27. package/dist/agent/adapters/openrouter.test.d.ts +0 -5
  28. package/dist/agent/adapters/openrouter.test.d.ts.map +0 -1
  29. package/dist/agent/adapters/openrouter.test.js +0 -73
  30. package/dist/agent/adapters/openrouter.test.js.map +0 -1
  31. package/dist/agent/adapters/xai.d.ts +0 -15
  32. package/dist/agent/adapters/xai.d.ts.map +0 -1
  33. package/dist/agent/adapters/xai.js +0 -95
  34. package/dist/agent/adapters/xai.js.map +0 -1
  35. package/dist/agent/index.d.ts +0 -69
  36. package/dist/agent/index.d.ts.map +0 -1
  37. package/dist/agent/index.js +0 -180
  38. package/dist/agent/index.js.map +0 -1
  39. package/dist/agent/memory/index.d.ts +0 -70
  40. package/dist/agent/memory/index.d.ts.map +0 -1
  41. package/dist/agent/memory/index.js +0 -132
  42. package/dist/agent/memory/index.js.map +0 -1
  43. package/dist/agent/prompts/system.d.ts +0 -6
  44. package/dist/agent/prompts/system.d.ts.map +0 -1
  45. package/dist/agent/prompts/system.js +0 -103
  46. package/dist/agent/prompts/system.js.map +0 -1
  47. package/dist/agent/tools/ai-editing.d.ts +0 -15
  48. package/dist/agent/tools/ai-editing.d.ts.map +0 -1
  49. package/dist/agent/tools/ai-editing.js +0 -763
  50. package/dist/agent/tools/ai-editing.js.map +0 -1
  51. package/dist/agent/tools/ai-generation.d.ts +0 -13
  52. package/dist/agent/tools/ai-generation.d.ts.map +0 -1
  53. package/dist/agent/tools/ai-generation.js +0 -973
  54. package/dist/agent/tools/ai-generation.js.map +0 -1
  55. package/dist/agent/tools/ai-pipeline.d.ts +0 -14
  56. package/dist/agent/tools/ai-pipeline.d.ts.map +0 -1
  57. package/dist/agent/tools/ai-pipeline.js +0 -961
  58. package/dist/agent/tools/ai-pipeline.js.map +0 -1
  59. package/dist/agent/tools/ai.d.ts +0 -13
  60. package/dist/agent/tools/ai.d.ts.map +0 -1
  61. package/dist/agent/tools/ai.js +0 -19
  62. package/dist/agent/tools/ai.js.map +0 -1
  63. package/dist/agent/tools/batch.d.ts +0 -6
  64. package/dist/agent/tools/batch.d.ts.map +0 -1
  65. package/dist/agent/tools/batch.js +0 -383
  66. package/dist/agent/tools/batch.js.map +0 -1
  67. package/dist/agent/tools/e2e.test.d.ts +0 -26
  68. package/dist/agent/tools/e2e.test.d.ts.map +0 -1
  69. package/dist/agent/tools/e2e.test.js +0 -397
  70. package/dist/agent/tools/e2e.test.js.map +0 -1
  71. package/dist/agent/tools/export.d.ts +0 -6
  72. package/dist/agent/tools/export.d.ts.map +0 -1
  73. package/dist/agent/tools/export.js +0 -171
  74. package/dist/agent/tools/export.js.map +0 -1
  75. package/dist/agent/tools/filesystem.d.ts +0 -6
  76. package/dist/agent/tools/filesystem.d.ts.map +0 -1
  77. package/dist/agent/tools/filesystem.js +0 -212
  78. package/dist/agent/tools/filesystem.js.map +0 -1
  79. package/dist/agent/tools/index.d.ts +0 -66
  80. package/dist/agent/tools/index.d.ts.map +0 -1
  81. package/dist/agent/tools/index.js +0 -123
  82. package/dist/agent/tools/index.js.map +0 -1
  83. package/dist/agent/tools/integration.test.d.ts +0 -11
  84. package/dist/agent/tools/integration.test.d.ts.map +0 -1
  85. package/dist/agent/tools/integration.test.js +0 -659
  86. package/dist/agent/tools/integration.test.js.map +0 -1
  87. package/dist/agent/tools/media.d.ts +0 -6
  88. package/dist/agent/tools/media.d.ts.map +0 -1
  89. package/dist/agent/tools/media.js +0 -616
  90. package/dist/agent/tools/media.js.map +0 -1
  91. package/dist/agent/tools/project.d.ts +0 -6
  92. package/dist/agent/tools/project.d.ts.map +0 -1
  93. package/dist/agent/tools/project.js +0 -284
  94. package/dist/agent/tools/project.js.map +0 -1
  95. package/dist/agent/tools/scene.d.ts +0 -17
  96. package/dist/agent/tools/scene.d.ts.map +0 -1
  97. package/dist/agent/tools/scene.js +0 -263
  98. package/dist/agent/tools/scene.js.map +0 -1
  99. package/dist/agent/tools/scene.test.d.ts +0 -2
  100. package/dist/agent/tools/scene.test.d.ts.map +0 -1
  101. package/dist/agent/tools/scene.test.js +0 -139
  102. package/dist/agent/tools/scene.test.js.map +0 -1
  103. package/dist/agent/tools/timeline.d.ts +0 -6
  104. package/dist/agent/tools/timeline.d.ts.map +0 -1
  105. package/dist/agent/tools/timeline.js +0 -875
  106. package/dist/agent/tools/timeline.js.map +0 -1
  107. package/dist/agent/types.d.ts +0 -59
  108. package/dist/agent/types.d.ts.map +0 -1
  109. package/dist/agent/types.js +0 -5
  110. package/dist/agent/types.js.map +0 -1
  111. package/dist/commands/_shared/scene-html-emit.d.ts +0 -120
  112. package/dist/commands/_shared/scene-html-emit.d.ts.map +0 -1
  113. package/dist/commands/_shared/scene-html-emit.js +0 -422
  114. package/dist/commands/_shared/scene-html-emit.js.map +0 -1
  115. package/dist/commands/_shared/scene-html-emit.test.d.ts +0 -2
  116. package/dist/commands/_shared/scene-html-emit.test.d.ts.map +0 -1
  117. package/dist/commands/_shared/scene-html-emit.test.js +0 -396
  118. package/dist/commands/_shared/scene-html-emit.test.js.map +0 -1
  119. package/dist/commands/_shared/scene-lint.d.ts +0 -110
  120. package/dist/commands/_shared/scene-lint.d.ts.map +0 -1
  121. package/dist/commands/_shared/scene-lint.js +0 -207
  122. package/dist/commands/_shared/scene-lint.js.map +0 -1
  123. package/dist/commands/_shared/scene-lint.test.d.ts +0 -2
  124. package/dist/commands/_shared/scene-lint.test.d.ts.map +0 -1
  125. package/dist/commands/_shared/scene-lint.test.js +0 -208
  126. package/dist/commands/_shared/scene-lint.test.js.map +0 -1
  127. package/dist/commands/_shared/scene-project.d.ts +0 -101
  128. package/dist/commands/_shared/scene-project.d.ts.map +0 -1
  129. package/dist/commands/_shared/scene-project.js +0 -281
  130. package/dist/commands/_shared/scene-project.js.map +0 -1
  131. package/dist/commands/_shared/scene-project.test.d.ts +0 -2
  132. package/dist/commands/_shared/scene-project.test.d.ts.map +0 -1
  133. package/dist/commands/_shared/scene-project.test.js +0 -203
  134. package/dist/commands/_shared/scene-project.test.js.map +0 -1
  135. package/dist/commands/_shared/scene-render.d.ts +0 -79
  136. package/dist/commands/_shared/scene-render.d.ts.map +0 -1
  137. package/dist/commands/_shared/scene-render.js +0 -138
  138. package/dist/commands/_shared/scene-render.js.map +0 -1
  139. package/dist/commands/_shared/scene-render.test.d.ts +0 -2
  140. package/dist/commands/_shared/scene-render.test.d.ts.map +0 -1
  141. package/dist/commands/_shared/scene-render.test.js +0 -145
  142. package/dist/commands/_shared/scene-render.test.js.map +0 -1
  143. package/dist/commands/_shared/segments-to-scenes.d.ts +0 -85
  144. package/dist/commands/_shared/segments-to-scenes.d.ts.map +0 -1
  145. package/dist/commands/_shared/segments-to-scenes.js +0 -202
  146. package/dist/commands/_shared/segments-to-scenes.js.map +0 -1
  147. package/dist/commands/_shared/segments-to-scenes.test.d.ts +0 -2
  148. package/dist/commands/_shared/segments-to-scenes.test.d.ts.map +0 -1
  149. package/dist/commands/_shared/segments-to-scenes.test.js +0 -219
  150. package/dist/commands/_shared/segments-to-scenes.test.js.map +0 -1
  151. package/dist/commands/_shared/tts-resolve.d.ts +0 -75
  152. package/dist/commands/_shared/tts-resolve.d.ts.map +0 -1
  153. package/dist/commands/_shared/tts-resolve.js +0 -89
  154. package/dist/commands/_shared/tts-resolve.js.map +0 -1
  155. package/dist/commands/_shared/tts-resolve.test.d.ts +0 -6
  156. package/dist/commands/_shared/tts-resolve.test.d.ts.map +0 -1
  157. package/dist/commands/_shared/tts-resolve.test.js +0 -147
  158. package/dist/commands/_shared/tts-resolve.test.js.map +0 -1
  159. package/dist/commands/agent.d.ts +0 -21
  160. package/dist/commands/agent.d.ts.map +0 -1
  161. package/dist/commands/agent.js +0 -285
  162. package/dist/commands/agent.js.map +0 -1
  163. package/dist/commands/ai-analyze.d.ts +0 -106
  164. package/dist/commands/ai-analyze.d.ts.map +0 -1
  165. package/dist/commands/ai-analyze.js +0 -320
  166. package/dist/commands/ai-analyze.js.map +0 -1
  167. package/dist/commands/ai-animated-caption.d.ts +0 -64
  168. package/dist/commands/ai-animated-caption.d.ts.map +0 -1
  169. package/dist/commands/ai-animated-caption.js +0 -272
  170. package/dist/commands/ai-animated-caption.js.map +0 -1
  171. package/dist/commands/ai-audio.d.ts +0 -98
  172. package/dist/commands/ai-audio.d.ts.map +0 -1
  173. package/dist/commands/ai-audio.js +0 -1059
  174. package/dist/commands/ai-audio.js.map +0 -1
  175. package/dist/commands/ai-broll.d.ts +0 -15
  176. package/dist/commands/ai-broll.d.ts.map +0 -1
  177. package/dist/commands/ai-broll.js +0 -421
  178. package/dist/commands/ai-broll.js.map +0 -1
  179. package/dist/commands/ai-edit-cli.d.ts +0 -14
  180. package/dist/commands/ai-edit-cli.d.ts.map +0 -1
  181. package/dist/commands/ai-edit-cli.js +0 -585
  182. package/dist/commands/ai-edit-cli.js.map +0 -1
  183. package/dist/commands/ai-edit.d.ts +0 -398
  184. package/dist/commands/ai-edit.d.ts.map +0 -1
  185. package/dist/commands/ai-edit.js +0 -1060
  186. package/dist/commands/ai-edit.js.map +0 -1
  187. package/dist/commands/ai-fill-gaps.d.ts +0 -14
  188. package/dist/commands/ai-fill-gaps.d.ts.map +0 -1
  189. package/dist/commands/ai-fill-gaps.js +0 -447
  190. package/dist/commands/ai-fill-gaps.js.map +0 -1
  191. package/dist/commands/ai-helpers.d.ts +0 -20
  192. package/dist/commands/ai-helpers.d.ts.map +0 -1
  193. package/dist/commands/ai-helpers.js +0 -59
  194. package/dist/commands/ai-helpers.js.map +0 -1
  195. package/dist/commands/ai-highlights.d.ts +0 -127
  196. package/dist/commands/ai-highlights.d.ts.map +0 -1
  197. package/dist/commands/ai-highlights.js +0 -1053
  198. package/dist/commands/ai-highlights.js.map +0 -1
  199. package/dist/commands/ai-image.d.ts +0 -76
  200. package/dist/commands/ai-image.d.ts.map +0 -1
  201. package/dist/commands/ai-image.js +0 -858
  202. package/dist/commands/ai-image.js.map +0 -1
  203. package/dist/commands/ai-motion.d.ts +0 -50
  204. package/dist/commands/ai-motion.d.ts.map +0 -1
  205. package/dist/commands/ai-motion.js +0 -297
  206. package/dist/commands/ai-motion.js.map +0 -1
  207. package/dist/commands/ai-narrate.d.ts +0 -66
  208. package/dist/commands/ai-narrate.d.ts.map +0 -1
  209. package/dist/commands/ai-narrate.js +0 -332
  210. package/dist/commands/ai-narrate.js.map +0 -1
  211. package/dist/commands/ai-review.d.ts +0 -57
  212. package/dist/commands/ai-review.d.ts.map +0 -1
  213. package/dist/commands/ai-review.js +0 -270
  214. package/dist/commands/ai-review.js.map +0 -1
  215. package/dist/commands/ai-script-pipeline-cli.d.ts +0 -9
  216. package/dist/commands/ai-script-pipeline-cli.d.ts.map +0 -1
  217. package/dist/commands/ai-script-pipeline-cli.js +0 -502
  218. package/dist/commands/ai-script-pipeline-cli.js.map +0 -1
  219. package/dist/commands/ai-script-pipeline.d.ts +0 -305
  220. package/dist/commands/ai-script-pipeline.d.ts.map +0 -1
  221. package/dist/commands/ai-script-pipeline.js +0 -1546
  222. package/dist/commands/ai-script-pipeline.js.map +0 -1
  223. package/dist/commands/ai-suggest-edit.d.ts +0 -14
  224. package/dist/commands/ai-suggest-edit.d.ts.map +0 -1
  225. package/dist/commands/ai-suggest-edit.js +0 -215
  226. package/dist/commands/ai-suggest-edit.js.map +0 -1
  227. package/dist/commands/ai-video-fx.d.ts +0 -14
  228. package/dist/commands/ai-video-fx.d.ts.map +0 -1
  229. package/dist/commands/ai-video-fx.js +0 -396
  230. package/dist/commands/ai-video-fx.js.map +0 -1
  231. package/dist/commands/ai-video.d.ts +0 -91
  232. package/dist/commands/ai-video.d.ts.map +0 -1
  233. package/dist/commands/ai-video.js +0 -1055
  234. package/dist/commands/ai-video.js.map +0 -1
  235. package/dist/commands/ai-viral.d.ts +0 -15
  236. package/dist/commands/ai-viral.d.ts.map +0 -1
  237. package/dist/commands/ai-viral.js +0 -507
  238. package/dist/commands/ai-viral.js.map +0 -1
  239. package/dist/commands/ai-visual-fx.d.ts +0 -14
  240. package/dist/commands/ai-visual-fx.d.ts.map +0 -1
  241. package/dist/commands/ai-visual-fx.js +0 -521
  242. package/dist/commands/ai-visual-fx.js.map +0 -1
  243. package/dist/commands/ai.d.ts +0 -38
  244. package/dist/commands/ai.d.ts.map +0 -1
  245. package/dist/commands/ai.js +0 -225
  246. package/dist/commands/ai.js.map +0 -1
  247. package/dist/commands/ai.test.d.ts +0 -2
  248. package/dist/commands/ai.test.d.ts.map +0 -1
  249. package/dist/commands/ai.test.js +0 -557
  250. package/dist/commands/ai.test.js.map +0 -1
  251. package/dist/commands/analyze.d.ts +0 -16
  252. package/dist/commands/analyze.d.ts.map +0 -1
  253. package/dist/commands/analyze.js +0 -247
  254. package/dist/commands/analyze.js.map +0 -1
  255. package/dist/commands/audio.d.ts +0 -18
  256. package/dist/commands/audio.d.ts.map +0 -1
  257. package/dist/commands/audio.js +0 -539
  258. package/dist/commands/audio.js.map +0 -1
  259. package/dist/commands/batch.d.ts +0 -3
  260. package/dist/commands/batch.d.ts.map +0 -1
  261. package/dist/commands/batch.js +0 -419
  262. package/dist/commands/batch.js.map +0 -1
  263. package/dist/commands/batch.test.d.ts +0 -2
  264. package/dist/commands/batch.test.d.ts.map +0 -1
  265. package/dist/commands/batch.test.js +0 -203
  266. package/dist/commands/batch.test.js.map +0 -1
  267. package/dist/commands/context.d.ts +0 -7
  268. package/dist/commands/context.d.ts.map +0 -1
  269. package/dist/commands/context.js +0 -66
  270. package/dist/commands/context.js.map +0 -1
  271. package/dist/commands/demo.d.ts +0 -7
  272. package/dist/commands/demo.d.ts.map +0 -1
  273. package/dist/commands/demo.js +0 -192
  274. package/dist/commands/demo.js.map +0 -1
  275. package/dist/commands/detect.d.ts +0 -56
  276. package/dist/commands/detect.d.ts.map +0 -1
  277. package/dist/commands/detect.js +0 -464
  278. package/dist/commands/detect.js.map +0 -1
  279. package/dist/commands/doctor.d.ts +0 -6
  280. package/dist/commands/doctor.d.ts.map +0 -1
  281. package/dist/commands/doctor.js +0 -322
  282. package/dist/commands/doctor.js.map +0 -1
  283. package/dist/commands/edit-cmd.d.ts +0 -112
  284. package/dist/commands/edit-cmd.d.ts.map +0 -1
  285. package/dist/commands/edit-cmd.js +0 -1041
  286. package/dist/commands/edit-cmd.js.map +0 -1
  287. package/dist/commands/export.d.ts +0 -61
  288. package/dist/commands/export.d.ts.map +0 -1
  289. package/dist/commands/export.js +0 -980
  290. package/dist/commands/export.js.map +0 -1
  291. package/dist/commands/export.test.d.ts +0 -2
  292. package/dist/commands/export.test.d.ts.map +0 -1
  293. package/dist/commands/export.test.js +0 -107
  294. package/dist/commands/export.test.js.map +0 -1
  295. package/dist/commands/generate.d.ts +0 -84
  296. package/dist/commands/generate.d.ts.map +0 -1
  297. package/dist/commands/generate.js +0 -2030
  298. package/dist/commands/generate.js.map +0 -1
  299. package/dist/commands/media.d.ts +0 -3
  300. package/dist/commands/media.d.ts.map +0 -1
  301. package/dist/commands/media.js +0 -163
  302. package/dist/commands/media.js.map +0 -1
  303. package/dist/commands/output.d.ts +0 -51
  304. package/dist/commands/output.d.ts.map +0 -1
  305. package/dist/commands/output.js +0 -219
  306. package/dist/commands/output.js.map +0 -1
  307. package/dist/commands/output.test.d.ts +0 -2
  308. package/dist/commands/output.test.d.ts.map +0 -1
  309. package/dist/commands/output.test.js +0 -151
  310. package/dist/commands/output.test.js.map +0 -1
  311. package/dist/commands/pipeline.d.ts +0 -19
  312. package/dist/commands/pipeline.d.ts.map +0 -1
  313. package/dist/commands/pipeline.js +0 -345
  314. package/dist/commands/pipeline.js.map +0 -1
  315. package/dist/commands/project.d.ts +0 -3
  316. package/dist/commands/project.d.ts.map +0 -1
  317. package/dist/commands/project.js +0 -181
  318. package/dist/commands/project.js.map +0 -1
  319. package/dist/commands/project.test.d.ts +0 -2
  320. package/dist/commands/project.test.d.ts.map +0 -1
  321. package/dist/commands/project.test.js +0 -105
  322. package/dist/commands/project.test.js.map +0 -1
  323. package/dist/commands/run.d.ts +0 -15
  324. package/dist/commands/run.d.ts.map +0 -1
  325. package/dist/commands/run.js +0 -204
  326. package/dist/commands/run.js.map +0 -1
  327. package/dist/commands/sanitize.d.ts +0 -21
  328. package/dist/commands/sanitize.d.ts.map +0 -1
  329. package/dist/commands/sanitize.js +0 -56
  330. package/dist/commands/sanitize.js.map +0 -1
  331. package/dist/commands/scene.d.ts +0 -86
  332. package/dist/commands/scene.d.ts.map +0 -1
  333. package/dist/commands/scene.js +0 -719
  334. package/dist/commands/scene.js.map +0 -1
  335. package/dist/commands/scene.test.d.ts +0 -2
  336. package/dist/commands/scene.test.d.ts.map +0 -1
  337. package/dist/commands/scene.test.js +0 -219
  338. package/dist/commands/scene.test.js.map +0 -1
  339. package/dist/commands/schema.d.ts +0 -13
  340. package/dist/commands/schema.d.ts.map +0 -1
  341. package/dist/commands/schema.js +0 -218
  342. package/dist/commands/schema.js.map +0 -1
  343. package/dist/commands/setup.d.ts +0 -6
  344. package/dist/commands/setup.d.ts.map +0 -1
  345. package/dist/commands/setup.js +0 -461
  346. package/dist/commands/setup.js.map +0 -1
  347. package/dist/commands/timeline.d.ts +0 -3
  348. package/dist/commands/timeline.d.ts.map +0 -1
  349. package/dist/commands/timeline.js +0 -609
  350. package/dist/commands/timeline.js.map +0 -1
  351. package/dist/commands/timeline.test.d.ts +0 -2
  352. package/dist/commands/timeline.test.d.ts.map +0 -1
  353. package/dist/commands/timeline.test.js +0 -334
  354. package/dist/commands/timeline.test.js.map +0 -1
  355. package/dist/commands/validate.d.ts +0 -34
  356. package/dist/commands/validate.d.ts.map +0 -1
  357. package/dist/commands/validate.js +0 -70
  358. package/dist/commands/validate.js.map +0 -1
  359. package/dist/config/config.test.d.ts +0 -2
  360. package/dist/config/config.test.d.ts.map +0 -1
  361. package/dist/config/config.test.js +0 -164
  362. package/dist/config/config.test.js.map +0 -1
  363. package/dist/config/index.d.ts +0 -35
  364. package/dist/config/index.d.ts.map +0 -1
  365. package/dist/config/index.js +0 -101
  366. package/dist/config/index.js.map +0 -1
  367. package/dist/config/schema.d.ts +0 -52
  368. package/dist/config/schema.d.ts.map +0 -1
  369. package/dist/config/schema.js +0 -44
  370. package/dist/config/schema.js.map +0 -1
  371. package/dist/engine/index.d.ts +0 -3
  372. package/dist/engine/index.d.ts.map +0 -1
  373. package/dist/engine/index.js +0 -2
  374. package/dist/engine/index.js.map +0 -1
  375. package/dist/engine/project.d.ts +0 -84
  376. package/dist/engine/project.d.ts.map +0 -1
  377. package/dist/engine/project.js +0 -355
  378. package/dist/engine/project.js.map +0 -1
  379. package/dist/engine/project.test.d.ts +0 -2
  380. package/dist/engine/project.test.d.ts.map +0 -1
  381. package/dist/engine/project.test.js +0 -599
  382. package/dist/engine/project.test.js.map +0 -1
  383. package/dist/index.d.ts +0 -7
  384. package/dist/index.d.ts.map +0 -1
  385. package/dist/index.js.map +0 -1
  386. package/dist/pipeline/executor.d.ts +0 -19
  387. package/dist/pipeline/executor.d.ts.map +0 -1
  388. package/dist/pipeline/executor.js +0 -397
  389. package/dist/pipeline/executor.js.map +0 -1
  390. package/dist/pipeline/index.d.ts +0 -4
  391. package/dist/pipeline/index.d.ts.map +0 -1
  392. package/dist/pipeline/index.js +0 -2
  393. package/dist/pipeline/index.js.map +0 -1
  394. package/dist/pipeline/renderers/__tests__/html-template.test.d.ts +0 -2
  395. package/dist/pipeline/renderers/__tests__/html-template.test.d.ts.map +0 -1
  396. package/dist/pipeline/renderers/__tests__/html-template.test.js +0 -170
  397. package/dist/pipeline/renderers/__tests__/html-template.test.js.map +0 -1
  398. package/dist/pipeline/renderers/__tests__/integration.test.d.ts +0 -2
  399. package/dist/pipeline/renderers/__tests__/integration.test.d.ts.map +0 -1
  400. package/dist/pipeline/renderers/__tests__/integration.test.js +0 -175
  401. package/dist/pipeline/renderers/__tests__/integration.test.js.map +0 -1
  402. package/dist/pipeline/renderers/chrome.d.ts +0 -26
  403. package/dist/pipeline/renderers/chrome.d.ts.map +0 -1
  404. package/dist/pipeline/renderers/chrome.js +0 -48
  405. package/dist/pipeline/renderers/chrome.js.map +0 -1
  406. package/dist/pipeline/renderers/html-clips.d.ts +0 -20
  407. package/dist/pipeline/renderers/html-clips.d.ts.map +0 -1
  408. package/dist/pipeline/renderers/html-clips.js +0 -50
  409. package/dist/pipeline/renderers/html-clips.js.map +0 -1
  410. package/dist/pipeline/renderers/html-runtime.d.ts +0 -7
  411. package/dist/pipeline/renderers/html-runtime.d.ts.map +0 -1
  412. package/dist/pipeline/renderers/html-runtime.js +0 -80
  413. package/dist/pipeline/renderers/html-runtime.js.map +0 -1
  414. package/dist/pipeline/renderers/html-template.d.ts +0 -3
  415. package/dist/pipeline/renderers/html-template.d.ts.map +0 -1
  416. package/dist/pipeline/renderers/html-template.js +0 -36
  417. package/dist/pipeline/renderers/html-template.js.map +0 -1
  418. package/dist/pipeline/renderers/hyperframes.d.ts +0 -8
  419. package/dist/pipeline/renderers/hyperframes.d.ts.map +0 -1
  420. package/dist/pipeline/renderers/hyperframes.js +0 -63
  421. package/dist/pipeline/renderers/hyperframes.js.map +0 -1
  422. package/dist/pipeline/renderers/project-builder.d.ts +0 -8
  423. package/dist/pipeline/renderers/project-builder.d.ts.map +0 -1
  424. package/dist/pipeline/renderers/project-builder.js +0 -65
  425. package/dist/pipeline/renderers/project-builder.js.map +0 -1
  426. package/dist/pipeline/renderers/types.d.ts +0 -41
  427. package/dist/pipeline/renderers/types.d.ts.map +0 -1
  428. package/dist/pipeline/renderers/types.js +0 -11
  429. package/dist/pipeline/renderers/types.js.map +0 -1
  430. package/dist/pipeline/resolver.d.ts +0 -20
  431. package/dist/pipeline/resolver.d.ts.map +0 -1
  432. package/dist/pipeline/resolver.js +0 -74
  433. package/dist/pipeline/resolver.js.map +0 -1
  434. package/dist/pipeline/types.d.ts +0 -82
  435. package/dist/pipeline/types.d.ts.map +0 -1
  436. package/dist/pipeline/types.js +0 -8
  437. package/dist/pipeline/types.js.map +0 -1
  438. package/dist/utils/api-key.d.ts +0 -33
  439. package/dist/utils/api-key.d.ts.map +0 -1
  440. package/dist/utils/api-key.js +0 -228
  441. package/dist/utils/api-key.js.map +0 -1
  442. package/dist/utils/api-key.test.d.ts +0 -2
  443. package/dist/utils/api-key.test.d.ts.map +0 -1
  444. package/dist/utils/api-key.test.js +0 -35
  445. package/dist/utils/api-key.test.js.map +0 -1
  446. package/dist/utils/audio.d.ts +0 -23
  447. package/dist/utils/audio.d.ts.map +0 -1
  448. package/dist/utils/audio.js +0 -79
  449. package/dist/utils/audio.js.map +0 -1
  450. package/dist/utils/exec-safe.d.ts +0 -22
  451. package/dist/utils/exec-safe.d.ts.map +0 -1
  452. package/dist/utils/exec-safe.js +0 -62
  453. package/dist/utils/exec-safe.js.map +0 -1
  454. package/dist/utils/first-run.d.ts +0 -17
  455. package/dist/utils/first-run.d.ts.map +0 -1
  456. package/dist/utils/first-run.js +0 -73
  457. package/dist/utils/first-run.js.map +0 -1
  458. package/dist/utils/input-validation.d.ts +0 -27
  459. package/dist/utils/input-validation.d.ts.map +0 -1
  460. package/dist/utils/input-validation.js +0 -67
  461. package/dist/utils/input-validation.js.map +0 -1
  462. package/dist/utils/provider-resolver.d.ts +0 -19
  463. package/dist/utils/provider-resolver.d.ts.map +0 -1
  464. package/dist/utils/provider-resolver.js +0 -75
  465. package/dist/utils/provider-resolver.js.map +0 -1
  466. package/dist/utils/remotion.d.ts +0 -231
  467. package/dist/utils/remotion.d.ts.map +0 -1
  468. package/dist/utils/remotion.js +0 -879
  469. package/dist/utils/remotion.js.map +0 -1
  470. package/dist/utils/subtitle.d.ts +0 -65
  471. package/dist/utils/subtitle.d.ts.map +0 -1
  472. package/dist/utils/subtitle.js +0 -135
  473. package/dist/utils/subtitle.js.map +0 -1
  474. package/dist/utils/subtitle.test.d.ts +0 -2
  475. package/dist/utils/subtitle.test.d.ts.map +0 -1
  476. package/dist/utils/subtitle.test.js +0 -175
  477. package/dist/utils/subtitle.test.js.map +0 -1
  478. package/dist/utils/tty.d.ts +0 -45
  479. package/dist/utils/tty.d.ts.map +0 -1
  480. package/dist/utils/tty.js +0 -231
  481. package/dist/utils/tty.js.map +0 -1
@@ -1,2030 +0,0 @@
1
- /**
2
- * @module generate
3
- *
4
- * Top-level `vibe generate` command group for AI asset generation.
5
- *
6
- * Commands:
7
- * generate image - Generate image (Gemini, OpenAI, Grok, Runway)
8
- * generate video - Generate video (Kling, Runway, Veo, Grok)
9
- * generate speech - Text-to-speech (ElevenLabs)
10
- * generate sound-effect - Sound effects (ElevenLabs)
11
- * generate music - Music generation (ElevenLabs default, Replicate MusicGen)
12
- * generate music-status - Check music generation status
13
- * generate storyboard - Script-to-storyboard (Claude)
14
- * generate motion - Motion graphics (Claude/Gemini + Remotion)
15
- * generate thumbnail - Thumbnail generation/extraction
16
- * generate background - AI background generation (OpenAI)
17
- * generate video-status - Check video generation status (Grok/Runway/Kling)
18
- * generate video-cancel - Cancel video generation (Grok/Runway)
19
- * generate video-extend - Extend video (Kling/Veo)
20
- *
21
- * @dependencies OpenAI, Gemini, Runway, Kling, ElevenLabs, Replicate, Claude, FFmpeg
22
- */
23
- import { Command } from "commander";
24
- import { resolve, dirname, basename, extname } from "node:path";
25
- import { fileURLToPath } from "node:url";
26
- import { readFile, writeFile, mkdir } from "node:fs/promises";
27
- import { existsSync } from "node:fs";
28
- import chalk from "chalk";
29
- import ora from "ora";
30
- import imageSize from "image-size";
31
- import { GeminiProvider, OpenAIImageProvider, KlingProvider, RunwayProvider, ElevenLabsProvider, ReplicateProvider, ClaudeProvider, GrokProvider, } from "@vibeframe/ai-providers";
32
- import { requireApiKey, hasApiKey } from "../utils/api-key.js";
33
- import { hasTTY, prompt as promptText } from "../utils/tty.js";
34
- import { getApiKeyFromConfig } from "../config/index.js";
35
- import { sanitizeLLMResponse } from "./sanitize.js";
36
- import { isJsonMode, outputResult, log, exitWithError, usageError, apiError, generalError, authError, notFoundError } from "./output.js";
37
- import { commandExists } from "../utils/exec-safe.js";
38
- import { uploadToImgbb } from "./ai-script-pipeline.js";
39
- import { downloadVideo, formatTime } from "./ai-helpers.js";
40
- import { rejectControlChars, validateOutputPath } from "./validate.js";
41
- import { resolveProvider } from "../utils/provider-resolver.js";
42
- import { executeThumbnailBestFrame } from "./ai-image.js";
43
- import { registerMotionCommand } from "./ai-motion.js";
44
- // ── Helpers ──────────────────────────────────────────────────────────────────
45
- function getStatusColor(status) {
46
- switch (status) {
47
- case "completed":
48
- return chalk.green(status);
49
- case "processing":
50
- case "running":
51
- case "in_progress":
52
- return chalk.yellow(status);
53
- case "failed":
54
- case "error":
55
- return chalk.red(status);
56
- default:
57
- return chalk.gray(status);
58
- }
59
- }
60
- // ── Command group ────────────────────────────────────────────────────────────
61
- export const generateCommand = new Command("generate")
62
- .alias("gen")
63
- .description("Generate assets using AI (images, videos, speech, music, motion)")
64
- .addHelpText("after", `
65
- Examples:
66
- $ vibe generate image "a sunset over the ocean" -o sunset.png
67
- $ vibe generate image "logo design" -o logo.png -p openai
68
- $ vibe generate video "dancing cat" -o cat.mp4 # Grok (default, native audio)
69
- $ vibe generate video "city timelapse" -o city.mp4 -p kling # Kling
70
- $ vibe generate video "epic scene" -i frame.png -o out.mp4 -p runway # Image-to-video
71
- $ vibe generate speech "Hello world" -o hello.mp3
72
- $ vibe generate music "upbeat jazz" -o jazz.mp3 -d 30
73
- $ vibe generate motion "animated logo intro" -o intro.mp4 --render
74
-
75
- API Keys (per provider):
76
- GOOGLE_API_KEY Image (default), Veo video
77
- OPENAI_API_KEY Image (-p openai)
78
- XAI_API_KEY Grok image/video (default video)
79
- KLING_API_KEY Kling video (-p kling)
80
- RUNWAY_API_SECRET Runway video (-p runway)
81
- ELEVENLABS_API_KEY Speech, sound effects, music
82
- ANTHROPIC_API_KEY Storyboard, motion graphics
83
-
84
- Run 'vibe setup --show' to check API key status.
85
- Run 'vibe schema generate.<command>' for structured parameter info.
86
- `);
87
- // ============================================================================
88
- // 1. Image
89
- // ============================================================================
90
- generateCommand
91
- .command("image")
92
- .alias("img")
93
- .description("Generate image using AI (Gemini, DALL-E, or Runway)")
94
- .argument("[prompt]", "Image description prompt (interactive if omitted)")
95
- .option("-p, --provider <provider>", "Provider: gemini, openai, grok, runway (dalle is deprecated)", "gemini")
96
- .option("-k, --api-key <key>", "API key (or set env: OPENAI_API_KEY, GOOGLE_API_KEY)")
97
- .option("-o, --output <path>", "Output file path (downloads image)")
98
- .option("-s, --size <size>", "Image size (openai: 1024x1024, 1536x1024, 1024x1536)", "1024x1024")
99
- .option("-r, --ratio <ratio>", "Aspect ratio (gemini: 1:1, 1:4, 1:8, 4:1, 8:1, 16:9, 9:16, 3:4, 4:3, etc.)", "1:1")
100
- .option("-q, --quality <quality>", "Quality: standard, hd (openai only)", "standard")
101
- .option("--style <style>", "Style: vivid, natural (openai only)", "vivid")
102
- .option("-n, --count <n>", "Number of images to generate", "1")
103
- .option("-m, --model <model>", "Model. Gemini: flash, 3.1-flash, latest, pro. OpenAI: 1.5 (default), 2 (gpt-image-2)")
104
- .option("--dry-run", "Preview parameters without executing")
105
- .addHelpText("after", `
106
- Examples:
107
- $ vibe generate image "a sunset over the ocean" -o sunset.png
108
- $ vibe gen img "logo design" -o logo.png -p openai
109
- $ vibe gen img "landscape photo" -o wide.png -r 16:9
110
- $ vibe gen img "portrait" -o portrait.png -p gemini -m pro
111
- $ vibe gen img "product shot" --dry-run --json`)
112
- .action(async (prompt, options) => {
113
- try {
114
- // Interactive prompt if no argument provided
115
- if (!prompt) {
116
- if (hasTTY()) {
117
- prompt = await promptText(chalk.cyan("What would you like to generate? "));
118
- if (!prompt?.trim()) {
119
- exitWithError(usageError("Prompt is required."));
120
- }
121
- }
122
- else {
123
- exitWithError(usageError("Prompt argument is required.", "Usage: vibe generate image <prompt>"));
124
- }
125
- }
126
- rejectControlChars(prompt);
127
- if (options.output) {
128
- validateOutputPath(options.output);
129
- }
130
- // Auto-resolve provider if user didn't explicitly set one
131
- let provider = options.provider.toLowerCase();
132
- const validProviders = ["openai", "dalle", "gemini", "grok", "runway"];
133
- if (!validProviders.includes(provider)) {
134
- exitWithError(usageError(`Invalid provider: ${provider}`, `Available providers: openai, gemini, grok, runway`));
135
- }
136
- // Auto-fallback: if default provider's key is missing, find one that works
137
- const providerEnvMap = {
138
- gemini: "GOOGLE_API_KEY", openai: "OPENAI_API_KEY", grok: "XAI_API_KEY",
139
- };
140
- if (providerEnvMap[provider] && !hasApiKey(providerEnvMap[provider]) && !options.apiKey) {
141
- const resolved = resolveProvider("image");
142
- if (resolved) {
143
- log(chalk.dim(` ${provider} key not found. Using ${resolved.label} instead.`));
144
- provider = resolved.name;
145
- }
146
- }
147
- // Show deprecation warning for "dalle"
148
- if (provider === "dalle") {
149
- console.log(chalk.yellow('Warning: "dalle" is deprecated. Use "openai" instead.'));
150
- }
151
- // Dry-run check
152
- if (options.dryRun) {
153
- outputResult({ dryRun: true, command: "generate image", params: { prompt, provider, model: options.model, ratio: options.ratio, size: options.size, quality: options.quality, count: options.count, output: options.output } });
154
- return;
155
- }
156
- // Get API key based on provider
157
- const envKeyMap = {
158
- openai: "OPENAI_API_KEY",
159
- dalle: "OPENAI_API_KEY",
160
- gemini: "GOOGLE_API_KEY",
161
- grok: "XAI_API_KEY",
162
- runway: "RUNWAY_API_SECRET",
163
- };
164
- const providerNameMap = {
165
- openai: "OpenAI",
166
- dalle: "OpenAI",
167
- gemini: "Google",
168
- grok: "xAI Grok",
169
- runway: "Runway",
170
- };
171
- const envKey = envKeyMap[provider];
172
- const providerName = providerNameMap[provider];
173
- const apiKey = await requireApiKey(envKey, providerName, options.apiKey);
174
- const spinner = ora(`Generating image with ${providerName}...`).start();
175
- if (provider === "dalle" || provider === "openai") {
176
- const openaiImage = new OpenAIImageProvider();
177
- await openaiImage.initialize({ apiKey });
178
- const modelAlias = options.model;
179
- const openaiModel = modelAlias === "2" || modelAlias === "gpt-image-2"
180
- ? "gpt-image-2"
181
- : undefined;
182
- const result = await openaiImage.generateImage(prompt, {
183
- model: openaiModel,
184
- size: options.size,
185
- quality: options.quality,
186
- style: options.style,
187
- n: parseInt(options.count),
188
- });
189
- if (!result.success || !result.images) {
190
- spinner.fail(result.error || "Image generation failed");
191
- exitWithError(apiError(result.error || "Image generation failed", true));
192
- }
193
- const modelLabel = openaiModel === "gpt-image-2" ? "GPT Image 2" : "GPT Image 1.5";
194
- spinner.succeed(chalk.green(`Generated ${result.images.length} image(s) with OpenAI ${modelLabel}`));
195
- if (isJsonMode()) {
196
- const outputPath = options.output ? resolve(process.cwd(), options.output) : undefined;
197
- // Still save the file in JSON mode
198
- if (outputPath && result.images.length > 0) {
199
- const img = result.images[0];
200
- let buffer;
201
- if (img.url) {
202
- const response = await fetch(img.url);
203
- buffer = Buffer.from(await response.arrayBuffer());
204
- }
205
- else if (img.base64) {
206
- buffer = Buffer.from(img.base64, "base64");
207
- }
208
- else {
209
- throw new Error("No image data available");
210
- }
211
- await mkdir(dirname(outputPath), { recursive: true });
212
- await writeFile(outputPath, buffer);
213
- }
214
- outputResult({ success: true, provider: "openai", images: result.images.map(img => ({ url: img.url, revisedPrompt: img.revisedPrompt })), outputPath });
215
- return;
216
- }
217
- console.log();
218
- console.log(chalk.bold.cyan("Generated Images"));
219
- console.log(chalk.dim("─".repeat(60)));
220
- for (let i = 0; i < result.images.length; i++) {
221
- const img = result.images[i];
222
- console.log();
223
- if (img.url) {
224
- console.log(`${chalk.yellow(`[${i + 1}]`)} ${img.url}`);
225
- }
226
- else if (img.base64) {
227
- console.log(`${chalk.yellow(`[${i + 1}]`)} (base64 image data)`);
228
- }
229
- if (img.revisedPrompt) {
230
- console.log(chalk.dim(` Revised: ${img.revisedPrompt.slice(0, 100)}...`));
231
- }
232
- }
233
- console.log();
234
- // Save if output specified
235
- if (options.output && result.images.length > 0) {
236
- const img = result.images[0];
237
- const saveSpinner = ora("Saving image...").start();
238
- try {
239
- let buffer;
240
- if (img.url) {
241
- const response = await fetch(img.url);
242
- buffer = Buffer.from(await response.arrayBuffer());
243
- }
244
- else if (img.base64) {
245
- buffer = Buffer.from(img.base64, "base64");
246
- }
247
- else {
248
- throw new Error("No image data available");
249
- }
250
- const outputPath = resolve(process.cwd(), options.output);
251
- await mkdir(dirname(outputPath), { recursive: true });
252
- await writeFile(outputPath, buffer);
253
- saveSpinner.succeed(chalk.green(`Saved to: ${outputPath}`));
254
- }
255
- catch (err) {
256
- saveSpinner.fail(chalk.red(`Failed to save image: ${err instanceof Error ? err.message : err}`));
257
- }
258
- }
259
- }
260
- else if (provider === "gemini") {
261
- // Validate model name
262
- const validGeminiModels = ["flash", "3.1-flash", "latest", "pro"];
263
- if (options.model && !validGeminiModels.includes(options.model)) {
264
- console.warn(chalk.yellow(`Unknown model "${options.model}", using flash. Valid: ${validGeminiModels.join(", ")}`));
265
- options.model = "flash";
266
- }
267
- // Validate aspect ratio
268
- const validRatios = ["1:1", "1:4", "1:8", "2:3", "3:2", "3:4", "4:1", "4:3", "4:5", "5:4", "8:1", "9:16", "16:9", "21:9"];
269
- if (options.ratio && !validRatios.includes(options.ratio)) {
270
- exitWithError(usageError(`Invalid ratio "${options.ratio}". Valid: ${validRatios.join(", ")}`));
271
- }
272
- const gemini = new GeminiProvider();
273
- await gemini.initialize({ apiKey });
274
- const geminiModelNames = {
275
- flash: "Nano Banana",
276
- "3.1-flash": "Nano Banana 2",
277
- latest: "Nano Banana 2",
278
- pro: "Nano Banana Pro",
279
- };
280
- const modelLabel = geminiModelNames[options.model] || "Nano Banana";
281
- let result = await gemini.generateImage(prompt, {
282
- model: options.model,
283
- aspectRatio: options.ratio,
284
- });
285
- // Auto-fallback: if latest/3.1-flash fails, retry with flash
286
- let usedLabel = modelLabel;
287
- const fallbackModels = ["latest", "3.1-flash"];
288
- if (!result.success && options.model && fallbackModels.includes(options.model)) {
289
- spinner.text = `${chalk.dim(result.error || "Failed")} — retrying with Nano Banana (flash)...`;
290
- result = await gemini.generateImage(prompt, {
291
- model: "flash",
292
- aspectRatio: options.ratio,
293
- });
294
- usedLabel = "Nano Banana (fallback)";
295
- }
296
- if (!result.success || !result.images) {
297
- spinner.fail(result.error || "Image generation failed");
298
- exitWithError(apiError(result.error || "Image generation failed", true));
299
- }
300
- spinner.succeed(chalk.green(`Generated ${result.images.length} image(s) with Gemini (${usedLabel})`));
301
- if (isJsonMode()) {
302
- const outputPath = options.output ? resolve(process.cwd(), options.output) : undefined;
303
- if (outputPath && result.images.length > 0) {
304
- const img = result.images[0];
305
- const buffer = Buffer.from(img.base64, "base64");
306
- await mkdir(dirname(outputPath), { recursive: true });
307
- await writeFile(outputPath, buffer);
308
- }
309
- outputResult({ success: true, provider: "gemini", images: result.images.map((img) => ({ mimeType: img.mimeType })), outputPath });
310
- return;
311
- }
312
- console.log();
313
- console.log(chalk.bold.cyan("Generated Images"));
314
- console.log(chalk.dim("─".repeat(60)));
315
- for (let i = 0; i < result.images.length; i++) {
316
- const img = result.images[i];
317
- console.log();
318
- console.log(`${chalk.yellow(`[${i + 1}]`)} (base64 image, ${img.mimeType})`);
319
- }
320
- console.log();
321
- // Save if output specified
322
- if (options.output && result.images.length > 0) {
323
- const saveSpinner = ora("Saving image...").start();
324
- try {
325
- const img = result.images[0];
326
- const buffer = Buffer.from(img.base64, "base64");
327
- const outputPath = resolve(process.cwd(), options.output);
328
- await mkdir(dirname(outputPath), { recursive: true });
329
- await writeFile(outputPath, buffer);
330
- saveSpinner.succeed(chalk.green(`Saved to: ${outputPath}`));
331
- }
332
- catch (err) {
333
- saveSpinner.fail(chalk.red(`Failed to save image: ${err instanceof Error ? err.message : err}`));
334
- }
335
- }
336
- else {
337
- console.log(chalk.yellow("Use -o to save the generated image to a file"));
338
- }
339
- }
340
- else if (provider === "grok") {
341
- const grok = new GrokProvider();
342
- await grok.initialize({ apiKey });
343
- // Validate aspect ratio for Grok
344
- const validGrokRatios = ["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "2:1", "1:2", "19.5:9", "9:19.5", "20:9", "9:20", "auto"];
345
- if (options.ratio && !validGrokRatios.includes(options.ratio)) {
346
- console.warn(chalk.yellow(`Unknown ratio "${options.ratio}" for Grok, using 1:1. Valid: ${validGrokRatios.join(", ")}`));
347
- options.ratio = "1:1";
348
- }
349
- const result = await grok.generateImage(prompt, {
350
- aspectRatio: options.ratio || "1:1",
351
- n: parseInt(options.count),
352
- });
353
- if (!result.success || !result.images) {
354
- spinner.fail(result.error || "Image generation failed");
355
- exitWithError(apiError(result.error || "Image generation failed", true));
356
- }
357
- spinner.succeed(chalk.green(`Generated ${result.images.length} image(s) with xAI Grok`));
358
- if (isJsonMode()) {
359
- const outputPath = options.output ? resolve(process.cwd(), options.output) : undefined;
360
- if (outputPath && result.images.length > 0) {
361
- const img = result.images[0];
362
- let buffer;
363
- if (img.url) {
364
- const response = await fetch(img.url);
365
- buffer = Buffer.from(await response.arrayBuffer());
366
- }
367
- else if (img.base64) {
368
- buffer = Buffer.from(img.base64, "base64");
369
- }
370
- else {
371
- throw new Error("No image data available");
372
- }
373
- await mkdir(dirname(outputPath), { recursive: true });
374
- await writeFile(outputPath, buffer);
375
- }
376
- outputResult({ success: true, provider: "grok", images: result.images.map(img => ({ url: img.url })), outputPath });
377
- return;
378
- }
379
- console.log();
380
- console.log(chalk.bold.cyan("Generated Images"));
381
- console.log(chalk.dim("─".repeat(60)));
382
- for (let i = 0; i < result.images.length; i++) {
383
- const img = result.images[i];
384
- console.log();
385
- if (img.url) {
386
- console.log(`${chalk.yellow(`[${i + 1}]`)} ${img.url}`);
387
- }
388
- else if (img.base64) {
389
- console.log(`${chalk.yellow(`[${i + 1}]`)} (base64 image data)`);
390
- }
391
- }
392
- console.log();
393
- // Save if output specified
394
- if (options.output && result.images.length > 0) {
395
- const img = result.images[0];
396
- const saveSpinner = ora("Saving image...").start();
397
- try {
398
- let buffer;
399
- if (img.url) {
400
- const response = await fetch(img.url);
401
- buffer = Buffer.from(await response.arrayBuffer());
402
- }
403
- else if (img.base64) {
404
- buffer = Buffer.from(img.base64, "base64");
405
- }
406
- else {
407
- throw new Error("No image data available");
408
- }
409
- const outputPath = resolve(process.cwd(), options.output);
410
- await mkdir(dirname(outputPath), { recursive: true });
411
- await writeFile(outputPath, buffer);
412
- saveSpinner.succeed(chalk.green(`Saved to: ${outputPath}`));
413
- }
414
- catch (err) {
415
- saveSpinner.fail(chalk.red(`Failed to save image: ${err instanceof Error ? err.message : err}`));
416
- }
417
- }
418
- }
419
- else if (provider === "runway") {
420
- const { spawn } = await import("child_process");
421
- const __filename = fileURLToPath(import.meta.url);
422
- const __dirname = dirname(__filename);
423
- const scriptPath = resolve(__dirname, "../../../../.claude/skills/runway-video/scripts/image.py");
424
- if (!options.output) {
425
- spinner.fail("Output path required for Runway");
426
- exitWithError(usageError("Output path required for Runway. Use -o option."));
427
- }
428
- const outputPath = resolve(process.cwd(), options.output);
429
- const args = [scriptPath, prompt, "-o", outputPath, "-r", options.ratio || "16:9"];
430
- spinner.text = "Generating image with Runway (gemini_2.5_flash)...";
431
- await new Promise((resolvePromise, reject) => {
432
- const proc = spawn("python3", args, {
433
- env: { ...process.env, RUNWAY_API_SECRET: apiKey },
434
- stdio: ["ignore", "pipe", "pipe"],
435
- });
436
- let stdout = "";
437
- let stderr = "";
438
- proc.stdout.on("data", (data) => {
439
- stdout += data.toString();
440
- });
441
- proc.stderr.on("data", (data) => {
442
- stderr += data.toString();
443
- });
444
- proc.on("close", (code) => {
445
- if (code === 0) {
446
- if (isJsonMode()) {
447
- outputResult({ success: true, provider: "runway", images: [{ format: "file" }], outputPath });
448
- }
449
- else {
450
- spinner.succeed(chalk.green("Generated image with Runway"));
451
- console.log(chalk.dim(stdout.trim()));
452
- }
453
- resolvePromise();
454
- }
455
- else {
456
- spinner.fail(chalk.red("Runway image generation failed"));
457
- console.error(chalk.red(stderr || stdout));
458
- reject(new Error("Runway generation failed"));
459
- }
460
- });
461
- proc.on("error", (err) => {
462
- spinner.fail(chalk.red("Failed to run Runway script"));
463
- reject(err);
464
- });
465
- });
466
- }
467
- }
468
- catch (error) {
469
- exitWithError(apiError(`Image generation failed: ${error.message}`));
470
- }
471
- });
472
- // ============================================================================
473
- // 2. Video (merged: ai video + ai kling, unified via --provider)
474
- // ============================================================================
475
- generateCommand
476
- .command("video")
477
- .alias("vid")
478
- .description("Generate video using AI (Kling, Runway, Veo, or Grok)")
479
- .argument("[prompt]", "Text prompt describing the video (interactive if omitted)")
480
- .option("-p, --provider <provider>", "Provider: grok (default), kling, runway, veo", "grok")
481
- .option("-k, --api-key <key>", "API key (or set XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY / GOOGLE_API_KEY env)")
482
- .option("-o, --output <path>", "Output file path (downloads video)")
483
- .option("-i, --image <path>", "Reference image for image-to-video")
484
- .option("-d, --duration <sec>", "Duration: 5 or 10 seconds", "5")
485
- .option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, or 1:1 (auto-detected from image if omitted)")
486
- .option("-s, --seed <number>", "Random seed for reproducibility (Runway only)")
487
- .option("-m, --mode <mode>", "Generation mode: std or pro (Kling only)", "std")
488
- .option("-n, --negative <prompt>", "Negative prompt - what to avoid (Kling/Veo)")
489
- .option("--resolution <res>", "Video resolution: 720p, 1080p, 4k (Veo only)")
490
- .option("--last-frame <path>", "Last frame image for frame interpolation (Veo only)")
491
- .option("--ref-images <paths...>", "Reference images for character consistency (Veo 3.1 only, max 3)")
492
- .option("--person <mode>", "Person generation: allow_all, allow_adult (Veo only)")
493
- .option("--veo-model <model>", "Veo model: 3.0, 3.1, 3.1-fast (default: 3.1-fast)", "3.1-fast")
494
- .option("--runway-model <model>", "Runway model: gen4.5 (default, text+image-to-video), gen4_turbo (image-to-video only)", "gen4.5")
495
- .option("--no-wait", "Start generation and return task ID without waiting")
496
- .option("--dry-run", "Preview parameters without executing")
497
- .addHelpText("after", `
498
- Examples:
499
- $ vibe generate video "dancing cat" -o cat.mp4 # Grok (default)
500
- $ vibe gen vid "city timelapse" -o city.mp4 -p kling # Kling
501
- $ vibe gen vid "epic scene" -i frame.png -o out.mp4 -p runway # Image-to-video
502
- $ vibe gen vid "ocean waves" -o waves.mp4 -p veo --resolution 1080p # Veo
503
- $ vibe gen vid "sunset" -o sun.mp4 -d 10 --dry-run --json`)
504
- .action(async (prompt, options) => {
505
- try {
506
- // Interactive prompt if no argument provided
507
- if (!prompt) {
508
- if (hasTTY()) {
509
- prompt = await promptText(chalk.cyan("Describe your video: "));
510
- if (!prompt?.trim()) {
511
- exitWithError(usageError("Prompt is required."));
512
- }
513
- }
514
- else {
515
- exitWithError(usageError("Prompt argument is required.", "Usage: vibe generate video <prompt>"));
516
- }
517
- }
518
- rejectControlChars(prompt);
519
- if (options.output) {
520
- validateOutputPath(options.output);
521
- }
522
- let provider = options.provider.toLowerCase();
523
- const validProviders = ["runway", "kling", "veo", "grok"];
524
- if (!validProviders.includes(provider)) {
525
- exitWithError(usageError(`Invalid provider: ${provider}`, `Available providers: ${validProviders.join(", ")}`));
526
- }
527
- // Auto-fallback: if default provider's key is missing, find one that works
528
- const videoEnvMap = {
529
- grok: "XAI_API_KEY", veo: "GOOGLE_API_KEY", kling: "KLING_API_KEY", runway: "RUNWAY_API_SECRET",
530
- };
531
- if (videoEnvMap[provider] && !hasApiKey(videoEnvMap[provider]) && !options.apiKey) {
532
- const resolved = resolveProvider("video");
533
- if (resolved) {
534
- log(chalk.dim(` ${provider} key not found. Using ${resolved.label} instead.`));
535
- provider = resolved.name;
536
- }
537
- }
538
- // Read image early so we can auto-detect aspect ratio before dry-run
539
- let referenceImage;
540
- let isImageToVideo = false;
541
- if (options.image) {
542
- const imagePath = resolve(process.cwd(), options.image);
543
- const imageBuffer = await readFile(imagePath);
544
- const ext = options.image.toLowerCase().split(".").pop();
545
- const mimeTypes = {
546
- jpg: "image/jpeg",
547
- jpeg: "image/jpeg",
548
- png: "image/png",
549
- gif: "image/gif",
550
- webp: "image/webp",
551
- };
552
- const mimeType = mimeTypes[ext || "png"] || "image/png";
553
- referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
554
- isImageToVideo = true;
555
- // Auto-detect aspect ratio from image dimensions when not explicitly set
556
- if (!options.ratio) {
557
- const dimensions = imageSize(imageBuffer);
558
- if (dimensions.width && dimensions.height) {
559
- const ratio = dimensions.width / dimensions.height;
560
- if (ratio > 1.2) {
561
- options.ratio = "16:9";
562
- }
563
- else if (ratio < 0.8) {
564
- options.ratio = "9:16";
565
- }
566
- else {
567
- options.ratio = "1:1";
568
- }
569
- log(`Auto-detected aspect ratio: ${options.ratio} (${dimensions.width}x${dimensions.height})`);
570
- }
571
- }
572
- }
573
- // Default to 16:9 when no image and no explicit ratio
574
- if (!options.ratio) {
575
- options.ratio = "16:9";
576
- }
577
- // Veo and Runway only support 16:9 and 9:16 — clamp 1:1 to 16:9
578
- if ((provider === "veo" || provider === "runway") && options.ratio === "1:1") {
579
- log(`${provider} does not support 1:1 — falling back to 16:9`);
580
- options.ratio = "16:9";
581
- }
582
- if (options.dryRun) {
583
- outputResult({ dryRun: true, command: "generate video", params: { prompt, provider, duration: options.duration, ratio: options.ratio, image: options.image, mode: options.mode, negative: options.negative, resolution: options.resolution, veoModel: options.veoModel } });
584
- return;
585
- }
586
- const envKeyMap = {
587
- runway: "RUNWAY_API_SECRET",
588
- kling: "KLING_API_KEY",
589
- veo: "GOOGLE_API_KEY",
590
- grok: "XAI_API_KEY",
591
- };
592
- const providerNameMap = {
593
- runway: "Runway",
594
- kling: "Kling",
595
- veo: "Veo",
596
- grok: "Grok",
597
- };
598
- const envKey = envKeyMap[provider];
599
- const providerName = providerNameMap[provider];
600
- const apiKey = await requireApiKey(envKey, providerName, options.apiKey);
601
- // Runway gen4_turbo requires an input image; gen4.5 supports text-to-video
602
- const runwayModel = options.runwayModel || "gen4.5";
603
- if (provider === "runway" && !options.image && runwayModel !== "gen4.5") {
604
- exitWithError(usageError(`Runway ${runwayModel} requires an input image. Use -i <image> or use gen4.5 for text-to-video.`));
605
- }
606
- const spinner = ora(`Initializing ${providerName}...`).start();
607
- spinner.text = "Starting video generation...";
608
- let result;
609
- let finalResult;
610
- if (provider === "runway") {
611
- const runway = new RunwayProvider();
612
- await runway.initialize({ apiKey });
613
- result = await runway.generateVideo(prompt, {
614
- prompt,
615
- referenceImage,
616
- model: runwayModel,
617
- duration: parseInt(options.duration),
618
- aspectRatio: options.ratio,
619
- seed: options.seed ? parseInt(options.seed) : undefined,
620
- });
621
- if (result.status === "failed") {
622
- spinner.fail(result.error || "Failed to start generation");
623
- exitWithError(apiError(result.error || "Failed to start generation", true));
624
- }
625
- console.log();
626
- console.log(chalk.bold.cyan("Video Generation Started"));
627
- console.log(chalk.dim("─".repeat(60)));
628
- console.log(`Provider: ${chalk.bold(`Runway ${runwayModel}`)}`);
629
- console.log(`Task ID: ${chalk.bold(result.id)}`);
630
- if (!options.wait) {
631
- spinner.succeed(chalk.green("Generation started"));
632
- console.log();
633
- console.log(chalk.dim("Check status with:"));
634
- console.log(chalk.dim(` vibe generate video-status ${result.id} -p runway`));
635
- console.log();
636
- return;
637
- }
638
- spinner.text = "Generating video (this may take 1-2 minutes)...";
639
- finalResult = await runway.waitForCompletion(result.id, (status) => {
640
- if (status.progress !== undefined) {
641
- spinner.text = `Generating video... ${status.progress}%`;
642
- }
643
- }, 300000);
644
- }
645
- else if (provider === "kling") {
646
- const kling = new KlingProvider();
647
- await kling.initialize({ apiKey });
648
- if (!kling.isConfigured()) {
649
- spinner.fail("Invalid API key format");
650
- exitWithError(authError("KLING_API_KEY", "Kling"));
651
- }
652
- // Kling v2.x requires image URL, not base64 — auto-upload to ImgBB
653
- let klingImage = referenceImage;
654
- if (klingImage && klingImage.startsWith("data:")) {
655
- spinner.text = "Uploading image to ImgBB for Kling...";
656
- const imgbbKey = (await getApiKeyFromConfig("imgbb")) || process.env.IMGBB_API_KEY;
657
- if (!imgbbKey) {
658
- spinner.fail("ImgBB API key required");
659
- exitWithError(authError("IMGBB_API_KEY", "ImgBB"));
660
- }
661
- // Extract raw base64 from data URI
662
- const base64Data = klingImage.split(",")[1];
663
- const imageBuffer = Buffer.from(base64Data, "base64");
664
- const uploadResult = await uploadToImgbb(imageBuffer, imgbbKey);
665
- if (!uploadResult.success || !uploadResult.url) {
666
- spinner.fail("ImgBB upload failed");
667
- exitWithError(apiError(`ImgBB upload failed: ${uploadResult.error}`, true));
668
- }
669
- klingImage = uploadResult.url;
670
- spinner.text = "Starting video generation...";
671
- }
672
- result = await kling.generateVideo(prompt, {
673
- prompt,
674
- referenceImage: klingImage,
675
- duration: parseInt(options.duration),
676
- aspectRatio: options.ratio,
677
- negativePrompt: options.negative,
678
- mode: options.mode,
679
- });
680
- if (result.status === "failed") {
681
- spinner.fail(result.error || "Failed to start generation");
682
- exitWithError(apiError(result.error || "Failed to start generation", true));
683
- }
684
- console.log();
685
- console.log(chalk.bold.cyan("Video Generation Started"));
686
- console.log(chalk.dim("─".repeat(60)));
687
- console.log(`Provider: ${chalk.bold("Kling AI")}`);
688
- console.log(`Task ID: ${chalk.bold(result.id)}`);
689
- console.log(`Type: ${isImageToVideo ? "image2video" : "text2video"}`);
690
- if (!options.wait) {
691
- spinner.succeed(chalk.green("Generation started"));
692
- console.log();
693
- console.log(chalk.dim("Check status with:"));
694
- console.log(chalk.dim(` vibe generate video-status ${result.id} -p kling${isImageToVideo ? " --type image2video" : ""}`));
695
- console.log();
696
- return;
697
- }
698
- spinner.text = "Generating video (this may take 2-5 minutes)...";
699
- const taskType = isImageToVideo ? "image2video" : "text2video";
700
- finalResult = await kling.waitForCompletion(result.id, taskType, (status) => {
701
- spinner.text = `Generating video... ${status.status}`;
702
- }, 600000);
703
- }
704
- else if (provider === "veo") {
705
- const gemini = new GeminiProvider();
706
- await gemini.initialize({ apiKey });
707
- // Map Veo model alias to full model ID
708
- const veoModelMap = {
709
- "3.0": "veo-3.0-generate-preview",
710
- "3.1": "veo-3.1-generate-preview",
711
- "3.1-fast": "veo-3.1-fast-generate-preview",
712
- };
713
- const veoModel = veoModelMap[options.veoModel] || "veo-3.1-fast-generate-preview";
714
- const veoDuration = parseInt(options.duration) <= 6 ? 6 : 8;
715
- // Prepare last frame if provided
716
- let lastFrame;
717
- if (options.lastFrame) {
718
- const lastFramePath = resolve(process.cwd(), options.lastFrame);
719
- const lastFrameBuffer = await readFile(lastFramePath);
720
- const ext = options.lastFrame.toLowerCase().split(".").pop();
721
- const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
722
- lastFrame = `data:${mimeType};base64,${lastFrameBuffer.toString("base64")}`;
723
- }
724
- // Prepare reference images if provided
725
- let refImages;
726
- if (options.refImages && options.refImages.length > 0) {
727
- refImages = [];
728
- for (const refPath of options.refImages.slice(0, 3)) {
729
- const absRefPath = resolve(process.cwd(), refPath);
730
- const refBuffer = await readFile(absRefPath);
731
- const ext = refPath.toLowerCase().split(".").pop();
732
- const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
733
- refImages.push({ base64: refBuffer.toString("base64"), mimeType });
734
- }
735
- }
736
- result = await gemini.generateVideo(prompt, {
737
- prompt,
738
- referenceImage,
739
- duration: veoDuration,
740
- aspectRatio: options.ratio,
741
- model: veoModel,
742
- negativePrompt: options.negative,
743
- resolution: options.resolution,
744
- lastFrame,
745
- referenceImages: refImages,
746
- personGeneration: options.person,
747
- });
748
- if (result.status === "failed") {
749
- spinner.fail(result.error || "Failed to start generation");
750
- exitWithError(apiError(result.error || "Failed to start generation", true));
751
- }
752
- console.log();
753
- console.log(chalk.bold.cyan("Video Generation Started"));
754
- console.log(chalk.dim("─".repeat(60)));
755
- console.log(`Provider: ${chalk.bold("Google Veo 3.1")}`);
756
- console.log(`Task ID: ${chalk.bold(result.id)}`);
757
- if (!options.wait) {
758
- spinner.succeed(chalk.green("Generation started"));
759
- console.log();
760
- console.log(chalk.dim("Veo generation is synchronous - video URL available above"));
761
- console.log();
762
- return;
763
- }
764
- spinner.text = "Generating video (this may take 1-3 minutes)...";
765
- finalResult = await gemini.waitForVideoCompletion(result.id, (status) => {
766
- spinner.text = `Generating video... ${status.status}`;
767
- }, 300000);
768
- }
769
- else if (provider === "grok") {
770
- const grok = new GrokProvider();
771
- await grok.initialize({ apiKey });
772
- result = await grok.generateVideo(prompt, {
773
- prompt,
774
- referenceImage,
775
- duration: parseInt(options.duration),
776
- aspectRatio: options.ratio,
777
- });
778
- if (result.status === "failed") {
779
- spinner.fail(result.error || "Failed to start generation");
780
- exitWithError(apiError(result.error || "Failed to start generation", true));
781
- }
782
- console.log();
783
- console.log(chalk.bold.cyan("Video Generation Started"));
784
- console.log(chalk.dim("─".repeat(60)));
785
- console.log(`Provider: ${chalk.bold("xAI Grok Imagine")}`);
786
- console.log(`Task ID: ${chalk.bold(result.id)}`);
787
- if (!options.wait) {
788
- spinner.succeed(chalk.green("Generation started"));
789
- console.log();
790
- console.log(chalk.dim("Check status with:"));
791
- console.log(chalk.dim(` vibe generate video-status ${result.id} -p grok`));
792
- console.log();
793
- return;
794
- }
795
- spinner.text = "Generating video (this may take 1-3 minutes)...";
796
- finalResult = await grok.waitForCompletion(result.id, (status) => {
797
- spinner.text = `Generating video... ${status.status}`;
798
- }, 300000);
799
- }
800
- if (!finalResult || finalResult.status !== "completed") {
801
- spinner.fail(finalResult?.error || "Generation failed");
802
- exitWithError(apiError(finalResult?.error || "Generation failed", true));
803
- }
804
- spinner.succeed(chalk.green("Video generated"));
805
- if (isJsonMode()) {
806
- let outputPath;
807
- if (options.output && finalResult.videoUrl) {
808
- const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
809
- outputPath = resolve(process.cwd(), options.output);
810
- await writeFile(outputPath, buffer);
811
- }
812
- outputResult({ success: true, provider, taskId: result?.id, videoUrl: finalResult.videoUrl, duration: finalResult.duration, outputPath });
813
- return;
814
- }
815
- console.log();
816
- if (finalResult.videoUrl) {
817
- console.log(`Video URL: ${finalResult.videoUrl}`);
818
- }
819
- if (finalResult.duration) {
820
- console.log(`Duration: ${finalResult.duration}s`);
821
- }
822
- console.log();
823
- if (options.output && finalResult.videoUrl) {
824
- const downloadSpinner = ora("Downloading video...").start();
825
- try {
826
- const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
827
- const outputPath = resolve(process.cwd(), options.output);
828
- await writeFile(outputPath, buffer);
829
- downloadSpinner.succeed(chalk.green(`Saved to: ${outputPath}`));
830
- }
831
- catch (err) {
832
- downloadSpinner.fail(chalk.red(`Failed to download video: ${err instanceof Error ? err.message : err}`));
833
- }
834
- }
835
- }
836
- catch (error) {
837
- exitWithError(apiError(`Video generation failed: ${error.message}`));
838
- }
839
- });
840
- // ============================================================================
841
- // 3. Speech (was: ai tts)
842
- // ============================================================================
843
- generateCommand
844
- .command("speech")
845
- .alias("tts")
846
- .description("Generate speech from text using ElevenLabs")
847
- .argument("[text]", "Text to convert to speech (interactive if omitted)")
848
- .option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)")
849
- .option("-o, --output <path>", "Output audio file path", "output.mp3")
850
- .option("-v, --voice <id>", "Voice ID (default: Rachel)", "21m00Tcm4TlvDq8ikWAM")
851
- .option("--list-voices", "List available voices")
852
- .option("--fit-duration <seconds>", "Speed up audio to fit target duration (via FFmpeg atempo)", parseFloat)
853
- .option("--dry-run", "Preview parameters without executing")
854
- .action(async (text, options) => {
855
- try {
856
- // Interactive prompt if no argument provided
857
- if (!text) {
858
- if (hasTTY()) {
859
- text = await promptText(chalk.cyan("What text to speak? "));
860
- if (!text?.trim()) {
861
- exitWithError(usageError("Text is required."));
862
- }
863
- }
864
- else {
865
- exitWithError(usageError("Text argument is required.", "Usage: vibe generate speech <text>"));
866
- }
867
- }
868
- rejectControlChars(text);
869
- if (options.output) {
870
- validateOutputPath(options.output);
871
- }
872
- if (options.dryRun) {
873
- outputResult({ dryRun: true, command: "generate speech", params: { text, voice: options.voice, output: options.output } });
874
- return;
875
- }
876
- const apiKey = await requireApiKey("ELEVENLABS_API_KEY", "ElevenLabs", options.apiKey);
877
- const elevenlabs = new ElevenLabsProvider();
878
- await elevenlabs.initialize({ apiKey });
879
- // List voices mode
880
- if (options.listVoices) {
881
- const spinner = ora("Fetching voices...").start();
882
- const voices = await elevenlabs.getVoices();
883
- spinner.succeed(chalk.green(`Found ${voices.length} voices`));
884
- console.log();
885
- console.log(chalk.bold.cyan("Available Voices"));
886
- console.log(chalk.dim("─".repeat(60)));
887
- for (const voice of voices) {
888
- console.log();
889
- console.log(`${chalk.bold(voice.name)} ${chalk.dim(`(${voice.voice_id})`)}`);
890
- console.log(` Category: ${voice.category}`);
891
- if (voice.labels) {
892
- const labels = Object.entries(voice.labels)
893
- .map(([k, v]) => `${k}: ${v}`)
894
- .join(", ");
895
- console.log(` ${chalk.dim(labels)}`);
896
- }
897
- }
898
- console.log();
899
- return;
900
- }
901
- const spinner = ora("Generating speech...").start();
902
- const result = await elevenlabs.textToSpeech(text, {
903
- voiceId: options.voice,
904
- });
905
- if (!result.success || !result.audioBuffer) {
906
- spinner.fail(result.error || "TTS generation failed");
907
- exitWithError(apiError(result.error || "TTS generation failed", true));
908
- }
909
- const outputPath = resolve(process.cwd(), options.output);
910
- await writeFile(outputPath, result.audioBuffer);
911
- spinner.succeed(chalk.green("Speech generated"));
912
- // Post-process: fit to target duration via atempo
913
- if (options.fitDuration && options.fitDuration > 0) {
914
- const { ffprobeDuration, execSafe } = await import("../utils/exec-safe.js");
915
- const actualDuration = await ffprobeDuration(outputPath);
916
- if (actualDuration > options.fitDuration) {
917
- const tempo = actualDuration / options.fitDuration;
918
- if (tempo > 2.0) {
919
- log(chalk.yellow(`Warning: Audio is ${tempo.toFixed(1)}x longer than target — would sound unnatural. Skipping tempo adjustment.`));
920
- }
921
- else {
922
- const fitSpinner = ora(`Adjusting tempo (${tempo.toFixed(3)}x) to fit ${options.fitDuration}s...`).start();
923
- const tempPath = outputPath.replace(/(\.\w+)$/, `.tempo$1`);
924
- try {
925
- await execSafe("ffmpeg", [
926
- "-y", "-i", outputPath,
927
- "-filter:a", `atempo=${tempo.toFixed(4)}`,
928
- "-vn", tempPath,
929
- ]);
930
- const { rename } = await import("node:fs/promises");
931
- await rename(tempPath, outputPath);
932
- fitSpinner.succeed(chalk.green(`Adjusted to fit ${options.fitDuration}s (${tempo.toFixed(3)}x speed)`));
933
- }
934
- catch (err) {
935
- fitSpinner.fail(chalk.yellow("Tempo adjustment failed — keeping original audio"));
936
- }
937
- }
938
- }
939
- else {
940
- log(chalk.dim(`Audio (${actualDuration.toFixed(2)}s) already fits within ${options.fitDuration}s`));
941
- }
942
- }
943
- if (isJsonMode()) {
944
- outputResult({ success: true, characterCount: result.characterCount, outputPath });
945
- return;
946
- }
947
- console.log();
948
- console.log(chalk.dim(`Characters: ${result.characterCount}`));
949
- console.log(chalk.green(`Saved to: ${outputPath}`));
950
- console.log();
951
- }
952
- catch (error) {
953
- const msg = error instanceof Error ? error.message : String(error);
954
- exitWithError(apiError(`TTS generation failed: ${msg}`, true));
955
- }
956
- });
957
- // ============================================================================
958
- // 4. Sound Effect (was: ai sfx)
959
- // Note: -p is reserved for --provider; --prompt-influence is long-only
960
- // ============================================================================
961
- generateCommand
962
- .command("sound-effect")
963
- .description("Generate sound effect using ElevenLabs")
964
- .argument("<prompt>", "Description of the sound effect")
965
- .option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)")
966
- .option("-o, --output <path>", "Output audio file path", "sound-effect.mp3")
967
- .option("-d, --duration <seconds>", "Duration in seconds (0.5-22, default: auto)")
968
- .option("--prompt-influence <value>", "Prompt influence (0-1, default: 0.3)")
969
- .option("--dry-run", "Preview parameters without executing")
970
- .action(async (prompt, options) => {
971
- try {
972
- rejectControlChars(prompt);
973
- if (options.output) {
974
- validateOutputPath(options.output);
975
- }
976
- if (options.dryRun) {
977
- outputResult({ dryRun: true, command: "generate sound-effect", params: { prompt, duration: options.duration, promptInfluence: options.promptInfluence, output: options.output } });
978
- return;
979
- }
980
- const apiKey = await requireApiKey("ELEVENLABS_API_KEY", "ElevenLabs", options.apiKey);
981
- const spinner = ora("Generating sound effect...").start();
982
- const elevenlabs = new ElevenLabsProvider();
983
- await elevenlabs.initialize({ apiKey });
984
- const result = await elevenlabs.generateSoundEffect(prompt, {
985
- duration: options.duration ? parseFloat(options.duration) : undefined,
986
- promptInfluence: options.promptInfluence ? parseFloat(options.promptInfluence) : undefined,
987
- });
988
- if (!result.success || !result.audioBuffer) {
989
- spinner.fail(result.error || "Sound effect generation failed");
990
- exitWithError(apiError(result.error || "Sound effect generation failed", true));
991
- }
992
- const outputPath = resolve(process.cwd(), options.output);
993
- await writeFile(outputPath, result.audioBuffer);
994
- spinner.succeed(chalk.green("Sound effect generated"));
995
- if (isJsonMode()) {
996
- outputResult({ success: true, outputPath });
997
- return;
998
- }
999
- console.log(chalk.green(`Saved to: ${outputPath}`));
1000
- console.log();
1001
- }
1002
- catch (error) {
1003
- const msg = error instanceof Error ? error.message : String(error);
1004
- exitWithError(apiError(`Sound effect generation failed: ${msg}`, true));
1005
- }
1006
- });
1007
- // ============================================================================
1008
- // 5. Music
1009
- // ============================================================================
1010
- generateCommand
1011
- .command("music")
1012
- .description("Generate background music from a text prompt (ElevenLabs or Replicate MusicGen)")
1013
- .argument("<prompt>", "Description of the music to generate")
1014
- .option("-p, --provider <provider>", "Provider: elevenlabs (default, up to 10min), replicate (MusicGen, max 30s)", "elevenlabs")
1015
- .option("-k, --api-key <key>", "API key (or set ELEVENLABS_API_KEY / REPLICATE_API_TOKEN env)")
1016
- .option("-d, --duration <seconds>", "Duration in seconds (elevenlabs: 3-600, replicate: 1-30)", "8")
1017
- .option("--instrumental", "Force instrumental music, no vocals (ElevenLabs only)")
1018
- .option("-m, --melody <file>", "Reference melody audio file for conditioning (Replicate only)")
1019
- .option("--model <model>", "Model variant (Replicate only): large, stereo-large, melody-large, stereo-melody-large", "stereo-large")
1020
- .option("-o, --output <path>", "Output audio file path", "music.mp3")
1021
- .option("--no-wait", "Don't wait for generation to complete (Replicate async mode)")
1022
- .option("--dry-run", "Preview parameters without executing")
1023
- .action(async (prompt, options) => {
1024
- try {
1025
- rejectControlChars(prompt);
1026
- if (options.output) {
1027
- validateOutputPath(options.output);
1028
- }
1029
- const provider = (options.provider || "elevenlabs").toLowerCase();
1030
- if (options.dryRun) {
1031
- outputResult({ dryRun: true, command: "generate music", params: { prompt, provider, duration: options.duration, model: options.model, output: options.output, instrumental: options.instrumental } });
1032
- return;
1033
- }
1034
- if (provider === "elevenlabs") {
1035
- // ElevenLabs Music API — synchronous, up to 10 minutes
1036
- const apiKey = await requireApiKey("ELEVENLABS_API_KEY", "ElevenLabs", options.apiKey);
1037
- const elevenlabs = new ElevenLabsProvider();
1038
- await elevenlabs.initialize({ apiKey });
1039
- const duration = Math.max(3, Math.min(600, parseFloat(options.duration)));
1040
- const spinner = ora(`Generating music (${duration}s)...`).start();
1041
- const result = await elevenlabs.generateMusic(prompt, {
1042
- duration,
1043
- forceInstrumental: options.instrumental || false,
1044
- });
1045
- if (!result.success || !result.audioBuffer) {
1046
- spinner.fail(result.error || "Music generation failed");
1047
- exitWithError(apiError(result.error || "Music generation failed", true));
1048
- }
1049
- const outputPath = resolve(process.cwd(), options.output);
1050
- await writeFile(outputPath, result.audioBuffer);
1051
- spinner.succeed(chalk.green("Music generated successfully"));
1052
- if (isJsonMode()) {
1053
- outputResult({ success: true, provider: "elevenlabs", outputPath, duration });
1054
- return;
1055
- }
1056
- console.log();
1057
- console.log(`Saved to: ${chalk.bold(outputPath)}`);
1058
- console.log(`Duration: ${duration}s`);
1059
- console.log(`Provider: ElevenLabs (music_v1)`);
1060
- if (options.instrumental)
1061
- console.log(`Mode: Instrumental`);
1062
- console.log();
1063
- }
1064
- else {
1065
- // Replicate MusicGen — async, max 30 seconds
1066
- const apiKey = await requireApiKey("REPLICATE_API_TOKEN", "Replicate", options.apiKey);
1067
- const replicate = new ReplicateProvider();
1068
- await replicate.initialize({ apiKey });
1069
- const spinner = ora("Starting music generation...").start();
1070
- const duration = Math.max(1, Math.min(30, parseFloat(options.duration)));
1071
- // If melody file provided, upload it first
1072
- if (options.melody) {
1073
- spinner.text = "Uploading melody reference...";
1074
- const absPath = resolve(process.cwd(), options.melody);
1075
- if (!existsSync(absPath)) {
1076
- spinner.fail(`Melody file not found: ${options.melody}`);
1077
- exitWithError(notFoundError(options.melody));
1078
- }
1079
- exitWithError(usageError("Melody conditioning requires a publicly accessible URL", "Please upload your melody file and provide the URL."));
1080
- }
1081
- const result = await replicate.generateMusic(prompt, {
1082
- duration,
1083
- model: options.model,
1084
- });
1085
- if (!result.success || !result.taskId) {
1086
- spinner.fail(result.error || "Music generation failed");
1087
- exitWithError(apiError(result.error || "Music generation failed", true));
1088
- }
1089
- if (!options.wait) {
1090
- spinner.succeed(chalk.green("Music generation started"));
1091
- console.log();
1092
- console.log(`Task ID: ${chalk.bold(result.taskId)}`);
1093
- console.log(chalk.dim("Check status with: vibe generate music-status " + result.taskId));
1094
- return;
1095
- }
1096
- spinner.text = "Generating music (this may take a few minutes)...";
1097
- const finalResult = await replicate.waitForMusic(result.taskId);
1098
- if (!finalResult.success || !finalResult.audioUrl) {
1099
- spinner.fail(finalResult.error || "Music generation failed");
1100
- exitWithError(apiError(finalResult.error || "Music generation failed", true));
1101
- }
1102
- spinner.text = "Downloading generated audio...";
1103
- const response = await fetch(finalResult.audioUrl);
1104
- if (!response.ok) {
1105
- spinner.fail("Failed to download generated audio");
1106
- exitWithError(apiError("Failed to download generated audio", true));
1107
- }
1108
- const audioBuffer = Buffer.from(await response.arrayBuffer());
1109
- const outputPath = resolve(process.cwd(), options.output);
1110
- await writeFile(outputPath, audioBuffer);
1111
- spinner.succeed(chalk.green("Music generated successfully"));
1112
- if (isJsonMode()) {
1113
- outputResult({ success: true, provider: "replicate", taskId: result.taskId, audioUrl: finalResult.audioUrl, outputPath });
1114
- return;
1115
- }
1116
- console.log();
1117
- console.log(`Saved to: ${chalk.bold(outputPath)}`);
1118
- console.log(`Duration: ${duration}s`);
1119
- console.log(`Model: ${options.model}`);
1120
- console.log();
1121
- }
1122
- }
1123
- catch (error) {
1124
- const msg = error instanceof Error ? error.message : String(error);
1125
- exitWithError(apiError(`Music generation failed: ${msg}`, true));
1126
- }
1127
- });
1128
- // ============================================================================
1129
- // 6. Music Status
1130
- // ============================================================================
1131
- generateCommand
1132
- .command("music-status", { hidden: true })
1133
- .description("Check music generation status")
1134
- .argument("<task-id>", "Task ID from music generation")
1135
- .option("-k, --api-key <key>", "Replicate API token (or set REPLICATE_API_TOKEN env)")
1136
- .action(async (taskId, options) => {
1137
- try {
1138
- const apiKey = await requireApiKey("REPLICATE_API_TOKEN", "Replicate", options.apiKey);
1139
- const replicate = new ReplicateProvider();
1140
- await replicate.initialize({ apiKey });
1141
- const result = await replicate.getMusicStatus(taskId);
1142
- if (isJsonMode()) {
1143
- const status = result.audioUrl ? "completed" : result.error ? "failed" : "processing";
1144
- outputResult({ success: true, taskId, status, audioUrl: result.audioUrl, error: result.error });
1145
- return;
1146
- }
1147
- console.log();
1148
- console.log(chalk.bold.cyan("Music Generation Status"));
1149
- console.log(chalk.dim("─".repeat(60)));
1150
- console.log(`Task ID: ${taskId}`);
1151
- if (result.audioUrl) {
1152
- console.log(`Status: ${chalk.green("completed")}`);
1153
- console.log(`Audio URL: ${result.audioUrl}`);
1154
- }
1155
- else if (result.error) {
1156
- console.log(`Status: ${chalk.red("failed")}`);
1157
- console.log(`Error: ${result.error}`);
1158
- }
1159
- else {
1160
- console.log(`Status: ${chalk.yellow("processing")}`);
1161
- }
1162
- console.log();
1163
- }
1164
- catch (error) {
1165
- const msg = error instanceof Error ? error.message : String(error);
1166
- exitWithError(apiError(`Failed to get music status: ${msg}`, true));
1167
- }
1168
- });
1169
- // ============================================================================
1170
- // 7. Storyboard
1171
- // ============================================================================
1172
- generateCommand
1173
- .command("storyboard")
1174
- .description("Generate video storyboard from content using Claude")
1175
- .argument("<content>", "Content to analyze (text or file path)")
1176
- .option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)")
1177
- .option("-o, --output <path>", "Output JSON file path")
1178
- .option("-d, --duration <sec>", "Target total duration in seconds")
1179
- .option("-f, --file", "Treat content argument as file path")
1180
- .option("-c, --creativity <level>", "Creativity level: low (default, consistent) or high (varied, unexpected)", "low")
1181
- .option("--dry-run", "Preview parameters without executing")
1182
- .action(async (content, options) => {
1183
- try {
1184
- rejectControlChars(content);
1185
- if (options.output) {
1186
- validateOutputPath(options.output);
1187
- }
1188
- // Validate creativity level
1189
- const creativity = options.creativity?.toLowerCase();
1190
- if (creativity && creativity !== "low" && creativity !== "high") {
1191
- exitWithError(usageError("Invalid creativity level. Use 'low' or 'high'."));
1192
- }
1193
- let textContent = content;
1194
- if (options.file) {
1195
- const filePath = resolve(process.cwd(), content);
1196
- textContent = await readFile(filePath, "utf-8");
1197
- }
1198
- if (options.dryRun) {
1199
- outputResult({ dryRun: true, command: "generate storyboard", params: { content: textContent.substring(0, 200), duration: options.duration, creativity } });
1200
- return;
1201
- }
1202
- const apiKey = await requireApiKey("ANTHROPIC_API_KEY", "Anthropic", options.apiKey);
1203
- const spinnerText = creativity === "high"
1204
- ? "Analyzing content with high creativity..."
1205
- : "Analyzing content...";
1206
- const spinner = ora(spinnerText).start();
1207
- const claude = new ClaudeProvider();
1208
- await claude.initialize({ apiKey });
1209
- const segments = await claude.analyzeContent(textContent, options.duration ? parseFloat(options.duration) : undefined, { creativity: creativity });
1210
- if (segments.length === 0) {
1211
- spinner.fail("Could not generate storyboard");
1212
- exitWithError(apiError("Could not generate storyboard", true));
1213
- }
1214
- spinner.succeed(chalk.green(`Generated ${segments.length} segments`));
1215
- for (const seg of segments) {
1216
- seg.description = sanitizeLLMResponse(seg.description);
1217
- if (seg.visuals)
1218
- seg.visuals = sanitizeLLMResponse(seg.visuals);
1219
- }
1220
- if (options.output) {
1221
- const outputPath = resolve(process.cwd(), options.output);
1222
- await writeFile(outputPath, JSON.stringify(segments, null, 2), "utf-8");
1223
- if (isJsonMode()) {
1224
- outputResult({ success: true, segmentCount: segments.length, segments, outputPath });
1225
- return;
1226
- }
1227
- }
1228
- else if (isJsonMode()) {
1229
- outputResult({ success: true, segmentCount: segments.length, segments });
1230
- return;
1231
- }
1232
- console.log();
1233
- console.log(chalk.bold.cyan("Storyboard"));
1234
- console.log(chalk.dim("─".repeat(60)));
1235
- for (const seg of segments) {
1236
- console.log();
1237
- console.log(chalk.yellow(`[${seg.index + 1}] ${formatTime(seg.startTime)} - ${formatTime(seg.startTime + seg.duration)}`));
1238
- console.log(` ${seg.description}`);
1239
- console.log(chalk.dim(` Visuals: ${seg.visuals}`));
1240
- if (seg.audio) {
1241
- console.log(chalk.dim(` Audio: ${seg.audio}`));
1242
- }
1243
- if (seg.textOverlays && seg.textOverlays.length > 0) {
1244
- console.log(chalk.dim(` Text: ${seg.textOverlays.join(", ")}`));
1245
- }
1246
- }
1247
- console.log();
1248
- if (options.output) {
1249
- console.log(chalk.green(`Saved to: ${resolve(process.cwd(), options.output)}`));
1250
- }
1251
- }
1252
- catch (error) {
1253
- const msg = error instanceof Error ? error.message : String(error);
1254
- exitWithError(apiError(`Storyboard generation failed: ${msg}`, true));
1255
- }
1256
- });
1257
- // ============================================================================
1258
- // 8. Motion (delegated to registerMotionCommand)
1259
- // ============================================================================
1260
- registerMotionCommand(generateCommand);
1261
- // ============================================================================
1262
- // 9. Thumbnail
1263
- // ============================================================================
1264
- generateCommand
1265
- .command("thumbnail")
1266
- .description("Generate video thumbnail (DALL-E) or extract best frame from video (Gemini)")
1267
- .argument("[description]", "Thumbnail description (for DALL-E generation)")
1268
- .option("-k, --api-key <key>", "API key (OpenAI for generation, Google for best-frame)")
1269
- .option("-o, --output <path>", "Output file path")
1270
- .option("-s, --style <style>", "Platform style: youtube, instagram, tiktok, twitter")
1271
- .option("--best-frame <video>", "Extract best thumbnail frame from video using Gemini AI")
1272
- .option("--prompt <prompt>", "Custom prompt for best-frame analysis")
1273
- .option("--model <model>", "Gemini model: flash, latest, pro (default: flash)", "flash")
1274
- .action(async (description, options) => {
1275
- try {
1276
- if (description)
1277
- rejectControlChars(description);
1278
- if (options.output) {
1279
- validateOutputPath(options.output);
1280
- }
1281
- // Best-frame mode: analyze video with Gemini and extract frame
1282
- if (options.bestFrame) {
1283
- const absVideoPath = resolve(process.cwd(), options.bestFrame);
1284
- if (!existsSync(absVideoPath)) {
1285
- exitWithError(notFoundError(absVideoPath));
1286
- }
1287
- if (!commandExists("ffmpeg")) {
1288
- exitWithError(generalError("FFmpeg not found", "Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)"));
1289
- }
1290
- const apiKey = await requireApiKey("GOOGLE_API_KEY", "Google", options.apiKey);
1291
- const name = basename(options.bestFrame, extname(options.bestFrame));
1292
- const outputPath = options.output || `${name}-thumbnail.png`;
1293
- const spinner = ora("Analyzing video for best frame...").start();
1294
- const result = await executeThumbnailBestFrame({
1295
- videoPath: absVideoPath,
1296
- outputPath: resolve(process.cwd(), outputPath),
1297
- prompt: options.prompt,
1298
- model: options.model,
1299
- apiKey,
1300
- });
1301
- if (!result.success) {
1302
- spinner.fail(result.error || "Best frame extraction failed");
1303
- exitWithError(apiError(result.error || "Best frame extraction failed", true));
1304
- }
1305
- spinner.succeed(chalk.green("Best frame extracted"));
1306
- if (isJsonMode()) {
1307
- outputResult({ success: true, timestamp: result.timestamp, reason: result.reason, outputPath: result.outputPath });
1308
- return;
1309
- }
1310
- console.log();
1311
- console.log(chalk.bold.cyan("Best Frame Result"));
1312
- console.log(chalk.dim("─".repeat(60)));
1313
- console.log(`Timestamp: ${chalk.bold(result.timestamp.toFixed(2))}s`);
1314
- if (result.reason)
1315
- console.log(`Reason: ${chalk.dim(result.reason)}`);
1316
- console.log(`Output: ${chalk.green(result.outputPath)}`);
1317
- console.log();
1318
- return;
1319
- }
1320
- // Generation mode: create thumbnail with DALL-E
1321
- if (!description) {
1322
- exitWithError(usageError("Description required for thumbnail generation.", "Usage: vibe generate thumbnail <description> or vibe generate thumbnail --best-frame <video>"));
1323
- }
1324
- const apiKey = await requireApiKey("OPENAI_API_KEY", "OpenAI", options.apiKey);
1325
- const spinner = ora("Generating thumbnail...").start();
1326
- const openaiImage = new OpenAIImageProvider();
1327
- await openaiImage.initialize({ apiKey });
1328
- const result = await openaiImage.generateThumbnail(description, options.style);
1329
- if (!result.success || !result.images) {
1330
- spinner.fail(result.error || "Thumbnail generation failed");
1331
- exitWithError(apiError(result.error || "Thumbnail generation failed", true));
1332
- }
1333
- spinner.succeed(chalk.green("Thumbnail generated"));
1334
- const img = result.images[0];
1335
- if (isJsonMode()) {
1336
- let outputPath;
1337
- if (options.output) {
1338
- let buffer;
1339
- if (img.url) {
1340
- const response = await fetch(img.url);
1341
- buffer = Buffer.from(await response.arrayBuffer());
1342
- }
1343
- else if (img.base64) {
1344
- buffer = Buffer.from(img.base64, "base64");
1345
- }
1346
- else {
1347
- throw new Error("No image data available");
1348
- }
1349
- outputPath = resolve(process.cwd(), options.output);
1350
- await mkdir(dirname(outputPath), { recursive: true });
1351
- await writeFile(outputPath, buffer);
1352
- }
1353
- outputResult({ success: true, imageUrl: img.url, outputPath });
1354
- return;
1355
- }
1356
- console.log();
1357
- console.log(chalk.bold.cyan("Generated Thumbnail"));
1358
- console.log(chalk.dim("─".repeat(60)));
1359
- console.log(`URL: ${img.url}`);
1360
- if (img.revisedPrompt) {
1361
- console.log(chalk.dim(`Prompt: ${img.revisedPrompt.slice(0, 100)}...`));
1362
- }
1363
- console.log();
1364
- // Save if output specified
1365
- if (options.output) {
1366
- const saveSpinner = ora("Saving thumbnail...").start();
1367
- try {
1368
- let buffer;
1369
- if (img.url) {
1370
- const response = await fetch(img.url);
1371
- buffer = Buffer.from(await response.arrayBuffer());
1372
- }
1373
- else if (img.base64) {
1374
- buffer = Buffer.from(img.base64, "base64");
1375
- }
1376
- else {
1377
- throw new Error("No image data available");
1378
- }
1379
- const outputPath = resolve(process.cwd(), options.output);
1380
- await mkdir(dirname(outputPath), { recursive: true });
1381
- await writeFile(outputPath, buffer);
1382
- saveSpinner.succeed(chalk.green(`Saved to: ${outputPath}`));
1383
- }
1384
- catch (err) {
1385
- saveSpinner.fail(chalk.red("Failed to save thumbnail"));
1386
- }
1387
- }
1388
- }
1389
- catch (error) {
1390
- const msg = error instanceof Error ? error.message : String(error);
1391
- exitWithError(apiError(`Thumbnail generation failed: ${msg}`, true));
1392
- }
1393
- });
1394
- // ============================================================================
1395
- // 10. Background
1396
- // ============================================================================
1397
- generateCommand
1398
- .command("background")
1399
- .description("Generate video background using DALL-E")
1400
- .argument("<description>", "Background description")
1401
- .option("-k, --api-key <key>", "OpenAI API key (or set OPENAI_API_KEY env)")
1402
- .option("-o, --output <path>", "Output file path (downloads image)")
1403
- .option("-a, --aspect <ratio>", "Aspect ratio: 16:9, 9:16, 1:1", "16:9")
1404
- .option("--dry-run", "Preview parameters without executing")
1405
- .action(async (description, options) => {
1406
- try {
1407
- rejectControlChars(description);
1408
- if (options.output) {
1409
- validateOutputPath(options.output);
1410
- }
1411
- if (options.dryRun) {
1412
- outputResult({ dryRun: true, command: "generate background", params: { description, aspect: options.aspect, output: options.output } });
1413
- return;
1414
- }
1415
- const apiKey = await requireApiKey("OPENAI_API_KEY", "OpenAI", options.apiKey);
1416
- const spinner = ora("Generating background...").start();
1417
- const openaiImage = new OpenAIImageProvider();
1418
- await openaiImage.initialize({ apiKey });
1419
- const result = await openaiImage.generateBackground(description, options.aspect);
1420
- if (!result.success || !result.images) {
1421
- spinner.fail(result.error || "Background generation failed");
1422
- exitWithError(apiError(result.error || "Background generation failed", true));
1423
- }
1424
- spinner.succeed(chalk.green("Background generated"));
1425
- const img = result.images[0];
1426
- if (isJsonMode()) {
1427
- let outputPath;
1428
- if (options.output) {
1429
- let buffer;
1430
- if (img.url) {
1431
- const response = await fetch(img.url);
1432
- buffer = Buffer.from(await response.arrayBuffer());
1433
- }
1434
- else if (img.base64) {
1435
- buffer = Buffer.from(img.base64, "base64");
1436
- }
1437
- else {
1438
- throw new Error("No image data available");
1439
- }
1440
- outputPath = resolve(process.cwd(), options.output);
1441
- await mkdir(dirname(outputPath), { recursive: true });
1442
- await writeFile(outputPath, buffer);
1443
- }
1444
- outputResult({ success: true, imageUrl: img.url, outputPath });
1445
- return;
1446
- }
1447
- console.log();
1448
- console.log(chalk.bold.cyan("Generated Background"));
1449
- console.log(chalk.dim("─".repeat(60)));
1450
- console.log(`Image: ${img.url || "(base64 data)"}`);
1451
- if (img.revisedPrompt) {
1452
- console.log(chalk.dim(`Prompt: ${img.revisedPrompt.slice(0, 100)}...`));
1453
- }
1454
- console.log();
1455
- // Save if output specified
1456
- if (options.output) {
1457
- const saveSpinner = ora("Saving background...").start();
1458
- try {
1459
- let buffer;
1460
- if (img.url) {
1461
- const response = await fetch(img.url);
1462
- buffer = Buffer.from(await response.arrayBuffer());
1463
- }
1464
- else if (img.base64) {
1465
- buffer = Buffer.from(img.base64, "base64");
1466
- }
1467
- else {
1468
- throw new Error("No image data available");
1469
- }
1470
- const outputPath = resolve(process.cwd(), options.output);
1471
- await mkdir(dirname(outputPath), { recursive: true });
1472
- await writeFile(outputPath, buffer);
1473
- saveSpinner.succeed(chalk.green(`Saved to: ${outputPath}`));
1474
- }
1475
- catch (err) {
1476
- saveSpinner.fail(chalk.red("Failed to save background"));
1477
- }
1478
- }
1479
- }
1480
- catch (error) {
1481
- const msg = error instanceof Error ? error.message : String(error);
1482
- exitWithError(apiError(`Background generation failed: ${msg}`, true));
1483
- }
1484
- });
1485
- // ============================================================================
1486
- // 11. Video Status (merged: ai video-status + ai kling-status)
1487
- // ============================================================================
1488
- generateCommand
1489
- .command("video-status", { hidden: true })
1490
- .description("Check video generation status (Grok, Runway, or Kling)")
1491
- .argument("<task-id>", "Task ID from video generation")
1492
- .option("-p, --provider <provider>", "Provider: grok, runway, kling", "grok")
1493
- .option("-k, --api-key <key>", "API key (or set XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY env)")
1494
- .option("-t, --type <type>", "Task type: text2video or image2video (Kling only)", "text2video")
1495
- .option("-w, --wait", "Wait for completion")
1496
- .option("-o, --output <path>", "Download video when complete")
1497
- .action(async (taskId, options) => {
1498
- try {
1499
- const provider = (options.provider || "grok").toLowerCase();
1500
- if (provider === "grok") {
1501
- const apiKey = await requireApiKey("XAI_API_KEY", "xAI", options.apiKey);
1502
- const spinner = ora("Checking status...").start();
1503
- const grok = new GrokProvider();
1504
- await grok.initialize({ apiKey });
1505
- let result = await grok.getGenerationStatus(taskId);
1506
- if (options.wait && result.status !== "completed" && result.status !== "failed") {
1507
- spinner.text = "Waiting for completion...";
1508
- result = await grok.waitForCompletion(taskId, (status) => {
1509
- spinner.text = `Generating... ${status.status}`;
1510
- });
1511
- }
1512
- spinner.stop();
1513
- if (isJsonMode()) {
1514
- let outputPath;
1515
- if (options.output && result.videoUrl) {
1516
- const buffer = await downloadVideo(result.videoUrl);
1517
- outputPath = resolve(process.cwd(), options.output);
1518
- await writeFile(outputPath, buffer);
1519
- }
1520
- outputResult({ success: true, taskId, provider: "grok", status: result.status, videoUrl: result.videoUrl, error: result.error, outputPath });
1521
- return;
1522
- }
1523
- console.log();
1524
- console.log(chalk.bold.cyan("Generation Status"));
1525
- console.log(chalk.dim("─".repeat(60)));
1526
- console.log(`Task ID: ${taskId}`);
1527
- console.log(`Provider: Grok Imagine`);
1528
- console.log(`Status: ${getStatusColor(result.status)}`);
1529
- if (result.videoUrl) {
1530
- console.log(`Video URL: ${result.videoUrl}`);
1531
- }
1532
- if (result.error) {
1533
- console.log(`Error: ${chalk.red(result.error)}`);
1534
- }
1535
- console.log();
1536
- if (options.output && result.videoUrl) {
1537
- const downloadSpinner = ora("Downloading video...").start();
1538
- try {
1539
- const buffer = await downloadVideo(result.videoUrl);
1540
- const outputPath = resolve(process.cwd(), options.output);
1541
- await writeFile(outputPath, buffer);
1542
- downloadSpinner.succeed(chalk.green(`Saved to: ${outputPath}`));
1543
- }
1544
- catch (err) {
1545
- downloadSpinner.fail(chalk.red(`Failed to download video: ${err instanceof Error ? err.message : err}`));
1546
- }
1547
- }
1548
- }
1549
- else if (provider === "runway") {
1550
- const apiKey = await requireApiKey("RUNWAY_API_SECRET", "Runway", options.apiKey);
1551
- const spinner = ora("Checking status...").start();
1552
- const runway = new RunwayProvider();
1553
- await runway.initialize({ apiKey });
1554
- let result = await runway.getGenerationStatus(taskId);
1555
- if (options.wait && result.status !== "completed" && result.status !== "failed" && result.status !== "cancelled") {
1556
- spinner.text = "Waiting for completion...";
1557
- result = await runway.waitForCompletion(taskId, (status) => {
1558
- if (status.progress !== undefined) {
1559
- spinner.text = `Generating... ${status.progress}%`;
1560
- }
1561
- });
1562
- }
1563
- spinner.stop();
1564
- if (isJsonMode()) {
1565
- let outputPath;
1566
- if (options.output && result.videoUrl) {
1567
- const buffer = await downloadVideo(result.videoUrl, apiKey);
1568
- outputPath = resolve(process.cwd(), options.output);
1569
- await writeFile(outputPath, buffer);
1570
- }
1571
- outputResult({ success: true, taskId, provider: "runway", status: result.status, videoUrl: result.videoUrl, progress: result.progress, error: result.error, outputPath });
1572
- return;
1573
- }
1574
- console.log();
1575
- console.log(chalk.bold.cyan("Generation Status"));
1576
- console.log(chalk.dim("─".repeat(60)));
1577
- console.log(`Task ID: ${taskId}`);
1578
- console.log(`Provider: Runway`);
1579
- console.log(`Status: ${getStatusColor(result.status)}`);
1580
- if (result.progress !== undefined) {
1581
- console.log(`Progress: ${result.progress}%`);
1582
- }
1583
- if (result.videoUrl) {
1584
- console.log(`Video URL: ${result.videoUrl}`);
1585
- }
1586
- if (result.error) {
1587
- console.log(`Error: ${chalk.red(result.error)}`);
1588
- }
1589
- console.log();
1590
- if (options.output && result.videoUrl) {
1591
- const downloadSpinner = ora("Downloading video...").start();
1592
- try {
1593
- const buffer = await downloadVideo(result.videoUrl, apiKey);
1594
- const outputPath = resolve(process.cwd(), options.output);
1595
- await writeFile(outputPath, buffer);
1596
- downloadSpinner.succeed(chalk.green(`Saved to: ${outputPath}`));
1597
- }
1598
- catch (err) {
1599
- downloadSpinner.fail(chalk.red(`Failed to download video: ${err instanceof Error ? err.message : err}`));
1600
- }
1601
- }
1602
- }
1603
- else if (provider === "kling") {
1604
- const apiKey = await requireApiKey("KLING_API_KEY", "Kling", options.apiKey);
1605
- const spinner = ora("Checking status...").start();
1606
- const kling = new KlingProvider();
1607
- await kling.initialize({ apiKey });
1608
- const taskType = options.type;
1609
- let result = await kling.getGenerationStatus(taskId, taskType);
1610
- if (options.wait && result.status !== "completed" && result.status !== "failed" && result.status !== "cancelled") {
1611
- spinner.text = "Waiting for completion...";
1612
- result = await kling.waitForCompletion(taskId, taskType, (status) => {
1613
- spinner.text = `Generating... ${status.status}`;
1614
- });
1615
- }
1616
- spinner.stop();
1617
- if (isJsonMode()) {
1618
- let outputPath;
1619
- if (options.output && result.videoUrl) {
1620
- const buffer = await downloadVideo(result.videoUrl, apiKey);
1621
- outputPath = resolve(process.cwd(), options.output);
1622
- await writeFile(outputPath, buffer);
1623
- }
1624
- outputResult({ success: true, taskId, provider: "kling", status: result.status, videoUrl: result.videoUrl, duration: result.duration, error: result.error, outputPath });
1625
- return;
1626
- }
1627
- console.log();
1628
- console.log(chalk.bold.cyan("Generation Status"));
1629
- console.log(chalk.dim("─".repeat(60)));
1630
- console.log(`Task ID: ${taskId}`);
1631
- console.log(`Provider: Kling`);
1632
- console.log(`Type: ${taskType}`);
1633
- console.log(`Status: ${getStatusColor(result.status)}`);
1634
- if (result.videoUrl) {
1635
- console.log(`Video URL: ${result.videoUrl}`);
1636
- }
1637
- if (result.duration) {
1638
- console.log(`Duration: ${result.duration}s`);
1639
- }
1640
- if (result.error) {
1641
- console.log(`Error: ${chalk.red(result.error)}`);
1642
- }
1643
- console.log();
1644
- if (options.output && result.videoUrl) {
1645
- const downloadSpinner = ora("Downloading video...").start();
1646
- try {
1647
- const buffer = await downloadVideo(result.videoUrl, apiKey);
1648
- const outputPath = resolve(process.cwd(), options.output);
1649
- await writeFile(outputPath, buffer);
1650
- downloadSpinner.succeed(chalk.green(`Saved to: ${outputPath}`));
1651
- }
1652
- catch (err) {
1653
- downloadSpinner.fail(chalk.red(`Failed to download video: ${err instanceof Error ? err.message : err}`));
1654
- }
1655
- }
1656
- }
1657
- else {
1658
- exitWithError(usageError(`Invalid provider: ${provider}. Use grok, runway, or kling.`));
1659
- }
1660
- }
1661
- catch (error) {
1662
- const msg = error instanceof Error ? error.message : String(error);
1663
- exitWithError(apiError(`Failed to get status: ${msg}`, true));
1664
- }
1665
- });
1666
- // ============================================================================
1667
- // 12. Video Cancel
1668
- // ============================================================================
1669
- generateCommand
1670
- .command("video-cancel", { hidden: true })
1671
- .description("Cancel video generation (Grok or Runway)")
1672
- .argument("<task-id>", "Task ID to cancel")
1673
- .option("-p, --provider <provider>", "Provider: grok, runway", "grok")
1674
- .option("-k, --api-key <key>", "API key (or set XAI_API_KEY / RUNWAY_API_SECRET env)")
1675
- .action(async (taskId, options) => {
1676
- try {
1677
- const provider = (options.provider || "grok").toLowerCase();
1678
- let success = false;
1679
- if (provider === "grok") {
1680
- const apiKey = await requireApiKey("XAI_API_KEY", "xAI", options.apiKey);
1681
- const spinner = ora("Cancelling generation...").start();
1682
- const grok = new GrokProvider();
1683
- await grok.initialize({ apiKey });
1684
- success = await grok.cancelGeneration(taskId);
1685
- if (success) {
1686
- spinner.succeed(chalk.green("Generation cancelled"));
1687
- if (isJsonMode()) {
1688
- outputResult({ success: true, taskId, provider: "grok", cancelled: true });
1689
- return;
1690
- }
1691
- }
1692
- else {
1693
- spinner.fail("Failed to cancel generation");
1694
- exitWithError(apiError("Failed to cancel generation", true));
1695
- }
1696
- }
1697
- else if (provider === "runway") {
1698
- const apiKey = await requireApiKey("RUNWAY_API_SECRET", "Runway", options.apiKey);
1699
- const spinner = ora("Cancelling generation...").start();
1700
- const runway = new RunwayProvider();
1701
- await runway.initialize({ apiKey });
1702
- success = await runway.cancelGeneration(taskId);
1703
- if (success) {
1704
- spinner.succeed(chalk.green("Generation cancelled"));
1705
- if (isJsonMode()) {
1706
- outputResult({ success: true, taskId, provider: "runway", cancelled: true });
1707
- return;
1708
- }
1709
- }
1710
- else {
1711
- spinner.fail("Failed to cancel generation");
1712
- exitWithError(apiError("Failed to cancel generation", true));
1713
- }
1714
- }
1715
- else {
1716
- exitWithError(usageError(`Invalid provider: ${provider}. Use grok or runway.`));
1717
- }
1718
- }
1719
- catch (error) {
1720
- const msg = error instanceof Error ? error.message : String(error);
1721
- exitWithError(apiError(`Failed to cancel: ${msg}`, true));
1722
- }
1723
- });
1724
- // ============================================================================
1725
- // 13. Video Extend (merged: ai video-extend + ai veo-extend)
1726
- // Note: --prompt is long-only (-p is reserved for --provider)
1727
- // ============================================================================
1728
- generateCommand
1729
- .command("video-extend", { hidden: true })
1730
- .description("Extend video duration (Kling by video ID, Veo by operation name)")
1731
- .argument("<id>", "Kling video ID or Veo operation name")
1732
- .option("-p, --provider <provider>", "Provider: kling, veo", "kling")
1733
- .option("-k, --api-key <key>", "API key (KLING_API_KEY or GOOGLE_API_KEY)")
1734
- .option("-o, --output <path>", "Output file path")
1735
- .option("--prompt <text>", "Continuation prompt")
1736
- .option("-d, --duration <sec>", "Duration: 5 or 10 (Kling), 4/6/8 (Veo)", "5")
1737
- .option("-n, --negative <prompt>", "Negative prompt (what to avoid, Kling only)")
1738
- .option("--veo-model <model>", "Veo model: 3.0, 3.1, 3.1-fast", "3.1")
1739
- .option("--no-wait", "Start extension and return task ID without waiting")
1740
- .option("--dry-run", "Preview parameters without executing")
1741
- .action(async (id, options) => {
1742
- try {
1743
- const provider = (options.provider || "kling").toLowerCase();
1744
- if (options.output) {
1745
- validateOutputPath(options.output);
1746
- }
1747
- if (options.dryRun) {
1748
- outputResult({ dryRun: true, command: "generate video-extend", params: { id, provider, prompt: options.prompt, duration: options.duration, negative: options.negative, veoModel: options.veoModel } });
1749
- return;
1750
- }
1751
- if (provider === "kling") {
1752
- const apiKey = await requireApiKey("KLING_API_KEY", "Kling", options.apiKey);
1753
- const spinner = ora("Initializing Kling AI...").start();
1754
- const kling = new KlingProvider();
1755
- await kling.initialize({ apiKey });
1756
- if (!kling.isConfigured()) {
1757
- spinner.fail("Invalid API key format");
1758
- exitWithError(authError("KLING_API_KEY", "Kling"));
1759
- }
1760
- spinner.text = "Starting video extension...";
1761
- const result = await kling.extendVideo(id, {
1762
- prompt: options.prompt,
1763
- negativePrompt: options.negative,
1764
- duration: options.duration,
1765
- });
1766
- if (result.status === "failed") {
1767
- spinner.fail(result.error || "Failed to start extension");
1768
- exitWithError(apiError(result.error || "Failed to start extension", true));
1769
- }
1770
- console.log();
1771
- console.log(chalk.bold.cyan("Video Extension Started"));
1772
- console.log(chalk.dim("─".repeat(60)));
1773
- console.log(`Provider: Kling`);
1774
- console.log(`Task ID: ${chalk.bold(result.id)}`);
1775
- if (!options.wait) {
1776
- spinner.succeed(chalk.green("Extension started"));
1777
- console.log();
1778
- console.log(chalk.dim("Check status with:"));
1779
- console.log(chalk.dim(` vibe generate video-status ${result.id} -p kling`));
1780
- console.log();
1781
- return;
1782
- }
1783
- spinner.text = "Extending video (this may take 2-5 minutes)...";
1784
- const finalResult = await kling.waitForExtendCompletion(result.id, (status) => {
1785
- spinner.text = `Extending video... ${status.status}`;
1786
- }, 600000);
1787
- if (finalResult.status !== "completed") {
1788
- spinner.fail(finalResult.error || "Extension failed");
1789
- exitWithError(apiError(finalResult.error || "Extension failed", true));
1790
- }
1791
- spinner.succeed(chalk.green("Video extended"));
1792
- if (isJsonMode()) {
1793
- let outputPath;
1794
- if (options.output && finalResult.videoUrl) {
1795
- const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
1796
- outputPath = resolve(process.cwd(), options.output);
1797
- await writeFile(outputPath, buffer);
1798
- }
1799
- outputResult({ success: true, provider: "kling", taskId: result.id, videoUrl: finalResult.videoUrl, duration: finalResult.duration, outputPath });
1800
- return;
1801
- }
1802
- console.log();
1803
- if (finalResult.videoUrl) {
1804
- console.log(`Video URL: ${finalResult.videoUrl}`);
1805
- }
1806
- if (finalResult.duration) {
1807
- console.log(`Duration: ${finalResult.duration}s`);
1808
- }
1809
- console.log();
1810
- if (options.output && finalResult.videoUrl) {
1811
- const downloadSpinner = ora("Downloading video...").start();
1812
- try {
1813
- const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
1814
- const outputPath = resolve(process.cwd(), options.output);
1815
- await writeFile(outputPath, buffer);
1816
- downloadSpinner.succeed(chalk.green(`Saved to: ${outputPath}`));
1817
- }
1818
- catch (err) {
1819
- downloadSpinner.fail(chalk.red(`Failed to download video: ${err instanceof Error ? err.message : err}`));
1820
- }
1821
- }
1822
- }
1823
- else if (provider === "veo") {
1824
- const apiKey = await requireApiKey("GOOGLE_API_KEY", "Google", options.apiKey);
1825
- const spinner = ora("Initializing Veo...").start();
1826
- const gemini = new GeminiProvider();
1827
- await gemini.initialize({ apiKey });
1828
- const veoModelMap = {
1829
- "3.0": "veo-3.0-generate-preview",
1830
- "3.1": "veo-3.1-generate-preview",
1831
- "3.1-fast": "veo-3.1-fast-generate-preview",
1832
- };
1833
- const veoModel = veoModelMap[options.veoModel] || "veo-3.1-generate-preview";
1834
- spinner.text = "Starting video extension...";
1835
- const result = await gemini.extendVideo(id, options.prompt, {
1836
- duration: parseInt(options.duration),
1837
- model: veoModel,
1838
- });
1839
- if (result.status === "failed") {
1840
- spinner.fail(result.error || "Failed to start extension");
1841
- exitWithError(apiError(result.error || "Failed to start extension", true));
1842
- }
1843
- console.log();
1844
- console.log(chalk.bold.cyan("Veo Video Extension Started"));
1845
- console.log(chalk.dim("─".repeat(60)));
1846
- console.log(`Provider: Veo`);
1847
- console.log(`Operation: ${chalk.bold(result.id)}`);
1848
- if (!options.wait) {
1849
- spinner.succeed(chalk.green("Extension started"));
1850
- console.log();
1851
- console.log(chalk.dim("Check status or wait with:"));
1852
- console.log(chalk.dim(` vibe generate video-extend ${result.id} -p veo`));
1853
- console.log();
1854
- return;
1855
- }
1856
- spinner.text = "Extending video (this may take 1-3 minutes)...";
1857
- const finalResult = await gemini.waitForVideoCompletion(result.id, (status) => {
1858
- spinner.text = `Extending video... ${status.status}`;
1859
- }, 300000);
1860
- if (finalResult.status !== "completed") {
1861
- spinner.fail(finalResult.error || "Extension failed");
1862
- exitWithError(apiError(finalResult.error || "Extension failed", true));
1863
- }
1864
- spinner.succeed(chalk.green("Video extended"));
1865
- if (isJsonMode()) {
1866
- let outputPath;
1867
- if (options.output && finalResult.videoUrl) {
1868
- const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
1869
- outputPath = resolve(process.cwd(), options.output);
1870
- await writeFile(outputPath, buffer);
1871
- }
1872
- outputResult({ success: true, provider: "veo", taskId: result.id, videoUrl: finalResult.videoUrl, duration: finalResult.duration, outputPath });
1873
- return;
1874
- }
1875
- console.log();
1876
- if (finalResult.videoUrl) {
1877
- console.log(`Video URL: ${finalResult.videoUrl}`);
1878
- }
1879
- console.log();
1880
- if (options.output && finalResult.videoUrl) {
1881
- const downloadSpinner = ora("Downloading video...").start();
1882
- try {
1883
- const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
1884
- const outputPath = resolve(process.cwd(), options.output);
1885
- await writeFile(outputPath, buffer);
1886
- downloadSpinner.succeed(chalk.green(`Saved to: ${outputPath}`));
1887
- }
1888
- catch (err) {
1889
- downloadSpinner.fail(chalk.red(`Failed to download video: ${err instanceof Error ? err.message : err}`));
1890
- }
1891
- }
1892
- }
1893
- else {
1894
- exitWithError(usageError(`Invalid provider: ${provider}. Video extend supports: kling, veo`));
1895
- }
1896
- }
1897
- catch (error) {
1898
- const msg = error instanceof Error ? error.message : String(error);
1899
- exitWithError(apiError(`Video extension failed: ${msg}`, true));
1900
- }
1901
- });
1902
- export async function executeSpeech(options) {
1903
- try {
1904
- const apiKey = hasApiKey("ELEVENLABS_API_KEY")
1905
- ? (await getApiKeyFromConfig("elevenlabs") || process.env.ELEVENLABS_API_KEY)
1906
- : null;
1907
- if (!apiKey)
1908
- return { success: false, error: "ElevenLabs API key required. Set ELEVENLABS_API_KEY or run: vibe setup" };
1909
- const elevenlabs = new ElevenLabsProvider();
1910
- await elevenlabs.initialize({ apiKey });
1911
- const result = await elevenlabs.textToSpeech(options.text, {
1912
- voiceId: options.voice || "21m00Tcm4TlvDq8ikWAM",
1913
- });
1914
- if (!result.success || !result.audioBuffer) {
1915
- return { success: false, error: result.error || "TTS generation failed" };
1916
- }
1917
- const outputPath = resolve(process.cwd(), options.output || "output.mp3");
1918
- await writeFile(outputPath, result.audioBuffer);
1919
- return { success: true, outputPath, characterCount: result.characterCount };
1920
- }
1921
- catch (error) {
1922
- return { success: false, error: `TTS failed: ${error instanceof Error ? error.message : String(error)}` };
1923
- }
1924
- }
1925
- export async function executeSoundEffect(options) {
1926
- try {
1927
- const apiKey = hasApiKey("ELEVENLABS_API_KEY")
1928
- ? (await getApiKeyFromConfig("elevenlabs") || process.env.ELEVENLABS_API_KEY)
1929
- : null;
1930
- if (!apiKey)
1931
- return { success: false, error: "ElevenLabs API key required. Set ELEVENLABS_API_KEY or run: vibe setup" };
1932
- const elevenlabs = new ElevenLabsProvider();
1933
- await elevenlabs.initialize({ apiKey });
1934
- const result = await elevenlabs.generateSoundEffect(options.prompt, {
1935
- duration: options.duration,
1936
- promptInfluence: options.promptInfluence,
1937
- });
1938
- if (!result.success || !result.audioBuffer) {
1939
- return { success: false, error: result.error || "Sound effect generation failed" };
1940
- }
1941
- const outputPath = resolve(process.cwd(), options.output || "sound-effect.mp3");
1942
- await writeFile(outputPath, result.audioBuffer);
1943
- return { success: true, outputPath };
1944
- }
1945
- catch (error) {
1946
- return { success: false, error: `SFX failed: ${error instanceof Error ? error.message : String(error)}` };
1947
- }
1948
- }
1949
- export async function executeMusic(options) {
1950
- try {
1951
- const provider = options.provider || "elevenlabs";
1952
- if (provider === "elevenlabs") {
1953
- const apiKey = hasApiKey("ELEVENLABS_API_KEY")
1954
- ? (await getApiKeyFromConfig("elevenlabs") || process.env.ELEVENLABS_API_KEY)
1955
- : null;
1956
- if (!apiKey)
1957
- return { success: false, error: "ElevenLabs API key required. Set ELEVENLABS_API_KEY or run: vibe setup" };
1958
- const elevenlabs = new ElevenLabsProvider();
1959
- await elevenlabs.initialize({ apiKey });
1960
- const duration = Math.max(3, Math.min(600, options.duration || 8));
1961
- const result = await elevenlabs.generateMusic(options.prompt, {
1962
- duration,
1963
- forceInstrumental: options.instrumental || false,
1964
- });
1965
- if (!result.success || !result.audioBuffer) {
1966
- return { success: false, error: result.error || "Music generation failed" };
1967
- }
1968
- const outputPath = resolve(process.cwd(), options.output || "music.mp3");
1969
- await writeFile(outputPath, result.audioBuffer);
1970
- return { success: true, outputPath, provider: "elevenlabs", duration };
1971
- }
1972
- // Replicate MusicGen
1973
- const apiKey = hasApiKey("REPLICATE_API_TOKEN")
1974
- ? (await getApiKeyFromConfig("replicate") || process.env.REPLICATE_API_TOKEN)
1975
- : null;
1976
- if (!apiKey)
1977
- return { success: false, error: "Replicate API token required. Set REPLICATE_API_TOKEN or run: vibe setup" };
1978
- const replicate = new ReplicateProvider();
1979
- await replicate.initialize({ apiKey });
1980
- const duration = Math.max(1, Math.min(30, options.duration || 8));
1981
- const result = await replicate.generateMusic(options.prompt, { duration });
1982
- if (!result.success || !result.taskId) {
1983
- return { success: false, error: result.error || "Music generation failed" };
1984
- }
1985
- const finalResult = await replicate.waitForMusic(result.taskId);
1986
- if (!finalResult.success || !finalResult.audioUrl) {
1987
- return { success: false, error: finalResult.error || "Music generation failed" };
1988
- }
1989
- const response = await fetch(finalResult.audioUrl);
1990
- if (!response.ok)
1991
- return { success: false, error: "Failed to download generated audio" };
1992
- const audioBuffer = Buffer.from(await response.arrayBuffer());
1993
- const outputPath = resolve(process.cwd(), options.output || "music.mp3");
1994
- await writeFile(outputPath, audioBuffer);
1995
- return { success: true, outputPath, provider: "replicate", duration };
1996
- }
1997
- catch (error) {
1998
- return { success: false, error: `Music failed: ${error instanceof Error ? error.message : String(error)}` };
1999
- }
2000
- }
2001
- export async function executeStoryboard(options) {
2002
- const { content, duration, creativity = "low", output, apiKey } = options;
2003
- try {
2004
- const key = apiKey || process.env.ANTHROPIC_API_KEY;
2005
- if (!key)
2006
- return { success: false, error: "ANTHROPIC_API_KEY required" };
2007
- const claude = new ClaudeProvider();
2008
- await claude.initialize({ apiKey: key });
2009
- const segments = await claude.analyzeContent(content, duration, { creativity });
2010
- if (segments.length === 0) {
2011
- return { success: false, error: "Could not generate storyboard" };
2012
- }
2013
- // Sanitize LLM output
2014
- for (const seg of segments) {
2015
- seg.description = sanitizeLLMResponse(seg.description);
2016
- if (seg.visuals)
2017
- seg.visuals = sanitizeLLMResponse(seg.visuals);
2018
- }
2019
- let outputPath;
2020
- if (output) {
2021
- outputPath = resolve(process.cwd(), output);
2022
- await writeFile(outputPath, JSON.stringify(segments, null, 2), "utf-8");
2023
- }
2024
- return { success: true, segments, segmentCount: segments.length, outputPath };
2025
- }
2026
- catch (error) {
2027
- return { success: false, error: `Storyboard failed: ${error instanceof Error ? error.message : String(error)}` };
2028
- }
2029
- }
2030
- //# sourceMappingURL=generate.js.map