@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,845 @@
1
+ /**
2
+ * @module ai-editing
3
+ * @description Agent tools for post-production editing (text overlay, review,
4
+ * silence cut, jump cut, captions, noise reduction, fade, thumbnail,
5
+ * SRT translation). FFmpeg-based and AI-assisted editing tools for agent use.
6
+ * Most tools work without API keys (FFmpeg-only), some use Gemini or OpenAI.
7
+ *
8
+ * ## Tools: edit_text_overlay, analyze_review, edit_silence_cut, edit_jump_cut, edit_caption,
9
+ * edit_noise_reduce, edit_fade, generate_thumbnail, edit_translate_srt
10
+ * ## Dependencies: FFmpeg, Gemini (optional), OpenAI/Whisper (optional)
11
+ * @see MODELS.md for the Single Source of Truth (SSOT) on supported providers/models
12
+ */
13
+
14
+ import { resolve } from "node:path";
15
+ import type { ToolRegistry, ToolHandler } from "./index.js";
16
+ import type { ToolDefinition, ToolResult } from "../types.js";
17
+ import {
18
+ executeTextOverlay,
19
+ executeSilenceCut,
20
+ executeJumpCut,
21
+ executeCaption,
22
+ executeNoiseReduce,
23
+ executeFade,
24
+ executeTranslateSrt,
25
+ type TextOverlayStyle,
26
+ type CaptionStyle,
27
+ } from "../../commands/ai-edit.js";
28
+ import { executeReview } from "../../commands/ai-review.js";
29
+ import { executeThumbnailBestFrame } from "../../commands/ai-image.js";
30
+ import { sanitizeAIResult } from "../../commands/sanitize.js";
31
+
32
+ // ============================================================================
33
+ // Tool Definitions
34
+ // ============================================================================
35
+
36
+ const textOverlayDef: ToolDefinition = {
37
+ name: "edit_text_overlay",
38
+ description: "Apply text overlays to a video using FFmpeg drawtext. Supports 4 style presets: lower-third, center-bold, subtitle, minimal. Auto-detects font and scales based on video resolution.",
39
+ parameters: {
40
+ type: "object",
41
+ properties: {
42
+ videoPath: {
43
+ type: "string",
44
+ description: "Path to input video file",
45
+ },
46
+ texts: {
47
+ type: "array",
48
+ items: { type: "string", description: "Text line to overlay" },
49
+ description: "Text lines to overlay (multiple lines stack vertically)",
50
+ },
51
+ outputPath: {
52
+ type: "string",
53
+ description: "Output video file path",
54
+ },
55
+ style: {
56
+ type: "string",
57
+ description: "Overlay style preset",
58
+ enum: ["lower-third", "center-bold", "subtitle", "minimal"],
59
+ },
60
+ fontSize: {
61
+ type: "number",
62
+ description: "Font size in pixels (auto-calculated if omitted)",
63
+ },
64
+ fontColor: {
65
+ type: "string",
66
+ description: "Font color (default: white)",
67
+ },
68
+ fadeDuration: {
69
+ type: "number",
70
+ description: "Fade in/out duration in seconds (default: 0.3)",
71
+ },
72
+ startTime: {
73
+ type: "number",
74
+ description: "Start time for overlay in seconds (default: 0)",
75
+ },
76
+ endTime: {
77
+ type: "number",
78
+ description: "End time for overlay in seconds (default: video duration)",
79
+ },
80
+ },
81
+ required: ["videoPath", "texts", "outputPath"],
82
+ },
83
+ };
84
+
85
+ const reviewDef: ToolDefinition = {
86
+ name: "analyze_review",
87
+ description: "Review video quality using Gemini AI. Analyzes pacing, color, text readability, audio-visual sync, and composition. Can auto-apply fixable corrections (color grading). Returns structured feedback with scores and recommendations.",
88
+ parameters: {
89
+ type: "object",
90
+ properties: {
91
+ videoPath: {
92
+ type: "string",
93
+ description: "Path to video file to review",
94
+ },
95
+ storyboardPath: {
96
+ type: "string",
97
+ description: "Optional path to storyboard JSON for context",
98
+ },
99
+ autoApply: {
100
+ type: "boolean",
101
+ description: "Automatically apply fixable corrections (default: false)",
102
+ },
103
+ verify: {
104
+ type: "boolean",
105
+ description: "Run verification pass after applying fixes (default: false)",
106
+ },
107
+ model: {
108
+ type: "string",
109
+ description: "Gemini model: flash (default), flash-2.5, pro",
110
+ enum: ["flash", "flash-2.5", "pro"],
111
+ },
112
+ outputPath: {
113
+ type: "string",
114
+ description: "Output path for corrected video (when autoApply is true)",
115
+ },
116
+ },
117
+ required: ["videoPath"],
118
+ },
119
+ };
120
+
121
+ const silenceCutDef: ToolDefinition = {
122
+ name: "edit_silence_cut",
123
+ description: "Remove silent segments from a video. Default uses FFmpeg silencedetect (free, no API key). Use useGemini=true for smart context-aware detection via Gemini Video Understanding — distinguishes dead air from intentional pauses using visual+audio analysis.",
124
+ parameters: {
125
+ type: "object",
126
+ properties: {
127
+ videoPath: {
128
+ type: "string",
129
+ description: "Path to input video file",
130
+ },
131
+ outputPath: {
132
+ type: "string",
133
+ description: "Output file path (default: <name>-cut.<ext>)",
134
+ },
135
+ noiseThreshold: {
136
+ type: "number",
137
+ description: "Silence threshold in dB (default: -30). Lower = more sensitive. FFmpeg mode only.",
138
+ },
139
+ minDuration: {
140
+ type: "number",
141
+ description: "Minimum silence duration in seconds to cut (default: 0.5)",
142
+ },
143
+ padding: {
144
+ type: "number",
145
+ description: "Padding around non-silent segments in seconds (default: 0.1)",
146
+ },
147
+ analyzeOnly: {
148
+ type: "boolean",
149
+ description: "Only detect silence without cutting (default: false)",
150
+ },
151
+ useGemini: {
152
+ type: "boolean",
153
+ description: "Use Gemini Video Understanding for context-aware silence detection (default: false). Requires GOOGLE_API_KEY.",
154
+ },
155
+ model: {
156
+ type: "string",
157
+ description: "Gemini model to use (default: flash). Options: flash, flash-2.5, pro",
158
+ },
159
+ lowRes: {
160
+ type: "boolean",
161
+ description: "Low resolution mode for longer videos (Gemini only)",
162
+ },
163
+ },
164
+ required: ["videoPath"],
165
+ },
166
+ };
167
+
168
+ const jumpCutDef: ToolDefinition = {
169
+ name: "edit_jump_cut",
170
+ description: "Remove filler words (um, uh, like, etc.) from video using Whisper word-level timestamps + FFmpeg concat. Requires OpenAI API key. Detects filler words, cuts them out, and stitches remaining segments with stream copy (fast, no re-encode).",
171
+ parameters: {
172
+ type: "object",
173
+ properties: {
174
+ videoPath: {
175
+ type: "string",
176
+ description: "Path to input video file",
177
+ },
178
+ outputPath: {
179
+ type: "string",
180
+ description: "Output file path (default: <name>-jumpcut.<ext>)",
181
+ },
182
+ fillers: {
183
+ type: "array",
184
+ items: { type: "string", description: "A filler word to detect" },
185
+ description: "Custom filler words to detect (default: um, uh, like, you know, etc.)",
186
+ },
187
+ padding: {
188
+ type: "number",
189
+ description: "Padding around cuts in seconds (default: 0.05)",
190
+ },
191
+ language: {
192
+ type: "string",
193
+ description: "Language code for transcription (e.g., en, ko)",
194
+ },
195
+ analyzeOnly: {
196
+ type: "boolean",
197
+ description: "Only detect fillers without cutting (default: false)",
198
+ },
199
+ },
200
+ required: ["videoPath"],
201
+ },
202
+ };
203
+
204
+ const captionDef: ToolDefinition = {
205
+ name: "edit_caption",
206
+ description: "Transcribe video with Whisper and burn styled captions using FFmpeg. Requires OpenAI API key. 4 style presets: minimal, bold (default), outline, karaoke. Auto-sizes font based on video resolution.",
207
+ parameters: {
208
+ type: "object",
209
+ properties: {
210
+ videoPath: {
211
+ type: "string",
212
+ description: "Path to input video file",
213
+ },
214
+ outputPath: {
215
+ type: "string",
216
+ description: "Output file path (default: <name>-captioned.<ext>)",
217
+ },
218
+ style: {
219
+ type: "string",
220
+ description: "Caption style preset",
221
+ enum: ["minimal", "bold", "outline", "karaoke"],
222
+ },
223
+ fontSize: {
224
+ type: "number",
225
+ description: "Font size in pixels (auto-calculated based on resolution if omitted)",
226
+ },
227
+ fontColor: {
228
+ type: "string",
229
+ description: "Font color (default: white)",
230
+ },
231
+ language: {
232
+ type: "string",
233
+ description: "Language code for transcription (e.g., en, ko)",
234
+ },
235
+ position: {
236
+ type: "string",
237
+ description: "Caption position",
238
+ enum: ["top", "center", "bottom"],
239
+ },
240
+ },
241
+ required: ["videoPath"],
242
+ },
243
+ };
244
+
245
+ const noiseReduceDef: ToolDefinition = {
246
+ name: "edit_noise_reduce",
247
+ description: "Remove background noise from audio/video using FFmpeg afftdn filter. No API key needed. Three strength presets: low, medium (default), high. High adds bandpass filtering.",
248
+ parameters: {
249
+ type: "object",
250
+ properties: {
251
+ inputPath: {
252
+ type: "string",
253
+ description: "Path to input audio or video file",
254
+ },
255
+ outputPath: {
256
+ type: "string",
257
+ description: "Output file path (default: <name>-denoised.<ext>)",
258
+ },
259
+ strength: {
260
+ type: "string",
261
+ description: "Noise reduction strength",
262
+ enum: ["low", "medium", "high"],
263
+ },
264
+ noiseFloor: {
265
+ type: "number",
266
+ description: "Custom noise floor in dB (overrides strength preset)",
267
+ },
268
+ },
269
+ required: ["inputPath"],
270
+ },
271
+ };
272
+
273
+ const fadeDef: ToolDefinition = {
274
+ name: "edit_fade",
275
+ description: "Apply fade in/out effects to video using FFmpeg. No API key needed. Supports video-only, audio-only, or both. Configurable fade durations.",
276
+ parameters: {
277
+ type: "object",
278
+ properties: {
279
+ videoPath: {
280
+ type: "string",
281
+ description: "Path to input video file",
282
+ },
283
+ outputPath: {
284
+ type: "string",
285
+ description: "Output file path (default: <name>-faded.<ext>)",
286
+ },
287
+ fadeIn: {
288
+ type: "number",
289
+ description: "Fade-in duration in seconds (default: 1)",
290
+ },
291
+ fadeOut: {
292
+ type: "number",
293
+ description: "Fade-out duration in seconds (default: 1)",
294
+ },
295
+ audioOnly: {
296
+ type: "boolean",
297
+ description: "Apply fade to audio only (default: false)",
298
+ },
299
+ videoOnly: {
300
+ type: "boolean",
301
+ description: "Apply fade to video only (default: false)",
302
+ },
303
+ },
304
+ required: ["videoPath"],
305
+ },
306
+ };
307
+
308
+ const thumbnailBestFrameDef: ToolDefinition = {
309
+ name: "generate_thumbnail",
310
+ description: "Extract the best thumbnail frame from a video using Gemini AI analysis + FFmpeg frame extraction. Requires GOOGLE_API_KEY. Finds visually striking, well-composed frames.",
311
+ parameters: {
312
+ type: "object",
313
+ properties: {
314
+ videoPath: {
315
+ type: "string",
316
+ description: "Path to input video file",
317
+ },
318
+ outputPath: {
319
+ type: "string",
320
+ description: "Output image path (default: <name>-thumbnail.png)",
321
+ },
322
+ prompt: {
323
+ type: "string",
324
+ description: "Custom prompt for frame selection analysis",
325
+ },
326
+ model: {
327
+ type: "string",
328
+ description: "Gemini model to use",
329
+ enum: ["flash", "flash-2.5", "pro"],
330
+ },
331
+ },
332
+ required: ["videoPath"],
333
+ },
334
+ };
335
+
336
+ const translateSrtDef: ToolDefinition = {
337
+ name: "edit_translate_srt",
338
+ description: "Translate SRT subtitle file to another language using Claude or OpenAI. Preserves timestamps. Batches segments for efficiency.",
339
+ parameters: {
340
+ type: "object",
341
+ properties: {
342
+ srtPath: {
343
+ type: "string",
344
+ description: "Path to input SRT file",
345
+ },
346
+ outputPath: {
347
+ type: "string",
348
+ description: "Output file path (default: <name>-<target>.srt)",
349
+ },
350
+ targetLanguage: {
351
+ type: "string",
352
+ description: "Target language (e.g., ko, es, fr, ja, zh)",
353
+ },
354
+ provider: {
355
+ type: "string",
356
+ description: "Translation provider",
357
+ enum: ["claude", "openai"],
358
+ },
359
+ sourceLanguage: {
360
+ type: "string",
361
+ description: "Source language (auto-detected if omitted)",
362
+ },
363
+ },
364
+ required: ["srtPath", "targetLanguage"],
365
+ },
366
+ };
367
+
368
+ // ============================================================================
369
+ // Tool Handlers
370
+ // ============================================================================
371
+
372
+ const textOverlayHandler: ToolHandler = async (args) => {
373
+ const { videoPath, texts, outputPath, style, fontSize, fontColor, fadeDuration, startTime, endTime } = args as {
374
+ videoPath: string;
375
+ texts: string[];
376
+ outputPath: string;
377
+ style?: TextOverlayStyle;
378
+ fontSize?: number;
379
+ fontColor?: string;
380
+ fadeDuration?: number;
381
+ startTime?: number;
382
+ endTime?: number;
383
+ };
384
+
385
+ if (!videoPath || !texts || texts.length === 0 || !outputPath) {
386
+ return {
387
+ toolCallId: "",
388
+ success: false,
389
+ output: "",
390
+ error: "videoPath, texts (non-empty array), and outputPath are required",
391
+ };
392
+ }
393
+
394
+ const result = await executeTextOverlay({
395
+ videoPath,
396
+ texts,
397
+ outputPath,
398
+ style,
399
+ fontSize,
400
+ fontColor,
401
+ fadeDuration,
402
+ startTime,
403
+ endTime,
404
+ });
405
+
406
+ if (!result.success) {
407
+ return {
408
+ toolCallId: "",
409
+ success: false,
410
+ output: "",
411
+ error: result.error || "Text overlay failed",
412
+ };
413
+ }
414
+
415
+ return {
416
+ toolCallId: "",
417
+ success: true,
418
+ output: `Text overlay applied: ${result.outputPath}`,
419
+ };
420
+ };
421
+
422
+ const reviewHandler: ToolHandler = async (args) => {
423
+ const { videoPath, storyboardPath, autoApply, verify, model, outputPath } = args as {
424
+ videoPath: string;
425
+ storyboardPath?: string;
426
+ autoApply?: boolean;
427
+ verify?: boolean;
428
+ model?: "flash" | "flash-2.5" | "pro";
429
+ outputPath?: string;
430
+ };
431
+
432
+ if (!videoPath) {
433
+ return {
434
+ toolCallId: "",
435
+ success: false,
436
+ output: "",
437
+ error: "videoPath is required",
438
+ };
439
+ }
440
+
441
+ const result = await executeReview({
442
+ videoPath,
443
+ storyboardPath,
444
+ autoApply,
445
+ verify,
446
+ model,
447
+ outputPath,
448
+ });
449
+
450
+ if (!result.success) {
451
+ return {
452
+ toolCallId: "",
453
+ success: false,
454
+ output: "",
455
+ error: result.error || "Video review failed",
456
+ };
457
+ }
458
+
459
+ const fb = sanitizeAIResult(result.feedback!);
460
+ let output = `Video Review: ${fb.overallScore}/10\n`;
461
+ output += `Pacing: ${fb.categories.pacing.score}/10, Color: ${fb.categories.color.score}/10, `;
462
+ output += `Text: ${fb.categories.textReadability.score}/10, AV Sync: ${fb.categories.audioVisualSync.score}/10, `;
463
+ output += `Composition: ${fb.categories.composition.score}/10\n`;
464
+
465
+ if (result.appliedFixes && result.appliedFixes.length > 0) {
466
+ output += `Applied fixes: ${sanitizeAIResult(result.appliedFixes).join("; ")}\n`;
467
+ }
468
+ if (result.verificationScore !== undefined) {
469
+ output += `Verification score: ${result.verificationScore}/10\n`;
470
+ }
471
+ if (fb.recommendations.length > 0) {
472
+ output += `Recommendations: ${fb.recommendations.join("; ")}`;
473
+ }
474
+
475
+ return {
476
+ toolCallId: "",
477
+ success: true,
478
+ output,
479
+ };
480
+ };
481
+
482
+ const silenceCutHandler: ToolHandler = async (args, context): Promise<ToolResult> => {
483
+ const videoPath = resolve(context.workingDirectory, args.videoPath as string);
484
+ const ext = videoPath.split(".").pop() || "mp4";
485
+ const name = videoPath.replace(/\.[^.]+$/, "");
486
+ const outputPath = args.outputPath
487
+ ? resolve(context.workingDirectory, args.outputPath as string)
488
+ : `${name}-cut.${ext}`;
489
+
490
+ try {
491
+ const result = await executeSilenceCut({
492
+ videoPath,
493
+ outputPath,
494
+ noiseThreshold: args.noiseThreshold as number | undefined,
495
+ minDuration: args.minDuration as number | undefined,
496
+ padding: args.padding as number | undefined,
497
+ analyzeOnly: args.analyzeOnly as boolean | undefined,
498
+ useGemini: args.useGemini as boolean | undefined,
499
+ model: args.model as string | undefined,
500
+ lowRes: args.lowRes as boolean | undefined,
501
+ });
502
+
503
+ if (!result.success) {
504
+ return {
505
+ toolCallId: "",
506
+ success: false,
507
+ output: "",
508
+ error: result.error || "Silence cut failed",
509
+ };
510
+ }
511
+
512
+ const lines: string[] = [];
513
+ lines.push(`Detection method: ${result.method === "gemini" ? "Gemini Video Understanding" : "FFmpeg silencedetect"}`);
514
+ lines.push(`Total duration: ${result.totalDuration!.toFixed(1)}s`);
515
+ lines.push(`Silent periods: ${result.silentPeriods!.length}`);
516
+ lines.push(`Silent duration: ${result.silentDuration!.toFixed(1)}s`);
517
+ lines.push(`Non-silent duration: ${(result.totalDuration! - result.silentDuration!).toFixed(1)}s`);
518
+
519
+ if (result.outputPath) {
520
+ lines.push(`Output: ${result.outputPath}`);
521
+ }
522
+
523
+ return {
524
+ toolCallId: "",
525
+ success: true,
526
+ output: lines.join("\n"),
527
+ };
528
+ } catch (error) {
529
+ return {
530
+ toolCallId: "",
531
+ success: false,
532
+ output: "",
533
+ error: `Silence cut failed: ${error instanceof Error ? error.message : String(error)}`,
534
+ };
535
+ }
536
+ };
537
+
538
+ const jumpCutHandler: ToolHandler = async (args, context): Promise<ToolResult> => {
539
+ const videoPath = resolve(context.workingDirectory, args.videoPath as string);
540
+ const ext = videoPath.split(".").pop() || "mp4";
541
+ const name = videoPath.replace(/\.[^.]+$/, "");
542
+ const outputPath = args.outputPath
543
+ ? resolve(context.workingDirectory, args.outputPath as string)
544
+ : `${name}-jumpcut.${ext}`;
545
+
546
+ try {
547
+ const result = await executeJumpCut({
548
+ videoPath,
549
+ outputPath,
550
+ fillers: args.fillers as string[] | undefined,
551
+ padding: args.padding as number | undefined,
552
+ language: args.language as string | undefined,
553
+ analyzeOnly: args.analyzeOnly as boolean | undefined,
554
+ });
555
+
556
+ if (!result.success) {
557
+ return {
558
+ toolCallId: "",
559
+ success: false,
560
+ output: "",
561
+ error: result.error || "Jump cut failed",
562
+ };
563
+ }
564
+
565
+ const lines: string[] = [];
566
+ lines.push(`Total duration: ${result.totalDuration!.toFixed(1)}s`);
567
+ lines.push(`Filler words found: ${result.fillerCount}`);
568
+ lines.push(`Filler duration: ${result.fillerDuration!.toFixed(1)}s`);
569
+ lines.push(`Clean duration: ${(result.totalDuration! - result.fillerDuration!).toFixed(1)}s`);
570
+
571
+ if (result.fillers && result.fillers.length > 0) {
572
+ lines.push("");
573
+ lines.push("Detected fillers:");
574
+ for (const filler of result.fillers) {
575
+ lines.push(` "${filler.word}" at ${filler.start.toFixed(2)}s - ${filler.end.toFixed(2)}s`);
576
+ }
577
+ }
578
+
579
+ if (result.outputPath) {
580
+ lines.push(`Output: ${result.outputPath}`);
581
+ }
582
+
583
+ return {
584
+ toolCallId: "",
585
+ success: true,
586
+ output: lines.join("\n"),
587
+ };
588
+ } catch (error) {
589
+ return {
590
+ toolCallId: "",
591
+ success: false,
592
+ output: "",
593
+ error: `Jump cut failed: ${error instanceof Error ? error.message : String(error)}`,
594
+ };
595
+ }
596
+ };
597
+
598
+ const captionHandler: ToolHandler = async (args, context): Promise<ToolResult> => {
599
+ const videoPath = resolve(context.workingDirectory, args.videoPath as string);
600
+ const ext = videoPath.split(".").pop() || "mp4";
601
+ const name = videoPath.replace(/\.[^.]+$/, "");
602
+ const outputPath = args.outputPath
603
+ ? resolve(context.workingDirectory, args.outputPath as string)
604
+ : `${name}-captioned.${ext}`;
605
+
606
+ try {
607
+ const result = await executeCaption({
608
+ videoPath,
609
+ outputPath,
610
+ style: args.style as CaptionStyle | undefined,
611
+ fontSize: args.fontSize as number | undefined,
612
+ fontColor: args.fontColor as string | undefined,
613
+ language: args.language as string | undefined,
614
+ position: args.position as "top" | "center" | "bottom" | undefined,
615
+ });
616
+
617
+ if (!result.success) {
618
+ return {
619
+ toolCallId: "",
620
+ success: false,
621
+ output: "",
622
+ error: result.error || "Caption failed",
623
+ };
624
+ }
625
+
626
+ const lines: string[] = [];
627
+ lines.push(`Captions applied: ${result.outputPath}`);
628
+ lines.push(`Segments transcribed: ${result.segmentCount}`);
629
+ if (result.srtPath) {
630
+ lines.push(`SRT file: ${result.srtPath}`);
631
+ }
632
+
633
+ return {
634
+ toolCallId: "",
635
+ success: true,
636
+ output: lines.join("\n"),
637
+ };
638
+ } catch (error) {
639
+ return {
640
+ toolCallId: "",
641
+ success: false,
642
+ output: "",
643
+ error: `Caption failed: ${error instanceof Error ? error.message : String(error)}`,
644
+ };
645
+ }
646
+ };
647
+
648
+ const noiseReduceHandler: ToolHandler = async (args, context): Promise<ToolResult> => {
649
+ const inputPath = resolve(context.workingDirectory, args.inputPath as string);
650
+ const ext = inputPath.split(".").pop() || "mp4";
651
+ const name = inputPath.replace(/\.[^.]+$/, "");
652
+ const outputPath = args.outputPath
653
+ ? resolve(context.workingDirectory, args.outputPath as string)
654
+ : `${name}-denoised.${ext}`;
655
+
656
+ try {
657
+ const result = await executeNoiseReduce({
658
+ inputPath,
659
+ outputPath,
660
+ strength: args.strength as "low" | "medium" | "high" | undefined,
661
+ noiseFloor: args.noiseFloor as number | undefined,
662
+ });
663
+
664
+ if (!result.success) {
665
+ return {
666
+ toolCallId: "",
667
+ success: false,
668
+ output: "",
669
+ error: result.error || "Noise reduction failed",
670
+ };
671
+ }
672
+
673
+ const lines: string[] = [];
674
+ lines.push(`Noise reduction applied: ${result.outputPath}`);
675
+ lines.push(`Input duration: ${result.inputDuration!.toFixed(1)}s`);
676
+
677
+ return {
678
+ toolCallId: "",
679
+ success: true,
680
+ output: lines.join("\n"),
681
+ };
682
+ } catch (error) {
683
+ return {
684
+ toolCallId: "",
685
+ success: false,
686
+ output: "",
687
+ error: `Noise reduction failed: ${error instanceof Error ? error.message : String(error)}`,
688
+ };
689
+ }
690
+ };
691
+
692
+ const fadeHandler: ToolHandler = async (args, context): Promise<ToolResult> => {
693
+ const videoPath = resolve(context.workingDirectory, args.videoPath as string);
694
+ const ext = videoPath.split(".").pop() || "mp4";
695
+ const name = videoPath.replace(/\.[^.]+$/, "");
696
+ const outputPath = args.outputPath
697
+ ? resolve(context.workingDirectory, args.outputPath as string)
698
+ : `${name}-faded.${ext}`;
699
+
700
+ try {
701
+ const result = await executeFade({
702
+ videoPath,
703
+ outputPath,
704
+ fadeIn: args.fadeIn as number | undefined,
705
+ fadeOut: args.fadeOut as number | undefined,
706
+ audioOnly: args.audioOnly as boolean | undefined,
707
+ videoOnly: args.videoOnly as boolean | undefined,
708
+ });
709
+
710
+ if (!result.success) {
711
+ return {
712
+ toolCallId: "",
713
+ success: false,
714
+ output: "",
715
+ error: result.error || "Fade failed",
716
+ };
717
+ }
718
+
719
+ const lines: string[] = [];
720
+ lines.push(`Fade effects applied: ${result.outputPath}`);
721
+ lines.push(`Total duration: ${result.totalDuration!.toFixed(1)}s`);
722
+ if (result.fadeInApplied) lines.push(`Fade-in applied`);
723
+ if (result.fadeOutApplied) lines.push(`Fade-out applied`);
724
+
725
+ return {
726
+ toolCallId: "",
727
+ success: true,
728
+ output: lines.join("\n"),
729
+ };
730
+ } catch (error) {
731
+ return {
732
+ toolCallId: "",
733
+ success: false,
734
+ output: "",
735
+ error: `Fade failed: ${error instanceof Error ? error.message : String(error)}`,
736
+ };
737
+ }
738
+ };
739
+
740
+ const thumbnailBestFrameHandler: ToolHandler = async (args, context): Promise<ToolResult> => {
741
+ const videoPath = resolve(context.workingDirectory, args.videoPath as string);
742
+ const name = videoPath.replace(/\.[^.]+$/, "");
743
+ const outputPath = args.outputPath
744
+ ? resolve(context.workingDirectory, args.outputPath as string)
745
+ : `${name}-thumbnail.png`;
746
+
747
+ try {
748
+ const result = await executeThumbnailBestFrame({
749
+ videoPath,
750
+ outputPath,
751
+ prompt: args.prompt as string | undefined,
752
+ model: args.model as string | undefined,
753
+ });
754
+
755
+ if (!result.success) {
756
+ return {
757
+ toolCallId: "",
758
+ success: false,
759
+ output: "",
760
+ error: result.error || "Best frame extraction failed",
761
+ };
762
+ }
763
+
764
+ const lines: string[] = [];
765
+ lines.push(`Best frame extracted: ${result.outputPath}`);
766
+ lines.push(`Timestamp: ${result.timestamp!.toFixed(2)}s`);
767
+ if (result.reason) lines.push(`Reason: ${sanitizeAIResult(result.reason)}`);
768
+
769
+ return {
770
+ toolCallId: "",
771
+ success: true,
772
+ output: lines.join("\n"),
773
+ };
774
+ } catch (error) {
775
+ return {
776
+ toolCallId: "",
777
+ success: false,
778
+ output: "",
779
+ error: `Best frame extraction failed: ${error instanceof Error ? error.message : String(error)}`,
780
+ };
781
+ }
782
+ };
783
+
784
+ const translateSrtHandler: ToolHandler = async (args, context): Promise<ToolResult> => {
785
+ const srtPath = resolve(context.workingDirectory, args.srtPath as string);
786
+ const target = args.targetLanguage as string;
787
+ const ext = srtPath.split(".").pop() || "srt";
788
+ const name = srtPath.replace(/\.[^.]+$/, "");
789
+ const outputPath = args.outputPath
790
+ ? resolve(context.workingDirectory, args.outputPath as string)
791
+ : `${name}-${target}.${ext}`;
792
+
793
+ try {
794
+ const result = await executeTranslateSrt({
795
+ srtPath,
796
+ outputPath,
797
+ targetLanguage: target,
798
+ provider: args.provider as "claude" | "openai" | undefined,
799
+ sourceLanguage: args.sourceLanguage as string | undefined,
800
+ });
801
+
802
+ if (!result.success) {
803
+ return {
804
+ toolCallId: "",
805
+ success: false,
806
+ output: "",
807
+ error: result.error || "Translation failed",
808
+ };
809
+ }
810
+
811
+ const lines: string[] = [];
812
+ lines.push(`Translation complete: ${result.outputPath}`);
813
+ lines.push(`Segments translated: ${result.segmentCount}`);
814
+ lines.push(`Target language: ${result.targetLanguage}`);
815
+
816
+ return {
817
+ toolCallId: "",
818
+ success: true,
819
+ output: lines.join("\n"),
820
+ };
821
+ } catch (error) {
822
+ return {
823
+ toolCallId: "",
824
+ success: false,
825
+ output: "",
826
+ error: `Translation failed: ${error instanceof Error ? error.message : String(error)}`,
827
+ };
828
+ }
829
+ };
830
+
831
+ // ============================================================================
832
+ // Registration
833
+ // ============================================================================
834
+
835
+ export function registerEditingTools(registry: ToolRegistry): void {
836
+ registry.register(textOverlayDef, textOverlayHandler);
837
+ registry.register(reviewDef, reviewHandler);
838
+ registry.register(silenceCutDef, silenceCutHandler);
839
+ registry.register(jumpCutDef, jumpCutHandler);
840
+ registry.register(captionDef, captionHandler);
841
+ registry.register(noiseReduceDef, noiseReduceHandler);
842
+ registry.register(fadeDef, fadeHandler);
843
+ registry.register(thumbnailBestFrameDef, thumbnailBestFrameHandler);
844
+ registry.register(translateSrtDef, translateSrtHandler);
845
+ }