@kortix/sandbox 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/config/customize.sh +143 -0
  2. package/config/kortix-env-setup.sh +25 -0
  3. package/kortix-master/package.json +22 -0
  4. package/kortix-master/src/config.ts +22 -0
  5. package/kortix-master/src/index.ts +44 -0
  6. package/kortix-master/src/routes/env.ts +65 -0
  7. package/kortix-master/src/routes/proxy.ts +108 -0
  8. package/kortix-master/src/routes/update.ts +185 -0
  9. package/kortix-master/src/services/proxy.ts +43 -0
  10. package/kortix-master/src/services/secret-store.ts +156 -0
  11. package/kortix-master/tsconfig.json +14 -0
  12. package/opencode/agents/kortix-browser.md +142 -0
  13. package/opencode/agents/kortix-build.md +62 -0
  14. package/opencode/agents/kortix-explore.md +66 -0
  15. package/opencode/agents/kortix-image-gen.md +33 -0
  16. package/opencode/agents/kortix-main.md +450 -0
  17. package/opencode/agents/kortix-plan.md +100 -0
  18. package/opencode/agents/kortix-research.md +84 -0
  19. package/opencode/agents/kortix-sheets.md +61 -0
  20. package/opencode/agents/kortix-slides.md +64 -0
  21. package/opencode/agents/kortix-web-dev.md +572 -0
  22. package/opencode/commands/email.md +36 -0
  23. package/opencode/commands/init.md +43 -0
  24. package/opencode/commands/journal.md +44 -0
  25. package/opencode/commands/memory-init.md +81 -0
  26. package/opencode/commands/memory-search.md +50 -0
  27. package/opencode/commands/memory-status.md +56 -0
  28. package/opencode/commands/research.md +36 -0
  29. package/opencode/commands/search.md +38 -0
  30. package/opencode/commands/slides.md +32 -0
  31. package/opencode/commands/spreadsheet.md +30 -0
  32. package/opencode/memory.json +37 -0
  33. package/opencode/ocx.jsonc +10 -0
  34. package/opencode/opencode.jsonc +103 -0
  35. package/opencode/package.json +25 -0
  36. package/opencode/patches/apply.sh +19 -0
  37. package/opencode/patches/opencode-pty-spawn.txt +49 -0
  38. package/opencode/plugin/background-agents.ts.disabled +483 -0
  39. package/opencode/plugin/kdco-primitives/get-project-id.ts +172 -0
  40. package/opencode/plugin/kdco-primitives/index.ts +26 -0
  41. package/opencode/plugin/kdco-primitives/log-warn.ts +51 -0
  42. package/opencode/plugin/kdco-primitives/mutex.ts +122 -0
  43. package/opencode/plugin/kdco-primitives/shell.ts +138 -0
  44. package/opencode/plugin/kdco-primitives/temp.ts +36 -0
  45. package/opencode/plugin/kdco-primitives/terminal-detect.ts +34 -0
  46. package/opencode/plugin/kdco-primitives/types.ts +13 -0
  47. package/opencode/plugin/kdco-primitives/with-timeout.ts +84 -0
  48. package/opencode/plugin/memory.ts +306 -0
  49. package/opencode/plugin/worktree/state.ts +412 -0
  50. package/opencode/plugin/worktree/terminal.ts +1002 -0
  51. package/opencode/plugin/worktree.ts +861 -0
  52. package/opencode/skills/KORTIX-browser/SKILL.md +478 -0
  53. package/opencode/skills/KORTIX-cron-triggers/SKILL.md +173 -0
  54. package/opencode/skills/KORTIX-deep-research/SKILL.md +278 -0
  55. package/opencode/skills/KORTIX-docx/SKILL.md +398 -0
  56. package/opencode/skills/KORTIX-docx/scripts/__init__.py +1 -0
  57. package/opencode/skills/KORTIX-docx/scripts/accept_changes.py +104 -0
  58. package/opencode/skills/KORTIX-docx/scripts/comment.py +244 -0
  59. package/opencode/skills/KORTIX-docx/scripts/office/helpers/__init__.py +0 -0
  60. package/opencode/skills/KORTIX-docx/scripts/office/helpers/merge_runs.py +199 -0
  61. package/opencode/skills/KORTIX-docx/scripts/office/helpers/simplify_redlines.py +197 -0
  62. package/opencode/skills/KORTIX-docx/scripts/office/pack.py +159 -0
  63. package/opencode/skills/KORTIX-docx/scripts/office/soffice.py +183 -0
  64. package/opencode/skills/KORTIX-docx/scripts/office/unpack.py +132 -0
  65. package/opencode/skills/KORTIX-docx/scripts/office/validate.py +111 -0
  66. package/opencode/skills/KORTIX-docx/scripts/office/validators/__init__.py +15 -0
  67. package/opencode/skills/KORTIX-docx/scripts/office/validators/base.py +847 -0
  68. package/opencode/skills/KORTIX-docx/scripts/office/validators/docx.py +446 -0
  69. package/opencode/skills/KORTIX-docx/scripts/office/validators/pptx.py +275 -0
  70. package/opencode/skills/KORTIX-docx/scripts/office/validators/redlining.py +247 -0
  71. package/opencode/skills/KORTIX-docx/scripts/render_docx.py +179 -0
  72. package/opencode/skills/KORTIX-docx/scripts/templates/comments.xml +3 -0
  73. package/opencode/skills/KORTIX-docx/scripts/templates/commentsExtended.xml +3 -0
  74. package/opencode/skills/KORTIX-docx/scripts/templates/commentsExtensible.xml +3 -0
  75. package/opencode/skills/KORTIX-docx/scripts/templates/commentsIds.xml +3 -0
  76. package/opencode/skills/KORTIX-docx/scripts/templates/people.xml +3 -0
  77. package/opencode/skills/KORTIX-domain-research/SKILL.md +96 -0
  78. package/opencode/skills/KORTIX-domain-research/scripts/domain-lookup.py +810 -0
  79. package/opencode/skills/KORTIX-elevenlabs/SKILL.md +230 -0
  80. package/opencode/skills/KORTIX-elevenlabs/scripts/tts.py +389 -0
  81. package/opencode/skills/KORTIX-email/SKILL.md +145 -0
  82. package/opencode/skills/KORTIX-legal-writer/SKILL.md +409 -0
  83. package/opencode/skills/KORTIX-legal-writer/references/bluebook.md +152 -0
  84. package/opencode/skills/KORTIX-legal-writer/references/document-types.md +416 -0
  85. package/opencode/skills/KORTIX-legal-writer/scripts/courtlistener.py +291 -0
  86. package/opencode/skills/KORTIX-legal-writer/scripts/ecfr_lookup.py +299 -0
  87. package/opencode/skills/KORTIX-legal-writer/scripts/verify-legal.py +507 -0
  88. package/opencode/skills/KORTIX-logo-creator/SKILL.md +293 -0
  89. package/opencode/skills/KORTIX-logo-creator/references/prompt-patterns.md +134 -0
  90. package/opencode/skills/KORTIX-logo-creator/scripts/compose_logo.py +406 -0
  91. package/opencode/skills/KORTIX-logo-creator/scripts/create_logo_sheet.py +258 -0
  92. package/opencode/skills/KORTIX-logo-creator/scripts/remove_bg.py +96 -0
  93. package/opencode/skills/KORTIX-memory/SKILL.md +261 -0
  94. package/opencode/skills/KORTIX-memory/scripts/export-sessions.py +409 -0
  95. package/opencode/skills/KORTIX-paper-creator/SKILL.md +549 -0
  96. package/opencode/skills/KORTIX-paper-creator/assets/template.tex +101 -0
  97. package/opencode/skills/KORTIX-paper-creator/scripts/compile.sh +177 -0
  98. package/opencode/skills/KORTIX-paper-creator/scripts/openalex_to_bibtex.py +220 -0
  99. package/opencode/skills/KORTIX-paper-creator/scripts/verify.sh +354 -0
  100. package/opencode/skills/KORTIX-paper-search/SKILL.md +418 -0
  101. package/opencode/skills/KORTIX-pdf/SKILL.md +232 -0
  102. package/opencode/skills/KORTIX-pdf/forms.md +36 -0
  103. package/opencode/skills/KORTIX-pdf/reference.md +105 -0
  104. package/opencode/skills/KORTIX-pdf/scripts/check_bounding_boxes.py +65 -0
  105. package/opencode/skills/KORTIX-pdf/scripts/check_fillable_fields.py +11 -0
  106. package/opencode/skills/KORTIX-pdf/scripts/convert_pdf_to_images.py +33 -0
  107. package/opencode/skills/KORTIX-pdf/scripts/create_validation_image.py +37 -0
  108. package/opencode/skills/KORTIX-pdf/scripts/extract_form_field_info.py +122 -0
  109. package/opencode/skills/KORTIX-pdf/scripts/extract_form_structure.py +115 -0
  110. package/opencode/skills/KORTIX-pdf/scripts/fill_fillable_fields.py +98 -0
  111. package/opencode/skills/KORTIX-pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
  112. package/opencode/skills/KORTIX-plan/SKILL.md +228 -0
  113. package/opencode/skills/KORTIX-presentation-viewer/SKILL.md +87 -0
  114. package/opencode/skills/KORTIX-presentation-viewer/serve.ts +136 -0
  115. package/opencode/skills/KORTIX-presentation-viewer/viewer.html +559 -0
  116. package/opencode/skills/KORTIX-presentations/SKILL.md +344 -0
  117. package/opencode/skills/KORTIX-remotion/SKILL.md +56 -0
  118. package/opencode/skills/KORTIX-remotion/rules/3d.md +86 -0
  119. package/opencode/skills/KORTIX-remotion/rules/animations.md +29 -0
  120. package/opencode/skills/KORTIX-remotion/rules/assets.md +78 -0
  121. package/opencode/skills/KORTIX-remotion/rules/audio-visualization.md +198 -0
  122. package/opencode/skills/KORTIX-remotion/rules/audio.md +169 -0
  123. package/opencode/skills/KORTIX-remotion/rules/calculate-metadata.md +104 -0
  124. package/opencode/skills/KORTIX-remotion/rules/can-decode.md +75 -0
  125. package/opencode/skills/KORTIX-remotion/rules/charts.md +120 -0
  126. package/opencode/skills/KORTIX-remotion/rules/compositions.md +141 -0
  127. package/opencode/skills/KORTIX-remotion/rules/display-captions.md +184 -0
  128. package/opencode/skills/KORTIX-remotion/rules/extract-frames.md +229 -0
  129. package/opencode/skills/KORTIX-remotion/rules/ffmpeg.md +38 -0
  130. package/opencode/skills/KORTIX-remotion/rules/fonts.md +152 -0
  131. package/opencode/skills/KORTIX-remotion/rules/get-audio-duration.md +58 -0
  132. package/opencode/skills/KORTIX-remotion/rules/get-video-dimensions.md +68 -0
  133. package/opencode/skills/KORTIX-remotion/rules/get-video-duration.md +58 -0
  134. package/opencode/skills/KORTIX-remotion/rules/gifs.md +141 -0
  135. package/opencode/skills/KORTIX-remotion/rules/images.md +130 -0
  136. package/opencode/skills/KORTIX-remotion/rules/import-srt-captions.md +69 -0
  137. package/opencode/skills/KORTIX-remotion/rules/light-leaks.md +73 -0
  138. package/opencode/skills/KORTIX-remotion/rules/lottie.md +68 -0
  139. package/opencode/skills/KORTIX-remotion/rules/maps.md +401 -0
  140. package/opencode/skills/KORTIX-remotion/rules/measuring-dom-nodes.md +35 -0
  141. package/opencode/skills/KORTIX-remotion/rules/measuring-text.md +143 -0
  142. package/opencode/skills/KORTIX-remotion/rules/parameters.md +98 -0
  143. package/opencode/skills/KORTIX-remotion/rules/sequencing.md +118 -0
  144. package/opencode/skills/KORTIX-remotion/rules/subtitles.md +36 -0
  145. package/opencode/skills/KORTIX-remotion/rules/tailwind.md +11 -0
  146. package/opencode/skills/KORTIX-remotion/rules/text-animations.md +20 -0
  147. package/opencode/skills/KORTIX-remotion/rules/timing.md +179 -0
  148. package/opencode/skills/KORTIX-remotion/rules/transcribe-captions.md +70 -0
  149. package/opencode/skills/KORTIX-remotion/rules/transitions.md +197 -0
  150. package/opencode/skills/KORTIX-remotion/rules/transparent-videos.md +106 -0
  151. package/opencode/skills/KORTIX-remotion/rules/trimming.md +53 -0
  152. package/opencode/skills/KORTIX-remotion/rules/videos.md +171 -0
  153. package/opencode/skills/KORTIX-secrets/SKILL.md +280 -0
  154. package/opencode/skills/KORTIX-semantic-search/SKILL.md +213 -0
  155. package/opencode/skills/KORTIX-session-search/SKILL.md +807 -0
  156. package/opencode/skills/KORTIX-session-search/Untitled +1 -0
  157. package/opencode/skills/KORTIX-skill-creator/SKILL.md +163 -0
  158. package/opencode/skills/KORTIX-web-research/SKILL.md +69 -0
  159. package/opencode/skills/KORTIX-xlsx/LICENSE.txt +30 -0
  160. package/opencode/skills/KORTIX-xlsx/SKILL.md +549 -0
  161. package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/__init__.py +0 -0
  162. package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/merge_runs.py +199 -0
  163. package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/simplify_redlines.py +197 -0
  164. package/opencode/skills/KORTIX-xlsx/scripts/office/pack.py +159 -0
  165. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  166. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  167. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  168. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  169. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  170. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  171. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  172. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  173. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  174. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  175. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  176. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  177. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  178. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  179. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  180. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  181. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  182. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  183. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  184. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  185. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  186. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  187. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  188. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  189. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  190. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  191. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  192. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  193. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  194. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  195. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  196. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/mce/mc.xsd +75 -0
  197. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  198. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  199. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  200. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  201. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  202. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  203. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  204. package/opencode/skills/KORTIX-xlsx/scripts/office/soffice.py +183 -0
  205. package/opencode/skills/KORTIX-xlsx/scripts/office/unpack.py +132 -0
  206. package/opencode/skills/KORTIX-xlsx/scripts/office/validate.py +111 -0
  207. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/__init__.py +15 -0
  208. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/base.py +847 -0
  209. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/docx.py +446 -0
  210. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/pptx.py +275 -0
  211. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/redlining.py +247 -0
  212. package/opencode/skills/KORTIX-xlsx/scripts/recalc.py +184 -0
  213. package/opencode/tools/image-gen.ts +342 -0
  214. package/opencode/tools/image-search.ts +190 -0
  215. package/opencode/tools/memory-get.ts +168 -0
  216. package/opencode/tools/memory-search.ts +247 -0
  217. package/opencode/tools/presentation-gen.ts +723 -0
  218. package/opencode/tools/scrape-webpage.ts +115 -0
  219. package/opencode/tools/scripts/.python-version +1 -0
  220. package/opencode/tools/scripts/convert_pdf.py +184 -0
  221. package/opencode/tools/scripts/convert_pptx.py +562 -0
  222. package/opencode/tools/scripts/pyproject.toml +11 -0
  223. package/opencode/tools/scripts/uv.lock +287 -0
  224. package/opencode/tools/scripts/validate_slide.py +74 -0
  225. package/opencode/tools/show-user.ts +217 -0
  226. package/opencode/tools/tests/e2e-presentation-fix.ts +277 -0
  227. package/opencode/tools/tests/image-gen.test.ts +215 -0
  228. package/opencode/tools/tests/image-search.test.ts +125 -0
  229. package/opencode/tools/tests/memory-system-benchmark.ts +1076 -0
  230. package/opencode/tools/tests/presentation-gen.test.ts +389 -0
  231. package/opencode/tools/tests/scrape-webpage.test.ts +74 -0
  232. package/opencode/tools/tests/show-user.test.ts +241 -0
  233. package/opencode/tools/tests/video-gen.test.ts +110 -0
  234. package/opencode/tools/tests/web-search.test.ts +106 -0
  235. package/opencode/tools/video-gen.ts +200 -0
  236. package/opencode/tools/web-search.ts +153 -0
  237. package/opencode/tsconfig.json +29 -0
  238. package/package.json +36 -0
  239. package/patch-agent-browser.js +100 -0
  240. package/postinstall.sh +88 -0
  241. package/services/KORTIX-presentation-viewer/run +37 -0
  242. package/services/agent-browser-viewer/run +48 -0
  243. package/services/kortix-master/run +16 -0
  244. package/services/lss-sync/run +22 -0
  245. package/services/opencode-serve/run +25 -0
  246. package/services/opencode-web/run +21 -0
