@vibeframe/cli 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (420) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-lint.log +21 -0
  3. package/.turbo/turbo-test.log +689 -0
  4. package/dist/agent/adapters/claude.d.ts +15 -0
  5. package/dist/agent/adapters/claude.d.ts.map +1 -0
  6. package/dist/agent/adapters/claude.js +119 -0
  7. package/dist/agent/adapters/claude.js.map +1 -0
  8. package/dist/agent/adapters/gemini.d.ts +15 -0
  9. package/dist/agent/adapters/gemini.d.ts.map +1 -0
  10. package/dist/agent/adapters/gemini.js +132 -0
  11. package/dist/agent/adapters/gemini.js.map +1 -0
  12. package/dist/agent/adapters/index.d.ts +27 -0
  13. package/dist/agent/adapters/index.d.ts.map +1 -0
  14. package/dist/agent/adapters/index.js +38 -0
  15. package/dist/agent/adapters/index.js.map +1 -0
  16. package/dist/agent/adapters/ollama.d.ts +20 -0
  17. package/dist/agent/adapters/ollama.d.ts.map +1 -0
  18. package/dist/agent/adapters/ollama.js +186 -0
  19. package/dist/agent/adapters/ollama.js.map +1 -0
  20. package/dist/agent/adapters/openai.d.ts +15 -0
  21. package/dist/agent/adapters/openai.d.ts.map +1 -0
  22. package/dist/agent/adapters/openai.js +92 -0
  23. package/dist/agent/adapters/openai.js.map +1 -0
  24. package/dist/agent/adapters/xai.d.ts +15 -0
  25. package/dist/agent/adapters/xai.d.ts.map +1 -0
  26. package/dist/agent/adapters/xai.js +95 -0
  27. package/dist/agent/adapters/xai.js.map +1 -0
  28. package/dist/agent/index.d.ts +69 -0
  29. package/dist/agent/index.d.ts.map +1 -0
  30. package/dist/agent/index.js +180 -0
  31. package/dist/agent/index.js.map +1 -0
  32. package/dist/agent/memory/index.d.ts +70 -0
  33. package/dist/agent/memory/index.d.ts.map +1 -0
  34. package/dist/agent/memory/index.js +132 -0
  35. package/dist/agent/memory/index.js.map +1 -0
  36. package/dist/agent/prompts/system.d.ts +6 -0
  37. package/dist/agent/prompts/system.d.ts.map +1 -0
  38. package/dist/agent/prompts/system.js +103 -0
  39. package/dist/agent/prompts/system.js.map +1 -0
  40. package/dist/agent/tools/ai-editing.d.ts +15 -0
  41. package/dist/agent/tools/ai-editing.d.ts.map +1 -0
  42. package/dist/agent/tools/ai-editing.js +763 -0
  43. package/dist/agent/tools/ai-editing.js.map +1 -0
  44. package/dist/agent/tools/ai-generation.d.ts +13 -0
  45. package/dist/agent/tools/ai-generation.d.ts.map +1 -0
  46. package/dist/agent/tools/ai-generation.js +973 -0
  47. package/dist/agent/tools/ai-generation.js.map +1 -0
  48. package/dist/agent/tools/ai-pipeline.d.ts +14 -0
  49. package/dist/agent/tools/ai-pipeline.d.ts.map +1 -0
  50. package/dist/agent/tools/ai-pipeline.js +961 -0
  51. package/dist/agent/tools/ai-pipeline.js.map +1 -0
  52. package/dist/agent/tools/ai.d.ts +13 -0
  53. package/dist/agent/tools/ai.d.ts.map +1 -0
  54. package/dist/agent/tools/ai.js +19 -0
  55. package/dist/agent/tools/ai.js.map +1 -0
  56. package/dist/agent/tools/batch.d.ts +6 -0
  57. package/dist/agent/tools/batch.d.ts.map +1 -0
  58. package/dist/agent/tools/batch.js +383 -0
  59. package/dist/agent/tools/batch.js.map +1 -0
  60. package/dist/agent/tools/e2e.test.d.ts +26 -0
  61. package/dist/agent/tools/e2e.test.d.ts.map +1 -0
  62. package/dist/agent/tools/e2e.test.js +397 -0
  63. package/dist/agent/tools/e2e.test.js.map +1 -0
  64. package/dist/agent/tools/export.d.ts +6 -0
  65. package/dist/agent/tools/export.d.ts.map +1 -0
  66. package/dist/agent/tools/export.js +171 -0
  67. package/dist/agent/tools/export.js.map +1 -0
  68. package/dist/agent/tools/filesystem.d.ts +6 -0
  69. package/dist/agent/tools/filesystem.d.ts.map +1 -0
  70. package/dist/agent/tools/filesystem.js +212 -0
  71. package/dist/agent/tools/filesystem.js.map +1 -0
  72. package/dist/agent/tools/index.d.ts +65 -0
  73. package/dist/agent/tools/index.d.ts.map +1 -0
  74. package/dist/agent/tools/index.js +120 -0
  75. package/dist/agent/tools/index.js.map +1 -0
  76. package/dist/agent/tools/integration.test.d.ts +11 -0
  77. package/dist/agent/tools/integration.test.d.ts.map +1 -0
  78. package/dist/agent/tools/integration.test.js +659 -0
  79. package/dist/agent/tools/integration.test.js.map +1 -0
  80. package/dist/agent/tools/media.d.ts +6 -0
  81. package/dist/agent/tools/media.d.ts.map +1 -0
  82. package/dist/agent/tools/media.js +616 -0
  83. package/dist/agent/tools/media.js.map +1 -0
  84. package/dist/agent/tools/project.d.ts +6 -0
  85. package/dist/agent/tools/project.d.ts.map +1 -0
  86. package/dist/agent/tools/project.js +284 -0
  87. package/dist/agent/tools/project.js.map +1 -0
  88. package/dist/agent/tools/timeline.d.ts +6 -0
  89. package/dist/agent/tools/timeline.d.ts.map +1 -0
  90. package/dist/agent/tools/timeline.js +873 -0
  91. package/dist/agent/tools/timeline.js.map +1 -0
  92. package/dist/agent/types.d.ts +59 -0
  93. package/dist/agent/types.d.ts.map +1 -0
  94. package/dist/agent/types.js +5 -0
  95. package/dist/agent/types.js.map +1 -0
  96. package/dist/commands/agent.d.ts +21 -0
  97. package/dist/commands/agent.d.ts.map +1 -0
  98. package/dist/commands/agent.js +290 -0
  99. package/dist/commands/agent.js.map +1 -0
  100. package/dist/commands/ai-analyze.d.ts +106 -0
  101. package/dist/commands/ai-analyze.d.ts.map +1 -0
  102. package/dist/commands/ai-analyze.js +327 -0
  103. package/dist/commands/ai-analyze.js.map +1 -0
  104. package/dist/commands/ai-animated-caption.d.ts +64 -0
  105. package/dist/commands/ai-animated-caption.d.ts.map +1 -0
  106. package/dist/commands/ai-animated-caption.js +272 -0
  107. package/dist/commands/ai-animated-caption.js.map +1 -0
  108. package/dist/commands/ai-audio.d.ts +20 -0
  109. package/dist/commands/ai-audio.d.ts.map +1 -0
  110. package/dist/commands/ai-audio.js +808 -0
  111. package/dist/commands/ai-audio.js.map +1 -0
  112. package/dist/commands/ai-broll.d.ts +15 -0
  113. package/dist/commands/ai-broll.d.ts.map +1 -0
  114. package/dist/commands/ai-broll.js +406 -0
  115. package/dist/commands/ai-broll.js.map +1 -0
  116. package/dist/commands/ai-edit-cli.d.ts +14 -0
  117. package/dist/commands/ai-edit-cli.d.ts.map +1 -0
  118. package/dist/commands/ai-edit-cli.js +579 -0
  119. package/dist/commands/ai-edit-cli.js.map +1 -0
  120. package/dist/commands/ai-edit.d.ts +398 -0
  121. package/dist/commands/ai-edit.d.ts.map +1 -0
  122. package/dist/commands/ai-edit.js +1019 -0
  123. package/dist/commands/ai-edit.js.map +1 -0
  124. package/dist/commands/ai-fill-gaps.d.ts +14 -0
  125. package/dist/commands/ai-fill-gaps.d.ts.map +1 -0
  126. package/dist/commands/ai-fill-gaps.js +451 -0
  127. package/dist/commands/ai-fill-gaps.js.map +1 -0
  128. package/dist/commands/ai-helpers.d.ts +20 -0
  129. package/dist/commands/ai-helpers.d.ts.map +1 -0
  130. package/dist/commands/ai-helpers.js +59 -0
  131. package/dist/commands/ai-helpers.js.map +1 -0
  132. package/dist/commands/ai-highlights.d.ts +127 -0
  133. package/dist/commands/ai-highlights.d.ts.map +1 -0
  134. package/dist/commands/ai-highlights.js +1026 -0
  135. package/dist/commands/ai-highlights.js.map +1 -0
  136. package/dist/commands/ai-image.d.ts +34 -0
  137. package/dist/commands/ai-image.d.ts.map +1 -0
  138. package/dist/commands/ai-image.js +653 -0
  139. package/dist/commands/ai-image.js.map +1 -0
  140. package/dist/commands/ai-motion.d.ts +50 -0
  141. package/dist/commands/ai-motion.d.ts.map +1 -0
  142. package/dist/commands/ai-motion.js +271 -0
  143. package/dist/commands/ai-motion.js.map +1 -0
  144. package/dist/commands/ai-narrate.d.ts +66 -0
  145. package/dist/commands/ai-narrate.d.ts.map +1 -0
  146. package/dist/commands/ai-narrate.js +329 -0
  147. package/dist/commands/ai-narrate.js.map +1 -0
  148. package/dist/commands/ai-review.d.ts +57 -0
  149. package/dist/commands/ai-review.d.ts.map +1 -0
  150. package/dist/commands/ai-review.js +251 -0
  151. package/dist/commands/ai-review.js.map +1 -0
  152. package/dist/commands/ai-script-pipeline-cli.d.ts +9 -0
  153. package/dist/commands/ai-script-pipeline-cli.d.ts.map +1 -0
  154. package/dist/commands/ai-script-pipeline-cli.js +1494 -0
  155. package/dist/commands/ai-script-pipeline-cli.js.map +1 -0
  156. package/dist/commands/ai-script-pipeline.d.ts +259 -0
  157. package/dist/commands/ai-script-pipeline.d.ts.map +1 -0
  158. package/dist/commands/ai-script-pipeline.js +1027 -0
  159. package/dist/commands/ai-script-pipeline.js.map +1 -0
  160. package/dist/commands/ai-suggest-edit.d.ts +14 -0
  161. package/dist/commands/ai-suggest-edit.d.ts.map +1 -0
  162. package/dist/commands/ai-suggest-edit.js +220 -0
  163. package/dist/commands/ai-suggest-edit.js.map +1 -0
  164. package/dist/commands/ai-video-fx.d.ts +14 -0
  165. package/dist/commands/ai-video-fx.d.ts.map +1 -0
  166. package/dist/commands/ai-video-fx.js +395 -0
  167. package/dist/commands/ai-video-fx.js.map +1 -0
  168. package/dist/commands/ai-video.d.ts +15 -0
  169. package/dist/commands/ai-video.d.ts.map +1 -0
  170. package/dist/commands/ai-video.js +785 -0
  171. package/dist/commands/ai-video.js.map +1 -0
  172. package/dist/commands/ai-viral.d.ts +15 -0
  173. package/dist/commands/ai-viral.d.ts.map +1 -0
  174. package/dist/commands/ai-viral.js +519 -0
  175. package/dist/commands/ai-viral.js.map +1 -0
  176. package/dist/commands/ai-visual-fx.d.ts +14 -0
  177. package/dist/commands/ai-visual-fx.d.ts.map +1 -0
  178. package/dist/commands/ai-visual-fx.js +505 -0
  179. package/dist/commands/ai-visual-fx.js.map +1 -0
  180. package/dist/commands/ai.d.ts +38 -0
  181. package/dist/commands/ai.d.ts.map +1 -0
  182. package/dist/commands/ai.js +225 -0
  183. package/dist/commands/ai.js.map +1 -0
  184. package/dist/commands/ai.test.d.ts +2 -0
  185. package/dist/commands/ai.test.d.ts.map +1 -0
  186. package/dist/commands/ai.test.js +554 -0
  187. package/dist/commands/ai.test.js.map +1 -0
  188. package/dist/commands/analyze.d.ts +16 -0
  189. package/dist/commands/analyze.d.ts.map +1 -0
  190. package/dist/commands/analyze.js +247 -0
  191. package/dist/commands/analyze.js.map +1 -0
  192. package/dist/commands/audio.d.ts +18 -0
  193. package/dist/commands/audio.d.ts.map +1 -0
  194. package/dist/commands/audio.js +539 -0
  195. package/dist/commands/audio.js.map +1 -0
  196. package/dist/commands/batch.d.ts +3 -0
  197. package/dist/commands/batch.d.ts.map +1 -0
  198. package/dist/commands/batch.js +366 -0
  199. package/dist/commands/batch.js.map +1 -0
  200. package/dist/commands/batch.test.d.ts +2 -0
  201. package/dist/commands/batch.test.d.ts.map +1 -0
  202. package/dist/commands/batch.test.js +203 -0
  203. package/dist/commands/batch.test.js.map +1 -0
  204. package/dist/commands/detect.d.ts +3 -0
  205. package/dist/commands/detect.d.ts.map +1 -0
  206. package/dist/commands/detect.js +273 -0
  207. package/dist/commands/detect.js.map +1 -0
  208. package/dist/commands/doctor.d.ts +6 -0
  209. package/dist/commands/doctor.d.ts.map +1 -0
  210. package/dist/commands/doctor.js +191 -0
  211. package/dist/commands/doctor.js.map +1 -0
  212. package/dist/commands/edit-cmd.d.ts +26 -0
  213. package/dist/commands/edit-cmd.d.ts.map +1 -0
  214. package/dist/commands/edit-cmd.js +870 -0
  215. package/dist/commands/edit-cmd.js.map +1 -0
  216. package/dist/commands/export.d.ts +39 -0
  217. package/dist/commands/export.d.ts.map +1 -0
  218. package/dist/commands/export.js +730 -0
  219. package/dist/commands/export.js.map +1 -0
  220. package/dist/commands/generate.d.ts +25 -0
  221. package/dist/commands/generate.d.ts.map +1 -0
  222. package/dist/commands/generate.js +1885 -0
  223. package/dist/commands/generate.js.map +1 -0
  224. package/dist/commands/media.d.ts +3 -0
  225. package/dist/commands/media.d.ts.map +1 -0
  226. package/dist/commands/media.js +165 -0
  227. package/dist/commands/media.js.map +1 -0
  228. package/dist/commands/output.d.ts +45 -0
  229. package/dist/commands/output.d.ts.map +1 -0
  230. package/dist/commands/output.js +122 -0
  231. package/dist/commands/output.js.map +1 -0
  232. package/dist/commands/pipeline.d.ts +19 -0
  233. package/dist/commands/pipeline.d.ts.map +1 -0
  234. package/dist/commands/pipeline.js +345 -0
  235. package/dist/commands/pipeline.js.map +1 -0
  236. package/dist/commands/project.d.ts +3 -0
  237. package/dist/commands/project.d.ts.map +1 -0
  238. package/dist/commands/project.js +139 -0
  239. package/dist/commands/project.js.map +1 -0
  240. package/dist/commands/project.test.d.ts +2 -0
  241. package/dist/commands/project.test.d.ts.map +1 -0
  242. package/dist/commands/project.test.js +105 -0
  243. package/dist/commands/project.test.js.map +1 -0
  244. package/dist/commands/sanitize.d.ts +21 -0
  245. package/dist/commands/sanitize.d.ts.map +1 -0
  246. package/dist/commands/sanitize.js +56 -0
  247. package/dist/commands/sanitize.js.map +1 -0
  248. package/dist/commands/schema.d.ts +11 -0
  249. package/dist/commands/schema.d.ts.map +1 -0
  250. package/dist/commands/schema.js +101 -0
  251. package/dist/commands/schema.js.map +1 -0
  252. package/dist/commands/setup.d.ts +6 -0
  253. package/dist/commands/setup.d.ts.map +1 -0
  254. package/dist/commands/setup.js +440 -0
  255. package/dist/commands/setup.js.map +1 -0
  256. package/dist/commands/timeline.d.ts +3 -0
  257. package/dist/commands/timeline.d.ts.map +1 -0
  258. package/dist/commands/timeline.js +469 -0
  259. package/dist/commands/timeline.js.map +1 -0
  260. package/dist/commands/timeline.test.d.ts +2 -0
  261. package/dist/commands/timeline.test.d.ts.map +1 -0
  262. package/dist/commands/timeline.test.js +320 -0
  263. package/dist/commands/timeline.test.js.map +1 -0
  264. package/dist/commands/validate.d.ts +32 -0
  265. package/dist/commands/validate.d.ts.map +1 -0
  266. package/dist/commands/validate.js +63 -0
  267. package/dist/commands/validate.js.map +1 -0
  268. package/dist/config/config.test.d.ts +2 -0
  269. package/dist/config/config.test.d.ts.map +1 -0
  270. package/dist/config/config.test.js +164 -0
  271. package/dist/config/config.test.js.map +1 -0
  272. package/dist/config/index.d.ts +35 -0
  273. package/dist/config/index.d.ts.map +1 -0
  274. package/dist/config/index.js +101 -0
  275. package/dist/config/index.js.map +1 -0
  276. package/dist/config/schema.d.ts +43 -0
  277. package/dist/config/schema.d.ts.map +1 -0
  278. package/dist/config/schema.js +42 -0
  279. package/dist/config/schema.js.map +1 -0
  280. package/dist/engine/index.d.ts +3 -0
  281. package/dist/engine/index.d.ts.map +1 -0
  282. package/dist/engine/index.js +2 -0
  283. package/dist/engine/index.js.map +1 -0
  284. package/dist/engine/project.d.ts +84 -0
  285. package/dist/engine/project.d.ts.map +1 -0
  286. package/dist/engine/project.js +355 -0
  287. package/dist/engine/project.js.map +1 -0
  288. package/dist/engine/project.test.d.ts +2 -0
  289. package/dist/engine/project.test.d.ts.map +1 -0
  290. package/dist/engine/project.test.js +599 -0
  291. package/dist/engine/project.test.js.map +1 -0
  292. package/dist/index.d.ts +7 -0
  293. package/dist/index.d.ts.map +1 -0
  294. package/dist/index.js +131 -0
  295. package/dist/index.js.map +1 -0
  296. package/dist/utils/api-key.d.ts +36 -0
  297. package/dist/utils/api-key.d.ts.map +1 -0
  298. package/dist/utils/api-key.js +211 -0
  299. package/dist/utils/api-key.js.map +1 -0
  300. package/dist/utils/api-key.test.d.ts +2 -0
  301. package/dist/utils/api-key.test.d.ts.map +1 -0
  302. package/dist/utils/api-key.test.js +35 -0
  303. package/dist/utils/api-key.test.js.map +1 -0
  304. package/dist/utils/audio.d.ts +23 -0
  305. package/dist/utils/audio.d.ts.map +1 -0
  306. package/dist/utils/audio.js +79 -0
  307. package/dist/utils/audio.js.map +1 -0
  308. package/dist/utils/exec-safe.d.ts +22 -0
  309. package/dist/utils/exec-safe.d.ts.map +1 -0
  310. package/dist/utils/exec-safe.js +62 -0
  311. package/dist/utils/exec-safe.js.map +1 -0
  312. package/dist/utils/first-run.d.ts +13 -0
  313. package/dist/utils/first-run.d.ts.map +1 -0
  314. package/dist/utils/first-run.js +48 -0
  315. package/dist/utils/first-run.js.map +1 -0
  316. package/dist/utils/provider-resolver.d.ts +15 -0
  317. package/dist/utils/provider-resolver.d.ts.map +1 -0
  318. package/dist/utils/provider-resolver.js +42 -0
  319. package/dist/utils/provider-resolver.js.map +1 -0
  320. package/dist/utils/remotion.d.ts +210 -0
  321. package/dist/utils/remotion.d.ts.map +1 -0
  322. package/dist/utils/remotion.js +731 -0
  323. package/dist/utils/remotion.js.map +1 -0
  324. package/dist/utils/subtitle.d.ts +65 -0
  325. package/dist/utils/subtitle.d.ts.map +1 -0
  326. package/dist/utils/subtitle.js +135 -0
  327. package/dist/utils/subtitle.js.map +1 -0
  328. package/dist/utils/subtitle.test.d.ts +2 -0
  329. package/dist/utils/subtitle.test.d.ts.map +1 -0
  330. package/dist/utils/subtitle.test.js +175 -0
  331. package/dist/utils/subtitle.test.js.map +1 -0
  332. package/dist/utils/tty.d.ts +45 -0
  333. package/dist/utils/tty.d.ts.map +1 -0
  334. package/dist/utils/tty.js +172 -0
  335. package/dist/utils/tty.js.map +1 -0
  336. package/package.json +102 -0
  337. package/src/agent/adapters/claude.ts +143 -0
  338. package/src/agent/adapters/gemini.ts +159 -0
  339. package/src/agent/adapters/index.ts +61 -0
  340. package/src/agent/adapters/ollama.ts +231 -0
  341. package/src/agent/adapters/openai.ts +116 -0
  342. package/src/agent/adapters/xai.ts +119 -0
  343. package/src/agent/index.ts +251 -0
  344. package/src/agent/memory/index.ts +151 -0
  345. package/src/agent/prompts/system.ts +106 -0
  346. package/src/agent/tools/ai-editing.ts +845 -0
  347. package/src/agent/tools/ai-generation.ts +1073 -0
  348. package/src/agent/tools/ai-pipeline.ts +1055 -0
  349. package/src/agent/tools/ai.ts +21 -0
  350. package/src/agent/tools/batch.ts +429 -0
  351. package/src/agent/tools/e2e.test.ts +545 -0
  352. package/src/agent/tools/export.ts +184 -0
  353. package/src/agent/tools/filesystem.ts +237 -0
  354. package/src/agent/tools/index.ts +150 -0
  355. package/src/agent/tools/integration.test.ts +775 -0
  356. package/src/agent/tools/media.ts +697 -0
  357. package/src/agent/tools/project.ts +313 -0
  358. package/src/agent/tools/timeline.ts +951 -0
  359. package/src/agent/types.ts +68 -0
  360. package/src/commands/agent.ts +340 -0
  361. package/src/commands/ai-analyze.ts +429 -0
  362. package/src/commands/ai-animated-caption.ts +390 -0
  363. package/src/commands/ai-audio.ts +941 -0
  364. package/src/commands/ai-broll.ts +490 -0
  365. package/src/commands/ai-edit-cli.ts +658 -0
  366. package/src/commands/ai-edit.ts +1542 -0
  367. package/src/commands/ai-fill-gaps.ts +566 -0
  368. package/src/commands/ai-helpers.ts +65 -0
  369. package/src/commands/ai-highlights.ts +1303 -0
  370. package/src/commands/ai-image.ts +761 -0
  371. package/src/commands/ai-motion.ts +347 -0
  372. package/src/commands/ai-narrate.ts +451 -0
  373. package/src/commands/ai-review.ts +309 -0
  374. package/src/commands/ai-script-pipeline-cli.ts +1710 -0
  375. package/src/commands/ai-script-pipeline.ts +1365 -0
  376. package/src/commands/ai-suggest-edit.ts +264 -0
  377. package/src/commands/ai-video-fx.ts +445 -0
  378. package/src/commands/ai-video.ts +915 -0
  379. package/src/commands/ai-viral.ts +595 -0
  380. package/src/commands/ai-visual-fx.ts +601 -0
  381. package/src/commands/ai.test.ts +627 -0
  382. package/src/commands/ai.ts +307 -0
  383. package/src/commands/analyze.ts +282 -0
  384. package/src/commands/audio.ts +644 -0
  385. package/src/commands/batch.test.ts +279 -0
  386. package/src/commands/batch.ts +440 -0
  387. package/src/commands/detect.ts +329 -0
  388. package/src/commands/doctor.ts +237 -0
  389. package/src/commands/edit-cmd.ts +1014 -0
  390. package/src/commands/export.ts +918 -0
  391. package/src/commands/generate.ts +2146 -0
  392. package/src/commands/media.ts +177 -0
  393. package/src/commands/output.ts +142 -0
  394. package/src/commands/pipeline.ts +398 -0
  395. package/src/commands/project.test.ts +127 -0
  396. package/src/commands/project.ts +149 -0
  397. package/src/commands/sanitize.ts +60 -0
  398. package/src/commands/schema.ts +130 -0
  399. package/src/commands/setup.ts +509 -0
  400. package/src/commands/timeline.test.ts +499 -0
  401. package/src/commands/timeline.ts +529 -0
  402. package/src/commands/validate.ts +77 -0
  403. package/src/config/config.test.ts +197 -0
  404. package/src/config/index.ts +125 -0
  405. package/src/config/schema.ts +82 -0
  406. package/src/engine/index.ts +2 -0
  407. package/src/engine/project.test.ts +702 -0
  408. package/src/engine/project.ts +439 -0
  409. package/src/index.ts +146 -0
  410. package/src/utils/api-key.test.ts +41 -0
  411. package/src/utils/api-key.ts +247 -0
  412. package/src/utils/audio.ts +83 -0
  413. package/src/utils/exec-safe.ts +75 -0
  414. package/src/utils/first-run.ts +52 -0
  415. package/src/utils/provider-resolver.ts +56 -0
  416. package/src/utils/remotion.ts +951 -0
  417. package/src/utils/subtitle.test.ts +227 -0
  418. package/src/utils/subtitle.ts +169 -0
  419. package/src/utils/tty.ts +196 -0
  420. package/tsconfig.json +20 -0