@@ -0,0 +1,230 @@
1
+ ---
2
+ name: kortix-elevenlabs
3
+ description: "ElevenLabs audio generation — text-to-speech, voice cloning, and sound effects. Use this skill any time the agent needs to: convert text to spoken audio, narrate documents or content, generate voiceovers, clone voices from audio samples, create sound effects, or produce any audio output from text. Supports multiple voices, languages, models, voice cloning, batch processing, and sound effect generation. Requires ELEVENLABS_API_KEY."
4
+ ---
5
+
6
+ # ElevenLabs — Text-to-Speech, Voice Cloning & Sound Effects
7
+
8
+ ElevenLabs-powered audio generation. Convert any text to natural-sounding speech, clone voices, generate sound effects.
9
+
10
+ ---
11
+
12
+ ## Setup
13
+
14
+ **Required env var:** `ELEVENLABS_API_KEY`
15
+
16
+ The CLI script at `scripts/tts.py` uses only Python stdlib (`urllib`, `json`, `argparse`) — no pip dependencies needed.
17
+
18
+ ---
19
+
20
+ ## Quick Reference
21
+
22
+ All commands use the CLI script:
23
+
24
+ ```bash
25
+ python skills/KORTIX-tts/scripts/tts.py <command> [args]
26
+ ```
27
+
28
+ ### Speak — Convert text to speech
29
+
30
+ ```bash
31
+ # Basic — default voice (George), multilingual v2 model
32
+ python scripts/tts.py speak "Hello, this is Kortix speaking."
33
+
34
+ # Named voice
35
+ python scripts/tts.py speak "Welcome to the presentation." --voice Rachel
36
+
37
+ # Custom output file
38
+ python scripts/tts.py speak "Chapter one." --voice George -o chapter1.mp3
39
+
40
+ # From a file (prefix with @)
41
+ python scripts/tts.py speak @article.txt -o narration.mp3
42
+
43
+ # From stdin
44
+ echo "Dynamic text" | python scripts/tts.py speak -
45
+
46
+ # With voice tuning
47
+ python scripts/tts.py speak "Dramatic reading." --voice Rachel --stability 0.3 --similarity 0.9 --style 0.7
48
+
49
+ # High quality output
50
+ python scripts/tts.py speak "Studio quality." --format mp3_44100_192
51
+
52
+ # Different model (faster, English-only)
53
+ python scripts/tts.py speak "Quick response." --model eleven_turbo_v2_5
54
+
55
+ # Speed control
56
+ python scripts/tts.py speak "Slowly now." --speed 0.7
57
+ python scripts/tts.py speak "Fast paced!" --speed 1.5
58
+ ```
59
+
60
+ ### Voices — List and search
61
+
62
+ ```bash
63
+ # List all available voices
64
+ python scripts/tts.py voices
65
+
66
+ # Search by name, gender, accent, or use case
67
+ python scripts/tts.py voices --search "female"
68
+ python scripts/tts.py voices --search "british"
69
+ python scripts/tts.py voices --search "narration"
70
+ ```
71
+
72
+ ### Models — List available TTS models
73
+
74
+ ```bash
75
+ python scripts/tts.py models
76
+ ```
77
+
78
+ ### Clone — Create a custom voice from audio samples
79
+
80
+ ```bash
81
+ # Clone from audio files (1-25 samples, each 1-10 minutes)
82
+ python scripts/tts.py clone "ClientVoice" sample1.mp3 sample2.mp3
83
+
84
+ # With description
85
+ python scripts/tts.py clone "CEO" ceo_speech.mp3 --description "Confident male voice, American accent"
86
+
87
+ # Use the cloned voice
88
+ python scripts/tts.py speak "Hello from my cloned voice." --voice-id <returned_voice_id>
89
+ ```
90
+
91
+ ### Batch — Convert entire documents
92
+
93
+ ```bash
94
+ # Convert a text file to a single audio file
95
+ python scripts/tts.py batch article.txt -o article_audio/
96
+
97
+ # Split by paragraphs — one audio file per paragraph
98
+ python scripts/tts.py batch book_chapter.txt --split-paragraphs -o chapter_audio/
99
+
100
+ # With specific voice
101
+ python scripts/tts.py batch script.txt --voice Rachel --split-paragraphs
102
+ ```
103
+
104
+ ### Sound Effects — Generate from text prompts
105
+
106
+ ```bash
107
+ # Generate a sound effect
108
+ python scripts/tts.py sound "ocean waves crashing on a beach"
109
+
110
+ # With specific output and duration
111
+ python scripts/tts.py sound "thunderstorm with heavy rain" -o thunder.mp3 --duration 10.0
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Voice Settings Guide
117
+
118
+ Fine-tune voice output with these parameters:
119
+
120
+ | Parameter | Range | Default | Effect |
121
+ |---|---|---|---|
122
+ | `--stability` | 0.0 - 1.0 | 0.5 | Higher = more consistent, lower = more expressive/varied |
123
+ | `--similarity` | 0.0 - 1.0 | 0.75 | Higher = closer to original voice, lower = more creative |
124
+ | `--style` | 0.0 - 1.0 | 0.0 | Higher = more expressive style, can reduce stability |
125
+ | `--speed` | 0.5 - 2.0 | 1.0 | Playback speed multiplier |
126
+
127
+ **Recommended presets:**
128
+
129
+ - **Narration/Audiobook:** `--stability 0.5 --similarity 0.75` (balanced, natural)
130
+ - **News/Formal:** `--stability 0.8 --similarity 0.8` (consistent, clear)
131
+ - **Character/Dramatic:** `--stability 0.3 --similarity 0.8 --style 0.7` (expressive, varied)
132
+ - **Conversational:** `--stability 0.4 --similarity 0.6` (natural variation)
133
+
134
+ ---
135
+
136
+ ## Output Formats
137
+
138
+ | Format | Quality | Size | Use Case |
139
+ |---|---|---|---|
140
+ | `mp3_44100_128` | High (default) | Medium | General purpose, good quality |
141
+ | `mp3_44100_192` | Very high | Large | Studio quality, archival |
142
+ | `mp3_22050_32` | Low | Small | Voice messages, previews |
143
+ | `pcm_44100` | Lossless | Very large | Post-processing, editing |
144
+ | `pcm_16000` | Lossless low | Large | Speech recognition input |
145
+ | `opus_48000_128` | High | Small | Web streaming, efficient |
146
+
147
+ ---
148
+
149
+ ## Models
150
+
151
+ | Model | Speed | Quality | Languages | Best For |
152
+ |---|---|---|---|---|
153
+ | `eleven_multilingual_v2` | Normal | Highest | 29 languages | Default — best quality, multilingual |
154
+ | `eleven_turbo_v2_5` | Fast | High | 32 languages | Low-latency, near-instant generation |
155
+ | `eleven_monolingual_v1` | Normal | Good | English only | Legacy English-only workloads |
156
+
157
+ Always use `eleven_multilingual_v2` unless speed is critical (then use `eleven_turbo_v2_5`).
158
+
159
+ ---
160
+
161
+ ## Common Workflows
162
+
163
+ ### Narrate a document
164
+
165
+ ```bash
166
+ # Read the document, generate speech
167
+ python scripts/tts.py speak @workspace/report.md --voice Rachel -o report_narration.mp3
168
+ ```
169
+
170
+ ### Create a podcast intro
171
+
172
+ ```bash
173
+ python scripts/tts.py speak "Welcome to the Kortix Weekly. I'm your host, and today we're diving into autonomous AI agents." \
174
+ --voice George --stability 0.4 --similarity 0.8 --style 0.5 \
175
+ -o podcast_intro.mp3
176
+ ```
177
+
178
+ ### Narrate a presentation (per-slide)
179
+
180
+ For each slide, generate a separate audio file:
181
+ ```bash
182
+ python scripts/tts.py speak "Slide 1: Introduction to our company" --voice Rachel -o slides/01.mp3
183
+ python scripts/tts.py speak "Slide 2: Our key metrics this quarter" --voice Rachel -o slides/02.mp3
184
+ ```
185
+
186
+ Or write all narration to a text file (one paragraph per slide) and batch it:
187
+ ```bash
188
+ python scripts/tts.py batch slide_notes.txt --split-paragraphs --voice Rachel -o slide_audio/
189
+ ```
190
+
191
+ ### Voice clone for personalization
192
+
193
+ ```bash
194
+ # Clone the user's voice from samples they provide
195
+ python scripts/tts.py clone "UserVoice" sample1.mp3 sample2.mp3 sample3.mp3 \
196
+ --description "The user's natural speaking voice"
197
+
198
+ # Use it for all future TTS
199
+ python scripts/tts.py speak "Personalized message." --voice-id <voice_id> -o message.mp3
200
+ ```
201
+
202
+ ### Generate ambient audio
203
+
204
+ ```bash
205
+ python scripts/tts.py sound "coffee shop ambiance with gentle chatter" -o ambient.mp3 --duration 15
206
+ python scripts/tts.py sound "gentle rain on a window" -o rain.mp3 --duration 30
207
+ ```
208
+
209
+ ---
210
+
211
+ ## Integration Notes
212
+
213
+ - **No pip dependencies.** The script uses only Python stdlib (`urllib.request`, `json`, `argparse`). Works on any Python 3.10+ installation.
214
+ - **Output files** are saved relative to the current working directory. Use `-o` to specify exact paths.
215
+ - **Long text** is handled automatically by the API. For very long documents (>5000 chars), consider using `batch` with `--split-paragraphs` for better quality and to avoid timeouts.
216
+ - **Rate limits** apply per your ElevenLabs plan. The script will return API errors if limits are hit.
217
+ - **Character usage** counts against your ElevenLabs monthly quota. Check your plan's limits.
218
+
219
+ ---
220
+
221
+ ## Env Vars
222
+
223
+ | Variable | Required | Description |
224
+ |---|---|---|
225
+ | `ELEVENLABS_API_KEY` | Yes | Your ElevenLabs API key (also accepts `ELEVEN_API_KEY`) |
226
+
227
+ Add to `sandbox/.env` and `sandbox/opencode/.env`:
228
+ ```
229
+ ELEVENLABS_API_KEY=your_key_here
230
+ ```
@@ -0,0 +1,389 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Kortix TTS — ElevenLabs Text-to-Speech CLI
4
+
5
+ Usage:
6
+ python tts.py speak "Hello world" # Speak with default voice
7
+ python tts.py speak "Hello world" --voice Rachel # Speak with named voice
8
+ python tts.py speak "Hello world" --voice-id JBFqnC... # Speak with voice ID
9
+ python tts.py speak "Hello world" -o output.mp3 # Save to specific file
10
+ python tts.py speak "Hello world" --model eleven_turbo_v2_5 # Use specific model
11
+ python tts.py speak "Hello world" --format mp3_44100_192 # High quality output
12
+ python tts.py speak "Hello world" --stability 0.8 --similarity 0.9 # Custom settings
13
+
14
+ python tts.py voices # List all available voices
15
+ python tts.py voices --search "deep male" # Search voices
16
+ python tts.py models # List available models
17
+
18
+ python tts.py clone "MyVoice" sample1.mp3 sample2.mp3 # Clone a voice from samples
19
+ python tts.py clone "MyVoice" sample1.mp3 --description "A warm male voice"
20
+
21
+ python tts.py batch input.txt -o output_dir/ # Convert text file to speech
22
+ python tts.py batch input.txt --split-paragraphs # Split by paragraphs, one file each
23
+
24
+ python tts.py sound "ocean waves crashing" # Generate sound effect
25
+ python tts.py sound "thunder rumble" -o thunder.mp3 # Sound effect to file
26
+
27
+ Env: ELEVENLABS_API_KEY (required)
28
+ """
29
+
30
+ import argparse
31
+ import json
32
+ import os
33
+ import sys
34
+ import urllib.request
35
+ import urllib.error
36
+ from pathlib import Path
37
+
38
+ API_BASE = "https://api.elevenlabs.io/v1"
39
+
40
+
41
+ def get_api_key() -> str:
42
+ key = os.environ.get("ELEVENLABS_API_KEY") or os.environ.get("ELEVEN_API_KEY")
43
+ if not key:
44
+ print("ERROR: ELEVENLABS_API_KEY environment variable not set", file=sys.stderr)
45
+ sys.exit(1)
46
+ return key
47
+
48
+
49
+ def api_request(method: str, path: str, body: dict | None = None, stream: bool = False) -> bytes | dict:
50
+ """Make an API request to ElevenLabs."""
51
+ key = get_api_key()
52
+ url = f"{API_BASE}{path}"
53
+ headers = {
54
+ "xi-api-key": key,
55
+ "Content-Type": "application/json",
56
+ }
57
+
58
+ data = json.dumps(body).encode() if body else None
59
+ req = urllib.request.Request(url, data=data, headers=headers, method=method)
60
+
61
+ try:
62
+ with urllib.request.urlopen(req, timeout=120) as resp:
63
+ if stream:
64
+ return resp.read()
65
+ return json.loads(resp.read().decode())
66
+ except urllib.error.HTTPError as e:
67
+ error_body = e.read().decode()
68
+ try:
69
+ error_json = json.loads(error_body)
70
+ detail = error_json.get("detail", {})
71
+ if isinstance(detail, dict):
72
+ msg = detail.get("message", error_body)
73
+ else:
74
+ msg = str(detail)
75
+ except Exception:
76
+ msg = error_body
77
+ print(f"ERROR [{e.code}]: {msg}", file=sys.stderr)
78
+ sys.exit(1)
79
+
80
+
81
+ # ── Commands ─────────────────────────────────────────────────────────────────
82
+
83
+
84
+ def cmd_voices(args: argparse.Namespace) -> None:
85
+ """List available voices."""
86
+ resp = api_request("GET", "/voices")
87
+ voices = resp.get("voices", [])
88
+
89
+ if args.search:
90
+ query = args.search.lower()
91
+ voices = [v for v in voices if
92
+ query in v.get("name", "").lower() or
93
+ query in (v.get("description") or "").lower() or
94
+ query in (v.get("labels", {}).get("accent", "") or "").lower() or
95
+ query in (v.get("labels", {}).get("gender", "") or "").lower() or
96
+ query in (v.get("labels", {}).get("use_case", "") or "").lower()]
97
+
98
+ if not voices:
99
+ print("No voices found.")
100
+ return
101
+
102
+ print(f"{'Name':<25} {'ID':<25} {'Gender':<10} {'Accent':<15} {'Use Case':<15}")
103
+ print("-" * 90)
104
+ for v in voices:
105
+ labels = v.get("labels", {})
106
+ print(f"{v['name']:<25} {v['voice_id']:<25} {labels.get('gender', '-'):<10} "
107
+ f"{labels.get('accent', '-'):<15} {labels.get('use_case', '-'):<15}")
108
+
109
+ print(f"\nTotal: {len(voices)} voices")
110
+
111
+
112
+ def cmd_models(args: argparse.Namespace) -> None:
113
+ """List available models."""
114
+ resp = api_request("GET", "/models")
115
+ models = resp if isinstance(resp, list) else resp.get("models", resp)
116
+
117
+ print(f"{'Model ID':<35} {'Name':<35} {'Languages':<10}")
118
+ print("-" * 80)
119
+ for m in models:
120
+ if not m.get("can_do_text_to_speech", True):
121
+ continue
122
+ langs = len(m.get("languages", []))
123
+ print(f"{m['model_id']:<35} {m['name']:<35} {langs:<10}")
124
+
125
+
126
+ def resolve_voice_id(voice_name: str) -> str:
127
+ """Resolve a voice name to a voice ID. If already an ID, return as-is."""
128
+ # If it looks like a voice ID (long alphanumeric), return directly
129
+ if len(voice_name) > 15 and voice_name.isalnum():
130
+ return voice_name
131
+
132
+ resp = api_request("GET", "/voices")
133
+ voices = resp.get("voices", [])
134
+ for v in voices:
135
+ if v["name"].lower() == voice_name.lower():
136
+ return v["voice_id"]
137
+
138
+ # Fuzzy match
139
+ for v in voices:
140
+ if voice_name.lower() in v["name"].lower():
141
+ print(f"Matched voice: {v['name']} ({v['voice_id']})", file=sys.stderr)
142
+ return v["voice_id"]
143
+
144
+ print(f"ERROR: Voice '{voice_name}' not found. Run 'tts.py voices' to list available voices.", file=sys.stderr)
145
+ sys.exit(1)
146
+
147
+
148
+ def cmd_speak(args: argparse.Namespace) -> None:
149
+ """Convert text to speech."""
150
+ text = args.text
151
+
152
+ # Read from stdin if text is "-"
153
+ if text == "-":
154
+ text = sys.stdin.read().strip()
155
+ # Read from file if text starts with @
156
+ elif text.startswith("@"):
157
+ filepath = text[1:]
158
+ text = Path(filepath).read_text(encoding="utf-8").strip()
159
+
160
+ if not text:
161
+ print("ERROR: No text provided", file=sys.stderr)
162
+ sys.exit(1)
163
+
164
+ # Resolve voice
165
+ if args.voice_id:
166
+ voice_id = args.voice_id
167
+ elif args.voice:
168
+ voice_id = resolve_voice_id(args.voice)
169
+ else:
170
+ voice_id = "JBFqnCBsd6RMkjVDRZzb" # Default: George
171
+
172
+ # Build request body
173
+ body: dict = {
174
+ "text": text,
175
+ "model_id": args.model or "eleven_multilingual_v2",
176
+ }
177
+
178
+ voice_settings: dict = {}
179
+ if args.stability is not None:
180
+ voice_settings["stability"] = args.stability
181
+ if args.similarity is not None:
182
+ voice_settings["similarity_boost"] = args.similarity
183
+ if args.style is not None:
184
+ voice_settings["style"] = args.style
185
+ if args.speed is not None:
186
+ voice_settings["speed"] = args.speed
187
+ if voice_settings:
188
+ body["voice_settings"] = voice_settings
189
+
190
+ # Output format
191
+ fmt = args.format or "mp3_44100_128"
192
+
193
+ # Make the request
194
+ audio = api_request("POST", f"/text-to-speech/{voice_id}?output_format={fmt}", body=body, stream=True)
195
+
196
+ # Determine output path
197
+ if args.output:
198
+ out_path = Path(args.output)
199
+ else:
200
+ ext = fmt.split("_")[0] # mp3, pcm, opus, etc
201
+ out_path = Path(f"speech_output.{ext}")
202
+
203
+ out_path.parent.mkdir(parents=True, exist_ok=True)
204
+ out_path.write_bytes(audio)
205
+ print(f"Audio saved: {out_path} ({len(audio):,} bytes)")
206
+
207
+
208
+ def cmd_batch(args: argparse.Namespace) -> None:
209
+ """Convert a text file to speech, optionally splitting by paragraphs."""
210
+ input_path = Path(args.input_file)
211
+ if not input_path.exists():
212
+ print(f"ERROR: File not found: {input_path}", file=sys.stderr)
213
+ sys.exit(1)
214
+
215
+ text = input_path.read_text(encoding="utf-8").strip()
216
+
217
+ if args.split_paragraphs:
218
+ chunks = [p.strip() for p in text.split("\n\n") if p.strip()]
219
+ else:
220
+ chunks = [text]
221
+
222
+ # Output directory
223
+ if args.output:
224
+ out_dir = Path(args.output)
225
+ else:
226
+ out_dir = Path(f"{input_path.stem}_audio")
227
+ out_dir.mkdir(parents=True, exist_ok=True)
228
+
229
+ # Resolve voice
230
+ if args.voice_id:
231
+ voice_id = args.voice_id
232
+ elif args.voice:
233
+ voice_id = resolve_voice_id(args.voice)
234
+ else:
235
+ voice_id = "JBFqnCBsd6RMkjVDRZzb"
236
+
237
+ fmt = args.format or "mp3_44100_128"
238
+ ext = fmt.split("_")[0]
239
+ model = args.model or "eleven_multilingual_v2"
240
+
241
+ print(f"Converting {len(chunks)} chunk(s) to speech...")
242
+ for i, chunk in enumerate(chunks, 1):
243
+ body = {"text": chunk, "model_id": model}
244
+ audio = api_request("POST", f"/text-to-speech/{voice_id}?output_format={fmt}", body=body, stream=True)
245
+ out_path = out_dir / f"{i:03d}.{ext}"
246
+ out_path.write_bytes(audio)
247
+ print(f" [{i}/{len(chunks)}] {out_path} ({len(audio):,} bytes) — {chunk[:60]}...")
248
+
249
+ print(f"\nDone. {len(chunks)} audio files saved to: {out_dir}/")
250
+
251
+
252
+ def cmd_clone(args: argparse.Namespace) -> None:
253
+ """Clone a voice from audio samples."""
254
+ key = get_api_key()
255
+ url = f"{API_BASE}/voices/add"
256
+
257
+ # Build multipart form data manually
258
+ import mimetypes
259
+ boundary = "----KortixBoundary" + os.urandom(8).hex()
260
+
261
+ parts = []
262
+ parts.append(f'--{boundary}\r\nContent-Disposition: form-data; name="name"\r\n\r\n{args.name}')
263
+ if args.description:
264
+ parts.append(f'--{boundary}\r\nContent-Disposition: form-data; name="description"\r\n\r\n{args.description}')
265
+
266
+ file_parts = []
267
+ for filepath in args.files:
268
+ p = Path(filepath)
269
+ if not p.exists():
270
+ print(f"ERROR: Sample file not found: {p}", file=sys.stderr)
271
+ sys.exit(1)
272
+ mime = mimetypes.guess_type(str(p))[0] or "audio/mpeg"
273
+ file_data = p.read_bytes()
274
+ file_parts.append((p.name, mime, file_data))
275
+
276
+ # Assemble body
277
+ body_bytes = b""
278
+ for part in parts:
279
+ body_bytes += part.encode() + b"\r\n"
280
+ for fname, mime, fdata in file_parts:
281
+ body_bytes += f'--{boundary}\r\nContent-Disposition: form-data; name="files"; filename="{fname}"\r\nContent-Type: {mime}\r\n\r\n'.encode()
282
+ body_bytes += fdata + b"\r\n"
283
+ body_bytes += f"--{boundary}--\r\n".encode()
284
+
285
+ req = urllib.request.Request(url, data=body_bytes, method="POST")
286
+ req.add_header("xi-api-key", key)
287
+ req.add_header("Content-Type", f"multipart/form-data; boundary={boundary}")
288
+
289
+ try:
290
+ with urllib.request.urlopen(req, timeout=120) as resp:
291
+ result = json.loads(resp.read().decode())
292
+ print(f"Voice cloned successfully!")
293
+ print(f" Name: {args.name}")
294
+ print(f" Voice ID: {result.get('voice_id', 'unknown')}")
295
+ print(f" Use with: python tts.py speak \"text\" --voice-id {result.get('voice_id', '')}")
296
+ except urllib.error.HTTPError as e:
297
+ error_body = e.read().decode()
298
+ print(f"ERROR [{e.code}]: {error_body}", file=sys.stderr)
299
+ sys.exit(1)
300
+
301
+
302
+ def cmd_sound(args: argparse.Namespace) -> None:
303
+ """Generate a sound effect from a text prompt."""
304
+ body = {
305
+ "text": args.prompt,
306
+ }
307
+ if args.duration:
308
+ body["duration_seconds"] = args.duration
309
+
310
+ audio = api_request("POST", "/sound-generation", body=body, stream=True)
311
+
312
+ if args.output:
313
+ out_path = Path(args.output)
314
+ else:
315
+ slug = args.prompt[:40].replace(" ", "_").replace("/", "_")
316
+ out_path = Path(f"sfx_{slug}.mp3")
317
+
318
+ out_path.parent.mkdir(parents=True, exist_ok=True)
319
+ out_path.write_bytes(audio)
320
+ print(f"Sound effect saved: {out_path} ({len(audio):,} bytes)")
321
+
322
+
323
+ # ── CLI Parser ───────────────────────────────────────────────────────────────
324
+
325
+
326
+ def main() -> None:
327
+ parser = argparse.ArgumentParser(
328
+ prog="tts",
329
+ description="Kortix TTS — ElevenLabs Text-to-Speech CLI",
330
+ )
331
+ sub = parser.add_subparsers(dest="command", required=True)
332
+
333
+ # speak
334
+ p_speak = sub.add_parser("speak", help="Convert text to speech")
335
+ p_speak.add_argument("text", help="Text to speak (use '-' for stdin, '@file' to read from file)")
336
+ p_speak.add_argument("-o", "--output", help="Output file path")
337
+ p_speak.add_argument("--voice", help="Voice name (e.g. 'Rachel', 'George')")
338
+ p_speak.add_argument("--voice-id", help="Voice ID directly")
339
+ p_speak.add_argument("--model", help="Model ID (default: eleven_multilingual_v2)")
340
+ p_speak.add_argument("--format", help="Output format (default: mp3_44100_128)")
341
+ p_speak.add_argument("--stability", type=float, help="Voice stability 0.0-1.0")
342
+ p_speak.add_argument("--similarity", type=float, help="Similarity boost 0.0-1.0")
343
+ p_speak.add_argument("--style", type=float, help="Style exaggeration 0.0-1.0")
344
+ p_speak.add_argument("--speed", type=float, help="Speed 0.5-2.0")
345
+
346
+ # voices
347
+ p_voices = sub.add_parser("voices", help="List available voices")
348
+ p_voices.add_argument("--search", help="Search/filter voices")
349
+
350
+ # models
351
+ sub.add_parser("models", help="List available TTS models")
352
+
353
+ # clone
354
+ p_clone = sub.add_parser("clone", help="Clone a voice from audio samples")
355
+ p_clone.add_argument("name", help="Name for the cloned voice")
356
+ p_clone.add_argument("files", nargs="+", help="Audio sample files (mp3, wav, etc.)")
357
+ p_clone.add_argument("--description", help="Voice description")
358
+
359
+ # batch
360
+ p_batch = sub.add_parser("batch", help="Convert text file to speech")
361
+ p_batch.add_argument("input_file", help="Input text file")
362
+ p_batch.add_argument("-o", "--output", help="Output directory")
363
+ p_batch.add_argument("--voice", help="Voice name")
364
+ p_batch.add_argument("--voice-id", help="Voice ID directly")
365
+ p_batch.add_argument("--model", help="Model ID")
366
+ p_batch.add_argument("--format", help="Output format")
367
+ p_batch.add_argument("--split-paragraphs", action="store_true", help="Split by paragraphs into separate files")
368
+
369
+ # sound
370
+ p_sound = sub.add_parser("sound", help="Generate sound effect from text prompt")
371
+ p_sound.add_argument("prompt", help="Description of the sound effect")
372
+ p_sound.add_argument("-o", "--output", help="Output file path")
373
+ p_sound.add_argument("--duration", type=float, help="Duration in seconds")
374
+
375
+ args = parser.parse_args()
376
+
377
+ commands = {
378
+ "speak": cmd_speak,
379
+ "voices": cmd_voices,
380
+ "models": cmd_models,
381
+ "clone": cmd_clone,
382
+ "batch": cmd_batch,
383
+ "sound": cmd_sound,
384
+ }
385
+ commands[args.command](args)
386
+
387
+
388
+ if __name__ == "__main__":
389
+ main()