@@ -0,0 +1,595 @@
1
+ /**
2
+ * @module ai-viral
3
+ * @description AI Viral Optimizer. Optimizes video for viral potential across
4
+ * platforms (YouTube, TikTok, Instagram, Twitter). Analyzes content with
5
+ * Whisper + Claude, generates platform-specific cuts and captions.
6
+ *
7
+ * ## Commands: vibe ai viral
8
+ * ## Dependencies: Whisper, Claude, FFmpeg
9
+ * @see MODELS.md for AI model configuration
10
+ */
11
+
12
+ import { Command } from "commander";
13
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
14
+ import { resolve, dirname, basename, relative } from "node:path";
15
+ import { existsSync } from "node:fs";
16
+ import chalk from "chalk";
17
+ import ora from "ora";
18
+ import {
19
+ WhisperProvider,
20
+ ClaudeProvider,
21
+ type PlatformSpec,
22
+ type ViralOptimizationResult,
23
+ } from "@vibeframe/ai-providers";
24
+ import { Project, type ProjectFile } from "../engine/index.js";
25
+ import { getApiKey } from "../utils/api-key.js";
26
+ import { execSafe, commandExists } from "../utils/exec-safe.js";
27
+ import { formatTime } from "./ai-helpers.js";
28
+ import { autoNarrate } from "./ai-narrate.js";
29
+
30
+ // Platform specifications for viral optimization
31
+ export const PLATFORM_SPECS: Record<string, PlatformSpec> = {
32
+ youtube: {
33
+ id: "youtube",
34
+ name: "YouTube",
35
+ aspectRatio: "16:9",
36
+ maxDuration: 600,
37
+ idealDuration: { min: 60, max: 480 },
38
+ features: { captions: true, hook: true },
39
+ },
40
+ "youtube-shorts": {
41
+ id: "youtube-shorts",
42
+ name: "YouTube Shorts",
43
+ aspectRatio: "9:16",
44
+ maxDuration: 60,
45
+ idealDuration: { min: 15, max: 60 },
46
+ features: { captions: true, hook: true },
47
+ },
48
+ tiktok: {
49
+ id: "tiktok",
50
+ name: "TikTok",
51
+ aspectRatio: "9:16",
52
+ maxDuration: 180,
53
+ idealDuration: { min: 15, max: 60 },
54
+ features: { captions: true, hook: true },
55
+ },
56
+ "instagram-reels": {
57
+ id: "instagram-reels",
58
+ name: "Instagram Reels",
59
+ aspectRatio: "9:16",
60
+ maxDuration: 90,
61
+ idealDuration: { min: 15, max: 60 },
62
+ features: { captions: true, hook: true },
63
+ },
64
+ "instagram-feed": {
65
+ id: "instagram-feed",
66
+ name: "Instagram Feed",
67
+ aspectRatio: "1:1",
68
+ maxDuration: 60,
69
+ idealDuration: { min: 15, max: 60 },
70
+ features: { captions: true, hook: false },
71
+ },
72
+ twitter: {
73
+ id: "twitter",
74
+ name: "Twitter",
75
+ aspectRatio: "16:9",
76
+ maxDuration: 140,
77
+ idealDuration: { min: 15, max: 60 },
78
+ features: { captions: true, hook: true },
79
+ },
80
+ };
81
+
82
+ export function registerViralCommand(ai: Command): void {
83
+ // Viral Optimizer command
84
+ ai
85
+ .command("viral")
86
+ .description("Optimize video for viral potential across platforms (deprecated)")
87
+ .argument("<project>", "Source project file")
88
+ .option("--platforms <list>", "Target platforms (comma-separated): youtube, youtube-shorts, tiktok, instagram-reels, instagram-feed, twitter", "all")
89
+ .option("-o, --output-dir <dir>", "Output directory for platform variants", "viral-output")
90
+ .option("--analyze-only", "Only analyze, don't generate variants")
91
+ .option("--skip-captions", "Skip caption generation")
92
+ .option("--caption-style <style>", "Caption style: minimal, bold, animated", "bold")
93
+ .option("--hook-duration <sec>", "Hook duration in seconds", "3")
94
+ .option("-l, --language <lang>", "Language code for transcription")
95
+ .option("--auto-narrate", "Auto-generate narration if no audio source found")
96
+ .option("--narrate-voice <voice>", "Voice for auto-narration (default: rachel)", "rachel")
97
+ .option("--narrate-style <style>", "Style for auto-narration: informative, energetic, calm, dramatic", "informative")
98
+ .action(async (projectPath: string, options) => {
99
+ console.warn(chalk.yellow("Warning: 'pipeline viral' is deprecated. Use individual commands instead:"));
100
+ console.warn(chalk.dim(" vibe edit reframe <video> -a 9:16 → vibe edit caption <video> -s bold"));
101
+ console.warn();
102
+ try {
103
+ // Validate API keys
104
+ const openaiApiKey = await getApiKey("OPENAI_API_KEY", "OpenAI");
105
+ if (!openaiApiKey) {
106
+ console.error(chalk.red("OpenAI API key required for Whisper transcription. Set OPENAI_API_KEY in .env or run: vibe setup"));
107
+ console.error(chalk.dim("Set OPENAI_API_KEY environment variable"));
108
+ process.exit(1);
109
+ }
110
+
111
+ const claudeApiKey = await getApiKey("ANTHROPIC_API_KEY", "Anthropic");
112
+ if (!claudeApiKey) {
113
+ console.error(chalk.red("Anthropic API key required for viral analysis. Set ANTHROPIC_API_KEY in .env or run: vibe setup"));
114
+ console.error(chalk.dim("Set ANTHROPIC_API_KEY environment variable"));
115
+ process.exit(1);
116
+ }
117
+
118
+ // Load project
119
+ let filePath = resolve(process.cwd(), projectPath);
120
+ // If directory, look for project.vibe.json inside
121
+ const { statSync } = await import("node:fs");
122
+ try {
123
+ if (statSync(filePath).isDirectory()) {
124
+ const candidates = ["project.vibe.json", ".vibe.json"];
125
+ let found = false;
126
+ for (const candidate of candidates) {
127
+ const candidatePath = resolve(filePath, candidate);
128
+ if (existsSync(candidatePath)) {
129
+ filePath = candidatePath;
130
+ found = true;
131
+ break;
132
+ }
133
+ }
134
+ if (!found) {
135
+ // Try any .vibe.json file in the directory
136
+ const { readdirSync } = await import("node:fs");
137
+ const files = readdirSync(filePath).filter((f: string) => f.endsWith(".vibe.json"));
138
+ if (files.length > 0) {
139
+ filePath = resolve(filePath, files[0]);
140
+ } else {
141
+ console.error(chalk.red(`No .vibe.json project file found in: ${filePath}`));
142
+ process.exit(1);
143
+ }
144
+ }
145
+ }
146
+ } catch { /* not a directory, treat as file */ }
147
+
148
+ if (!existsSync(filePath)) {
149
+ console.error(chalk.red(`Project file not found: ${filePath}`));
150
+ process.exit(1);
151
+ }
152
+
153
+ const content = await readFile(filePath, "utf-8");
154
+ const data: ProjectFile = JSON.parse(content);
155
+ const project = Project.fromJSON(data);
156
+
157
+ // Parse target platforms
158
+ let targetPlatforms: string[];
159
+ if (options.platforms === "all") {
160
+ targetPlatforms = Object.keys(PLATFORM_SPECS);
161
+ } else {
162
+ targetPlatforms = options.platforms.split(",").map((p: string) => p.trim().toLowerCase());
163
+ // Validate platforms
164
+ for (const platform of targetPlatforms) {
165
+ if (!PLATFORM_SPECS[platform]) {
166
+ console.error(chalk.red(`Unknown platform: ${platform}`));
167
+ console.error(chalk.dim(`Available: ${Object.keys(PLATFORM_SPECS).join(", ")}`));
168
+ process.exit(1);
169
+ }
170
+ }
171
+ }
172
+
173
+ console.log();
174
+ console.log(chalk.bold.cyan("🚀 Viral Optimizer Pipeline"));
175
+ console.log(chalk.dim("─".repeat(60)));
176
+ console.log();
177
+
178
+ // Get project info
179
+ const clips = project.getClips();
180
+ const sources = project.getSources();
181
+
182
+ // Calculate total duration from clips
183
+ let totalDuration = 0;
184
+ for (const clip of clips) {
185
+ const endTime = clip.startTime + clip.duration;
186
+ if (endTime > totalDuration) {
187
+ totalDuration = endTime;
188
+ }
189
+ }
190
+
191
+ const projectInfo = `${project.getMeta().name} (${formatTime(totalDuration)}, ${clips.length} clips)`;
192
+ console.log(`✔ Loaded project: ${chalk.bold(projectInfo)}`);
193
+
194
+ // Step 1: Extract audio and transcribe
195
+ // Find audio source first (narration), fall back to video
196
+ let audioSource = sources.find((s) => s.type === "audio");
197
+ const videoSource = sources.find((s) => s.type === "video");
198
+
199
+ // Check if auto-narrate is needed
200
+ if (!audioSource && videoSource && options.autoNarrate) {
201
+ console.log();
202
+ console.log(chalk.yellow("📝 No narration found, generating with AI..."));
203
+
204
+ const outputDir = resolve(process.cwd(), options.outputDir);
205
+ const videoPath = resolve(dirname(filePath), videoSource.url);
206
+
207
+ const narrateResult = await autoNarrate({
208
+ videoPath,
209
+ duration: totalDuration,
210
+ outputDir,
211
+ voice: options.narrateVoice,
212
+ style: options.narrateStyle as "informative" | "energetic" | "calm" | "dramatic",
213
+ language: options.language || "en",
214
+ });
215
+
216
+ if (!narrateResult.success) {
217
+ console.error(chalk.red(`Auto-narrate failed: ${narrateResult.error}`));
218
+ process.exit(1);
219
+ }
220
+
221
+ console.log(chalk.green(`✔ Generated narration: ${narrateResult.audioPath}`));
222
+
223
+ // Add the generated narration as a source
224
+ // Use relative path from project directory to audio file
225
+ const projectDir = dirname(filePath);
226
+ const relativeAudioPath = relative(projectDir, narrateResult.audioPath!);
227
+ const newAudioSource = project.addSource({
228
+ name: "Auto-generated narration",
229
+ url: relativeAudioPath,
230
+ type: "audio",
231
+ duration: totalDuration,
232
+ });
233
+
234
+ // Add audio clip to timeline
235
+ const audioTrack = project.getTracks().find((t) => t.type === "audio");
236
+ if (audioTrack) {
237
+ project.addClip({
238
+ sourceId: newAudioSource.id,
239
+ trackId: audioTrack.id,
240
+ startTime: 0,
241
+ duration: totalDuration,
242
+ sourceStartOffset: 0,
243
+ sourceEndOffset: totalDuration,
244
+ });
245
+ }
246
+
247
+ // Save updated project
248
+ await writeFile(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
249
+
250
+ // Use the generated segments as transcript
251
+ if (narrateResult.segments && narrateResult.segments.length > 0) {
252
+ // Continue with viral analysis using auto-narrate segments
253
+ audioSource = newAudioSource;
254
+ }
255
+ }
256
+
257
+ const mediaSource = audioSource || videoSource;
258
+ if (!mediaSource) {
259
+ console.error(chalk.red("No video or audio source found in project"));
260
+ process.exit(1);
261
+ }
262
+
263
+ // Check FFmpeg availability
264
+ if (!commandExists("ffmpeg")) {
265
+ console.error(chalk.red("FFmpeg not found. Please install FFmpeg."));
266
+ process.exit(1);
267
+ }
268
+
269
+ const transcribeSpinner = ora("📝 Transcribing content with Whisper...").start();
270
+
271
+ let audioPath = resolve(dirname(filePath), mediaSource.url);
272
+ let tempAudioPath: string | null = null;
273
+
274
+ // Extract audio if video
275
+ if (mediaSource.type === "video") {
276
+ transcribeSpinner.text = "🎵 Extracting audio from video...";
277
+ tempAudioPath = `/tmp/vibe_viral_audio_${Date.now()}.wav`;
278
+ await execSafe("ffmpeg", ["-i", audioPath, "-vn", "-acodec", "pcm_s16le", "-ar", "16000", "-ac", "1", tempAudioPath, "-y"], { maxBuffer: 50 * 1024 * 1024 });
279
+ audioPath = tempAudioPath;
280
+ }
281
+
282
+ // Transcribe with Whisper
283
+ const whisper = new WhisperProvider();
284
+ await whisper.initialize({ apiKey: openaiApiKey });
285
+
286
+ const audioBuffer = await readFile(audioPath);
287
+ const audioBlob = new Blob([audioBuffer]);
288
+
289
+ transcribeSpinner.text = "📝 Transcribing with Whisper...";
290
+ const transcriptResult = await whisper.transcribe(audioBlob, options.language);
291
+
292
+ // Cleanup temp file
293
+ if (tempAudioPath && existsSync(tempAudioPath)) {
294
+ const { unlink } = await import("node:fs/promises");
295
+ await unlink(tempAudioPath).catch(() => {});
296
+ }
297
+
298
+ if (transcriptResult.status === "failed" || !transcriptResult.segments) {
299
+ transcribeSpinner.fail(chalk.red(`Transcription failed: ${transcriptResult.error}`));
300
+ process.exit(1);
301
+ }
302
+
303
+ transcribeSpinner.succeed(chalk.green(`Transcribed ${transcriptResult.segments.length} segments`));
304
+
305
+ // Step 2: Analyze viral potential with Claude
306
+ const analyzeSpinner = ora("📊 Analyzing viral potential...").start();
307
+
308
+ const claude = new ClaudeProvider();
309
+ await claude.initialize({ apiKey: claudeApiKey });
310
+
311
+ const viralAnalysis = await claude.analyzeViralPotential(
312
+ transcriptResult.segments,
313
+ { duration: totalDuration, clipCount: clips.length },
314
+ targetPlatforms
315
+ );
316
+
317
+ analyzeSpinner.succeed(chalk.green("Analysis complete"));
318
+
319
+ // Display analysis summary
320
+ console.log();
321
+ console.log(chalk.bold.cyan("Viral Potential Summary"));
322
+ console.log(chalk.dim("─".repeat(60)));
323
+ console.log(` Overall Score: ${chalk.bold(viralAnalysis.overallScore + "%")}`);
324
+ console.log(` Hook Strength: ${chalk.bold(viralAnalysis.hookStrength + "%")}`);
325
+ console.log(` Pacing: ${chalk.bold(viralAnalysis.pacing)}`);
326
+ console.log();
327
+
328
+ // Platform suitability bars
329
+ console.log(" Platform Suitability:");
330
+ for (const platform of targetPlatforms) {
331
+ const platformData = viralAnalysis.platforms[platform];
332
+ if (platformData) {
333
+ const score = Math.round(platformData.suitability * 100);
334
+ const filledBars = Math.round(score / 10);
335
+ const emptyBars = 10 - filledBars;
336
+ const bar = "█".repeat(filledBars) + "░".repeat(emptyBars);
337
+ const platformName = PLATFORM_SPECS[platform].name.padEnd(16);
338
+ console.log(` ${platformName} ${bar} ${score}%`);
339
+ }
340
+ }
341
+ console.log();
342
+
343
+ // Emotional peaks
344
+ if (viralAnalysis.emotionalPeaks.length > 0) {
345
+ console.log(" Emotional Peaks:");
346
+ for (const peak of viralAnalysis.emotionalPeaks.slice(0, 5)) {
347
+ console.log(` ${formatTime(peak.time)} - ${peak.emotion} (${(peak.intensity * 100).toFixed(0)}%)`);
348
+ }
349
+ console.log();
350
+ }
351
+
352
+ // Hook recommendation
353
+ if (viralAnalysis.hookRecommendation.suggestedStartTime > 0) {
354
+ console.log(` ${chalk.yellow("💡 Hook Tip:")} Consider starting at ${formatTime(viralAnalysis.hookRecommendation.suggestedStartTime)}`);
355
+ console.log(` ${chalk.dim(viralAnalysis.hookRecommendation.reason)}`);
356
+ console.log();
357
+ }
358
+
359
+ // If analyze-only, stop here
360
+ if (options.analyzeOnly) {
361
+ // Save analysis JSON
362
+ const outputDir = resolve(process.cwd(), options.outputDir);
363
+ if (!existsSync(outputDir)) {
364
+ await mkdir(outputDir, { recursive: true });
365
+ }
366
+ const analysisPath = resolve(outputDir, "analysis.json");
367
+ await writeFile(analysisPath, JSON.stringify(viralAnalysis, null, 2), "utf-8");
368
+
369
+ console.log(chalk.green(`💾 Analysis saved to: ${analysisPath}`));
370
+ console.log();
371
+ console.log(chalk.bold.green("✅ Analysis complete!"));
372
+ console.log();
373
+ return;
374
+ }
375
+
376
+ // Step 3: Generate platform variants
377
+ console.log(chalk.bold.cyan("🎬 Generating platform variants..."));
378
+
379
+ const outputDir = resolve(process.cwd(), options.outputDir);
380
+ if (!existsSync(outputDir)) {
381
+ await mkdir(outputDir, { recursive: true });
382
+ }
383
+
384
+ const generatedProjects: Array<{ platform: string; path: string; duration: number; aspectRatio: string }> = [];
385
+
386
+ for (const platformId of targetPlatforms) {
387
+ const platform = PLATFORM_SPECS[platformId];
388
+ const variantSpinner = ora(` Generating ${platform.name}...`).start();
389
+
390
+ try {
391
+ // Get platform-specific cuts from Claude
392
+ const clipsInfo = clips.map((c) => ({
393
+ id: c.id,
394
+ startTime: c.startTime,
395
+ duration: c.duration,
396
+ }));
397
+
398
+ const platformCut = await claude.suggestPlatformCuts(
399
+ transcriptResult.segments,
400
+ viralAnalysis,
401
+ platform,
402
+ clipsInfo
403
+ );
404
+
405
+ // Create platform-specific project
406
+ const platformProject = new Project(`${project.getMeta().name} - ${platform.name}`);
407
+ platformProject.setAspectRatio(platform.aspectRatio as "16:9" | "9:16" | "1:1");
408
+
409
+ // Copy sources
410
+ const sourceMap = new Map<string, string>();
411
+ for (const source of sources) {
412
+ const newSource = platformProject.addSource({
413
+ name: source.name,
414
+ url: source.url,
415
+ type: source.type,
416
+ duration: source.duration,
417
+ });
418
+ sourceMap.set(source.id, newSource.id);
419
+ }
420
+
421
+ // Get video track
422
+ const videoTrack = platformProject.getTracks().find((t) => t.type === "video");
423
+ if (!videoTrack) {
424
+ variantSpinner.fail(chalk.red(`Failed to create ${platform.name} variant`));
425
+ continue;
426
+ }
427
+
428
+ // Add clips based on platform cuts
429
+ let currentTime = 0;
430
+ let platformDuration = 0;
431
+ let audioStartOffset = 0; // Track where in original timeline the cut starts
432
+
433
+ if (platformCut.segments.length > 0) {
434
+ // Use AI-suggested segments
435
+ // Determine audio start offset from first segment's original timeline position
436
+ const firstSegment = platformCut.segments[0];
437
+ const firstOriginalClip = clips.find((c) => c.id === firstSegment.sourceClipId);
438
+ if (firstOriginalClip) {
439
+ // Calculate timeline position: clip start + offset within source
440
+ audioStartOffset = firstOriginalClip.startTime + (firstSegment.startTime - firstOriginalClip.sourceStartOffset);
441
+ }
442
+
443
+ for (const segment of platformCut.segments) {
444
+ // Find the original clip
445
+ const originalClip = clips.find((c) => c.id === segment.sourceClipId);
446
+ if (!originalClip) continue;
447
+
448
+ const sourceId = sourceMap.get(originalClip.sourceId);
449
+ if (!sourceId) continue;
450
+
451
+ const segmentDuration = segment.endTime - segment.startTime;
452
+ platformProject.addClip({
453
+ sourceId,
454
+ trackId: videoTrack.id,
455
+ startTime: currentTime,
456
+ duration: segmentDuration,
457
+ sourceStartOffset: segment.startTime,
458
+ sourceEndOffset: segment.endTime,
459
+ });
460
+ currentTime += segmentDuration;
461
+ platformDuration += segmentDuration;
462
+ }
463
+ } else {
464
+ // Fallback: use original clips, trimmed to fit duration
465
+ // Audio starts from first clip's timeline position
466
+ if (clips.length > 0) {
467
+ audioStartOffset = clips[0].startTime;
468
+ }
469
+
470
+ for (const clip of clips) {
471
+ const sourceId = sourceMap.get(clip.sourceId);
472
+ if (!sourceId) continue;
473
+
474
+ if (currentTime + clip.duration <= platform.maxDuration) {
475
+ platformProject.addClip({
476
+ sourceId,
477
+ trackId: videoTrack.id,
478
+ startTime: currentTime,
479
+ duration: clip.duration,
480
+ sourceStartOffset: clip.sourceStartOffset,
481
+ sourceEndOffset: clip.sourceEndOffset,
482
+ });
483
+ currentTime += clip.duration;
484
+ platformDuration += clip.duration;
485
+ } else {
486
+ // Trim the last clip to fit
487
+ const remainingDuration = platform.maxDuration - currentTime;
488
+ if (remainingDuration > 0) {
489
+ platformProject.addClip({
490
+ sourceId,
491
+ trackId: videoTrack.id,
492
+ startTime: currentTime,
493
+ duration: remainingDuration,
494
+ sourceStartOffset: clip.sourceStartOffset,
495
+ sourceEndOffset: clip.sourceStartOffset + remainingDuration,
496
+ });
497
+ platformDuration += remainingDuration;
498
+ }
499
+ break;
500
+ }
501
+ }
502
+ }
503
+
504
+ // Add audio clip if original project has audio
505
+ const originalAudioSource = sources.find((s) => s.type === "audio");
506
+ const audioTrack = platformProject.getTracks().find((t) => t.type === "audio");
507
+ if (originalAudioSource && audioTrack && platformDuration > 0) {
508
+ const audioSourceId = sourceMap.get(originalAudioSource.id);
509
+ if (audioSourceId) {
510
+ // Add audio clip synced with the video cut
511
+ platformProject.addClip({
512
+ sourceId: audioSourceId,
513
+ trackId: audioTrack.id,
514
+ startTime: 0,
515
+ duration: platformDuration,
516
+ sourceStartOffset: audioStartOffset,
517
+ sourceEndOffset: audioStartOffset + platformDuration,
518
+ });
519
+ }
520
+ }
521
+
522
+ // Generate captions if not skipped
523
+ if (!options.skipCaptions) {
524
+ const captionStyle = options.captionStyle as "minimal" | "bold" | "animated";
525
+ const captions = await claude.generateViralCaptions(
526
+ transcriptResult.segments.filter(
527
+ (s) => s.endTime <= platformDuration
528
+ ),
529
+ captionStyle
530
+ );
531
+
532
+ // Store captions as project metadata (for future caption track support)
533
+ // For now, save as separate file
534
+ if (captions.length > 0) {
535
+ const captionsPath = resolve(outputDir, `${platformId}-captions.json`);
536
+ await writeFile(captionsPath, JSON.stringify(captions, null, 2), "utf-8");
537
+ }
538
+ }
539
+
540
+ // Save platform project
541
+ const projectPath = resolve(outputDir, `${platformId}.vibe.json`);
542
+ await writeFile(projectPath, JSON.stringify(platformProject.toJSON(), null, 2), "utf-8");
543
+
544
+ generatedProjects.push({
545
+ platform: platform.name,
546
+ path: projectPath,
547
+ duration: platformDuration,
548
+ aspectRatio: platform.aspectRatio,
549
+ });
550
+
551
+ variantSpinner.succeed(chalk.green(` ✔ ${platformId}.vibe.json (${formatTime(platformDuration)}, ${platform.aspectRatio})`));
552
+ } catch (error) {
553
+ variantSpinner.fail(chalk.red(` ✘ Failed to generate ${platform.name}: ${error}`));
554
+ }
555
+ }
556
+
557
+ // Save analysis JSON
558
+ const analysisPath = resolve(outputDir, "analysis.json");
559
+ const result: ViralOptimizationResult = {
560
+ sourceProject: filePath,
561
+ analysis: viralAnalysis,
562
+ platformCuts: [],
563
+ platformProjects: generatedProjects.map((p) => ({
564
+ platform: p.platform,
565
+ projectPath: p.path,
566
+ duration: p.duration,
567
+ aspectRatio: p.aspectRatio,
568
+ })),
569
+ };
570
+ await writeFile(analysisPath, JSON.stringify(result, null, 2), "utf-8");
571
+
572
+ // Final summary
573
+ console.log();
574
+ console.log(chalk.dim("─".repeat(60)));
575
+ console.log(chalk.bold.green(`✅ Viral optimization complete!`));
576
+ console.log(` ${chalk.bold(generatedProjects.length)} platform variants generated`);
577
+ console.log();
578
+ console.log(`💾 Saved to: ${chalk.cyan(outputDir)}/`);
579
+ console.log();
580
+ console.log(chalk.dim("Next steps:"));
581
+ for (const proj of generatedProjects.slice(0, 3)) {
582
+ const filename = basename(proj.path);
583
+ console.log(chalk.dim(` vibe export ${options.outputDir}/${filename} -o ${proj.platform.toLowerCase().replace(/\s+/g, "-")}.mp4`));
584
+ }
585
+ if (generatedProjects.length > 3) {
586
+ console.log(chalk.dim(` ... and ${generatedProjects.length - 3} more`));
587
+ }
588
+ console.log();
589
+ } catch (error) {
590
+ console.error(chalk.red("Viral optimization failed"));
591
+ console.error(error);
592
+ process.exit(1);
593
+ }
594
+ });
595
+ }