calvyn-code 0.14.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 (1718) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +217 -0
  3. package/README.zh-CN.md +180 -0
  4. package/acp_adapter/__init__.py +1 -0
  5. package/acp_adapter/__main__.py +5 -0
  6. package/acp_adapter/auth.py +68 -0
  7. package/acp_adapter/bootstrap/__init__.py +0 -0
  8. package/acp_adapter/bootstrap/bootstrap_browser_tools.ps1 +288 -0
  9. package/acp_adapter/bootstrap/bootstrap_browser_tools.sh +399 -0
  10. package/acp_adapter/entry.py +292 -0
  11. package/acp_adapter/events.py +265 -0
  12. package/acp_adapter/permissions.py +148 -0
  13. package/acp_adapter/server.py +1713 -0
  14. package/acp_adapter/session.py +629 -0
  15. package/acp_adapter/tools.py +1180 -0
  16. package/agent/__init__.py +6 -0
  17. package/agent/__pycache__/__init__.cpython-312.pyc +0 -0
  18. package/agent/__pycache__/account_usage.cpython-312.pyc +0 -0
  19. package/agent/__pycache__/anthropic_adapter.cpython-312.pyc +0 -0
  20. package/agent/__pycache__/async_utils.cpython-312.pyc +0 -0
  21. package/agent/__pycache__/auxiliary_client.cpython-312.pyc +0 -0
  22. package/agent/__pycache__/codex_responses_adapter.cpython-312.pyc +0 -0
  23. package/agent/__pycache__/context_compressor.cpython-312.pyc +0 -0
  24. package/agent/__pycache__/context_engine.cpython-312.pyc +0 -0
  25. package/agent/__pycache__/context_references.cpython-312.pyc +0 -0
  26. package/agent/__pycache__/credential_pool.cpython-312.pyc +0 -0
  27. package/agent/__pycache__/curator.cpython-312.pyc +0 -0
  28. package/agent/__pycache__/display.cpython-312.pyc +0 -0
  29. package/agent/__pycache__/error_classifier.cpython-312.pyc +0 -0
  30. package/agent/__pycache__/file_safety.cpython-312.pyc +0 -0
  31. package/agent/__pycache__/google_code_assist.cpython-312.pyc +0 -0
  32. package/agent/__pycache__/google_oauth.cpython-312.pyc +0 -0
  33. package/agent/__pycache__/i18n.cpython-312.pyc +0 -0
  34. package/agent/__pycache__/image_gen_provider.cpython-312.pyc +0 -0
  35. package/agent/__pycache__/image_gen_registry.cpython-312.pyc +0 -0
  36. package/agent/__pycache__/insights.cpython-312.pyc +0 -0
  37. package/agent/__pycache__/lmstudio_reasoning.cpython-312.pyc +0 -0
  38. package/agent/__pycache__/manual_compression_feedback.cpython-312.pyc +0 -0
  39. package/agent/__pycache__/markdown_tables.cpython-312.pyc +0 -0
  40. package/agent/__pycache__/memory_manager.cpython-312.pyc +0 -0
  41. package/agent/__pycache__/memory_provider.cpython-312.pyc +0 -0
  42. package/agent/__pycache__/model_metadata.cpython-312.pyc +0 -0
  43. package/agent/__pycache__/models_dev.cpython-312.pyc +0 -0
  44. package/agent/__pycache__/moonshot_schema.cpython-312.pyc +0 -0
  45. package/agent/__pycache__/onboarding.cpython-312.pyc +0 -0
  46. package/agent/__pycache__/portal_tags.cpython-312.pyc +0 -0
  47. package/agent/__pycache__/prompt_builder.cpython-312.pyc +0 -0
  48. package/agent/__pycache__/prompt_caching.cpython-312.pyc +0 -0
  49. package/agent/__pycache__/redact.cpython-312.pyc +0 -0
  50. package/agent/__pycache__/retry_utils.cpython-312.pyc +0 -0
  51. package/agent/__pycache__/shell_hooks.cpython-312.pyc +0 -0
  52. package/agent/__pycache__/skill_commands.cpython-312.pyc +0 -0
  53. package/agent/__pycache__/skill_preprocessing.cpython-312.pyc +0 -0
  54. package/agent/__pycache__/skill_utils.cpython-312.pyc +0 -0
  55. package/agent/__pycache__/subdirectory_hints.cpython-312.pyc +0 -0
  56. package/agent/__pycache__/think_scrubber.cpython-312.pyc +0 -0
  57. package/agent/__pycache__/title_generator.cpython-312.pyc +0 -0
  58. package/agent/__pycache__/tool_guardrails.cpython-312.pyc +0 -0
  59. package/agent/__pycache__/tool_result_classification.cpython-312.pyc +0 -0
  60. package/agent/__pycache__/trajectory.cpython-312.pyc +0 -0
  61. package/agent/__pycache__/usage_pricing.cpython-312.pyc +0 -0
  62. package/agent/__pycache__/video_gen_provider.cpython-312.pyc +0 -0
  63. package/agent/__pycache__/video_gen_registry.cpython-312.pyc +0 -0
  64. package/agent/__pycache__/web_search_provider.cpython-312.pyc +0 -0
  65. package/agent/__pycache__/web_search_registry.cpython-312.pyc +0 -0
  66. package/agent/account_usage.py +326 -0
  67. package/agent/anthropic_adapter.py +2087 -0
  68. package/agent/async_utils.py +68 -0
  69. package/agent/auxiliary_client.py +4893 -0
  70. package/agent/bedrock_adapter.py +1276 -0
  71. package/agent/codex_responses_adapter.py +1084 -0
  72. package/agent/context_compressor.py +1583 -0
  73. package/agent/context_engine.py +211 -0
  74. package/agent/context_references.py +519 -0
  75. package/agent/copilot_acp_client.py +684 -0
  76. package/agent/credential_pool.py +1780 -0
  77. package/agent/credential_sources.py +449 -0
  78. package/agent/curator.py +1782 -0
  79. package/agent/curator_backup.py +694 -0
  80. package/agent/display.py +987 -0
  81. package/agent/error_classifier.py +1058 -0
  82. package/agent/file_safety.py +112 -0
  83. package/agent/gemini_cloudcode_adapter.py +909 -0
  84. package/agent/gemini_native_adapter.py +971 -0
  85. package/agent/gemini_schema.py +99 -0
  86. package/agent/google_code_assist.py +452 -0
  87. package/agent/google_oauth.py +1062 -0
  88. package/agent/i18n.py +258 -0
  89. package/agent/image_gen_provider.py +243 -0
  90. package/agent/image_gen_registry.py +145 -0
  91. package/agent/image_routing.py +301 -0
  92. package/agent/insights.py +931 -0
  93. package/agent/lmstudio_reasoning.py +48 -0
  94. package/agent/lsp/__init__.py +106 -0
  95. package/agent/lsp/__pycache__/__init__.cpython-312.pyc +0 -0
  96. package/agent/lsp/__pycache__/cli.cpython-312.pyc +0 -0
  97. package/agent/lsp/__pycache__/client.cpython-312.pyc +0 -0
  98. package/agent/lsp/__pycache__/eventlog.cpython-312.pyc +0 -0
  99. package/agent/lsp/__pycache__/manager.cpython-312.pyc +0 -0
  100. package/agent/lsp/__pycache__/protocol.cpython-312.pyc +0 -0
  101. package/agent/lsp/__pycache__/servers.cpython-312.pyc +0 -0
  102. package/agent/lsp/__pycache__/workspace.cpython-312.pyc +0 -0
  103. package/agent/lsp/cli.py +308 -0
  104. package/agent/lsp/client.py +930 -0
  105. package/agent/lsp/eventlog.py +213 -0
  106. package/agent/lsp/install.py +376 -0
  107. package/agent/lsp/manager.py +644 -0
  108. package/agent/lsp/protocol.py +196 -0
  109. package/agent/lsp/range_shift.py +149 -0
  110. package/agent/lsp/reporter.py +78 -0
  111. package/agent/lsp/servers.py +1040 -0
  112. package/agent/lsp/workspace.py +223 -0
  113. package/agent/manual_compression_feedback.py +49 -0
  114. package/agent/markdown_tables.py +309 -0
  115. package/agent/memory_manager.py +556 -0
  116. package/agent/memory_provider.py +279 -0
  117. package/agent/model_metadata.py +1827 -0
  118. package/agent/models_dev.py +724 -0
  119. package/agent/moonshot_schema.py +231 -0
  120. package/agent/nous_rate_guard.py +326 -0
  121. package/agent/onboarding.py +193 -0
  122. package/agent/plugin_llm.py +1046 -0
  123. package/agent/portal_tags.py +64 -0
  124. package/agent/prompt_builder.py +1457 -0
  125. package/agent/prompt_caching.py +79 -0
  126. package/agent/rate_limit_tracker.py +246 -0
  127. package/agent/redact.py +403 -0
  128. package/agent/retry_utils.py +57 -0
  129. package/agent/shell_hooks.py +837 -0
  130. package/agent/skill_commands.py +502 -0
  131. package/agent/skill_preprocessing.py +131 -0
  132. package/agent/skill_utils.py +512 -0
  133. package/agent/subdirectory_hints.py +224 -0
  134. package/agent/think_scrubber.py +386 -0
  135. package/agent/title_generator.py +171 -0
  136. package/agent/tool_guardrails.py +458 -0
  137. package/agent/tool_result_classification.py +26 -0
  138. package/agent/trajectory.py +56 -0
  139. package/agent/transports/__init__.py +68 -0
  140. package/agent/transports/__pycache__/__init__.cpython-312.pyc +0 -0
  141. package/agent/transports/__pycache__/anthropic.cpython-312.pyc +0 -0
  142. package/agent/transports/__pycache__/base.cpython-312.pyc +0 -0
  143. package/agent/transports/__pycache__/bedrock.cpython-312.pyc +0 -0
  144. package/agent/transports/__pycache__/chat_completions.cpython-312.pyc +0 -0
  145. package/agent/transports/__pycache__/codex.cpython-312.pyc +0 -0
  146. package/agent/transports/__pycache__/types.cpython-312.pyc +0 -0
  147. package/agent/transports/anthropic.py +179 -0
  148. package/agent/transports/base.py +89 -0
  149. package/agent/transports/bedrock.py +154 -0
  150. package/agent/transports/chat_completions.py +614 -0
  151. package/agent/transports/codex.py +283 -0
  152. package/agent/transports/codex_app_server.py +368 -0
  153. package/agent/transports/codex_app_server_session.py +810 -0
  154. package/agent/transports/codex_event_projector.py +312 -0
  155. package/agent/transports/hermes_tools_mcp_server.py +233 -0
  156. package/agent/transports/types.py +162 -0
  157. package/agent/usage_pricing.py +877 -0
  158. package/agent/video_gen_provider.py +300 -0
  159. package/agent/video_gen_registry.py +117 -0
  160. package/agent/web_search_provider.py +221 -0
  161. package/agent/web_search_registry.py +262 -0
  162. package/assets/banner.png +0 -0
  163. package/batch_runner.py +1303 -0
  164. package/bin/calvyn.js +67 -0
  165. package/calvyn_bootstrap.py +130 -0
  166. package/calvyn_constants.py +346 -0
  167. package/calvyn_logging.py +390 -0
  168. package/calvyn_state.py +2967 -0
  169. package/calvyn_time.py +105 -0
  170. package/cli.py +14160 -0
  171. package/cron/__init__.py +42 -0
  172. package/cron/__pycache__/__init__.cpython-312.pyc +0 -0
  173. package/cron/__pycache__/jobs.cpython-312.pyc +0 -0
  174. package/cron/__pycache__/scheduler.cpython-312.pyc +0 -0
  175. package/cron/jobs.py +1160 -0
  176. package/cron/scheduler.py +1832 -0
  177. package/gateway/__init__.py +35 -0
  178. package/gateway/__pycache__/__init__.cpython-312.pyc +0 -0
  179. package/gateway/__pycache__/channel_directory.cpython-312.pyc +0 -0
  180. package/gateway/__pycache__/config.cpython-312.pyc +0 -0
  181. package/gateway/__pycache__/delivery.cpython-312.pyc +0 -0
  182. package/gateway/__pycache__/display_config.cpython-312.pyc +0 -0
  183. package/gateway/__pycache__/hooks.cpython-312.pyc +0 -0
  184. package/gateway/__pycache__/pairing.cpython-312.pyc +0 -0
  185. package/gateway/__pycache__/platform_registry.cpython-312.pyc +0 -0
  186. package/gateway/__pycache__/restart.cpython-312.pyc +0 -0
  187. package/gateway/__pycache__/run.cpython-312.pyc +0 -0
  188. package/gateway/__pycache__/runtime_footer.cpython-312.pyc +0 -0
  189. package/gateway/__pycache__/session.cpython-312.pyc +0 -0
  190. package/gateway/__pycache__/session_context.cpython-312.pyc +0 -0
  191. package/gateway/__pycache__/shutdown_forensics.cpython-312.pyc +0 -0
  192. package/gateway/__pycache__/slash_access.cpython-312.pyc +0 -0
  193. package/gateway/__pycache__/status.cpython-312.pyc +0 -0
  194. package/gateway/__pycache__/stream_consumer.cpython-312.pyc +0 -0
  195. package/gateway/__pycache__/whatsapp_identity.cpython-312.pyc +0 -0
  196. package/gateway/assets/telegram-botfather-threads-settings.jpg +0 -0
  197. package/gateway/builtin_hooks/__init__.py +1 -0
  198. package/gateway/channel_directory.py +357 -0
  199. package/gateway/config.py +1873 -0
  200. package/gateway/delivery.py +258 -0
  201. package/gateway/display_config.py +206 -0
  202. package/gateway/hooks.py +210 -0
  203. package/gateway/mirror.py +179 -0
  204. package/gateway/pairing.py +322 -0
  205. package/gateway/platform_registry.py +260 -0
  206. package/gateway/platforms/ADDING_A_PLATFORM.md +374 -0
  207. package/gateway/platforms/__init__.py +45 -0
  208. package/gateway/platforms/__pycache__/__init__.cpython-312.pyc +0 -0
  209. package/gateway/platforms/__pycache__/base.cpython-312.pyc +0 -0
  210. package/gateway/platforms/__pycache__/helpers.cpython-312.pyc +0 -0
  211. package/gateway/platforms/__pycache__/telegram.cpython-312.pyc +0 -0
  212. package/gateway/platforms/__pycache__/telegram_network.cpython-312.pyc +0 -0
  213. package/gateway/platforms/__pycache__/yuanbao.cpython-312.pyc +0 -0
  214. package/gateway/platforms/__pycache__/yuanbao_media.cpython-312.pyc +0 -0
  215. package/gateway/platforms/__pycache__/yuanbao_proto.cpython-312.pyc +0 -0
  216. package/gateway/platforms/_http_client_limits.py +84 -0
  217. package/gateway/platforms/api_server.py +3488 -0
  218. package/gateway/platforms/base.py +3747 -0
  219. package/gateway/platforms/bluebubbles.py +937 -0
  220. package/gateway/platforms/dingtalk.py +1473 -0
  221. package/gateway/platforms/discord.py +5584 -0
  222. package/gateway/platforms/email.py +773 -0
  223. package/gateway/platforms/feishu.py +5059 -0
  224. package/gateway/platforms/feishu_comment.py +1382 -0
  225. package/gateway/platforms/feishu_comment_rules.py +430 -0
  226. package/gateway/platforms/helpers.py +279 -0
  227. package/gateway/platforms/homeassistant.py +449 -0
  228. package/gateway/platforms/matrix.py +2777 -0
  229. package/gateway/platforms/mattermost.py +852 -0
  230. package/gateway/platforms/msgraph_webhook.py +397 -0
  231. package/gateway/platforms/qqbot/__init__.py +91 -0
  232. package/gateway/platforms/qqbot/adapter.py +3072 -0
  233. package/gateway/platforms/qqbot/chunked_upload.py +602 -0
  234. package/gateway/platforms/qqbot/constants.py +74 -0
  235. package/gateway/platforms/qqbot/crypto.py +45 -0
  236. package/gateway/platforms/qqbot/keyboards.py +473 -0
  237. package/gateway/platforms/qqbot/onboard.py +220 -0
  238. package/gateway/platforms/qqbot/utils.py +71 -0
  239. package/gateway/platforms/signal.py +1518 -0
  240. package/gateway/platforms/signal_rate_limit.py +369 -0
  241. package/gateway/platforms/slack.py +3028 -0
  242. package/gateway/platforms/sms.py +377 -0
  243. package/gateway/platforms/telegram.py +4836 -0
  244. package/gateway/platforms/telegram_network.py +249 -0
  245. package/gateway/platforms/webhook.py +806 -0
  246. package/gateway/platforms/wecom.py +1610 -0
  247. package/gateway/platforms/wecom_callback.py +403 -0
  248. package/gateway/platforms/wecom_crypto.py +142 -0
  249. package/gateway/platforms/weixin.py +2170 -0
  250. package/gateway/platforms/whatsapp.py +1283 -0
  251. package/gateway/platforms/yuanbao.py +4873 -0
  252. package/gateway/platforms/yuanbao_media.py +645 -0
  253. package/gateway/platforms/yuanbao_proto.py +1209 -0
  254. package/gateway/platforms/yuanbao_sticker.py +558 -0
  255. package/gateway/restart.py +20 -0
  256. package/gateway/run.py +17074 -0
  257. package/gateway/runtime_footer.py +150 -0
  258. package/gateway/session.py +1399 -0
  259. package/gateway/session_context.py +156 -0
  260. package/gateway/shutdown_forensics.py +462 -0
  261. package/gateway/slash_access.py +229 -0
  262. package/gateway/status.py +972 -0
  263. package/gateway/sticker_cache.py +111 -0
  264. package/gateway/stream_consumer.py +1286 -0
  265. package/gateway/whatsapp_identity.py +156 -0
  266. package/hermes_cli/__init__.py +47 -0
  267. package/hermes_cli/__pycache__/__init__.cpython-312.pyc +0 -0
  268. package/hermes_cli/__pycache__/_parser.cpython-312.pyc +0 -0
  269. package/hermes_cli/__pycache__/auth.cpython-312.pyc +0 -0
  270. package/hermes_cli/__pycache__/banner.cpython-312.pyc +0 -0
  271. package/hermes_cli/__pycache__/browser_connect.cpython-312.pyc +0 -0
  272. package/hermes_cli/__pycache__/callbacks.cpython-312.pyc +0 -0
  273. package/hermes_cli/__pycache__/checkpoints.cpython-312.pyc +0 -0
  274. package/hermes_cli/__pycache__/cli_output.cpython-312.pyc +0 -0
  275. package/hermes_cli/__pycache__/codex_models.cpython-312.pyc +0 -0
  276. package/hermes_cli/__pycache__/codex_runtime_switch.cpython-312.pyc +0 -0
  277. package/hermes_cli/__pycache__/colors.cpython-312.pyc +0 -0
  278. package/hermes_cli/__pycache__/commands.cpython-312.pyc +0 -0
  279. package/hermes_cli/__pycache__/config.cpython-312.pyc +0 -0
  280. package/hermes_cli/__pycache__/copilot_auth.cpython-312.pyc +0 -0
  281. package/hermes_cli/__pycache__/curator.cpython-312.pyc +0 -0
  282. package/hermes_cli/__pycache__/curses_ui.cpython-312.pyc +0 -0
  283. package/hermes_cli/__pycache__/debug.cpython-312.pyc +0 -0
  284. package/hermes_cli/__pycache__/default_soul.cpython-312.pyc +0 -0
  285. package/hermes_cli/__pycache__/env_loader.cpython-312.pyc +0 -0
  286. package/hermes_cli/__pycache__/fallback_cmd.cpython-312.pyc +0 -0
  287. package/hermes_cli/__pycache__/gateway.cpython-312.pyc +0 -0
  288. package/hermes_cli/__pycache__/gateway_windows.cpython-312.pyc +0 -0
  289. package/hermes_cli/__pycache__/goals.cpython-312.pyc +0 -0
  290. package/hermes_cli/__pycache__/inventory.cpython-312.pyc +0 -0
  291. package/hermes_cli/__pycache__/kanban.cpython-312.pyc +0 -0
  292. package/hermes_cli/__pycache__/kanban_db.cpython-312.pyc +0 -0
  293. package/hermes_cli/__pycache__/main.cpython-312.pyc +0 -0
  294. package/hermes_cli/__pycache__/model_catalog.cpython-312.pyc +0 -0
  295. package/hermes_cli/__pycache__/model_normalize.cpython-312.pyc +0 -0
  296. package/hermes_cli/__pycache__/model_switch.cpython-312.pyc +0 -0
  297. package/hermes_cli/__pycache__/models.cpython-312.pyc +0 -0
  298. package/hermes_cli/__pycache__/nous_subscription.cpython-312.pyc +0 -0
  299. package/hermes_cli/__pycache__/pairing.cpython-312.pyc +0 -0
  300. package/hermes_cli/__pycache__/platforms.cpython-312.pyc +0 -0
  301. package/hermes_cli/__pycache__/plugins.cpython-312.pyc +0 -0
  302. package/hermes_cli/__pycache__/profiles.cpython-312.pyc +0 -0
  303. package/hermes_cli/__pycache__/providers.cpython-312.pyc +0 -0
  304. package/hermes_cli/__pycache__/pt_input_extras.cpython-312.pyc +0 -0
  305. package/hermes_cli/__pycache__/runtime_provider.cpython-312.pyc +0 -0
  306. package/hermes_cli/__pycache__/security_advisories.cpython-312.pyc +0 -0
  307. package/hermes_cli/__pycache__/setup.cpython-312.pyc +0 -0
  308. package/hermes_cli/__pycache__/skills_hub.cpython-312.pyc +0 -0
  309. package/hermes_cli/__pycache__/skin_engine.cpython-312.pyc +0 -0
  310. package/hermes_cli/__pycache__/stdio.cpython-312.pyc +0 -0
  311. package/hermes_cli/__pycache__/timeouts.cpython-312.pyc +0 -0
  312. package/hermes_cli/__pycache__/tips.cpython-312.pyc +0 -0
  313. package/hermes_cli/__pycache__/tools_config.cpython-312.pyc +0 -0
  314. package/hermes_cli/__pycache__/voice.cpython-312.pyc +0 -0
  315. package/hermes_cli/_parser.py +365 -0
  316. package/hermes_cli/_subprocess_compat.py +175 -0
  317. package/hermes_cli/auth.py +6299 -0
  318. package/hermes_cli/auth_commands.py +749 -0
  319. package/hermes_cli/azure_detect.py +300 -0
  320. package/hermes_cli/backup.py +938 -0
  321. package/hermes_cli/banner.py +703 -0
  322. package/hermes_cli/browser_connect.py +139 -0
  323. package/hermes_cli/callbacks.py +243 -0
  324. package/hermes_cli/checkpoints.py +244 -0
  325. package/hermes_cli/claw.py +810 -0
  326. package/hermes_cli/cli_output.py +78 -0
  327. package/hermes_cli/clipboard.py +495 -0
  328. package/hermes_cli/codex_models.py +198 -0
  329. package/hermes_cli/codex_runtime_plugin_migration.py +757 -0
  330. package/hermes_cli/codex_runtime_switch.py +266 -0
  331. package/hermes_cli/colors.py +38 -0
  332. package/hermes_cli/commands.py +1728 -0
  333. package/hermes_cli/completion.py +315 -0
  334. package/hermes_cli/config.py +5382 -0
  335. package/hermes_cli/copilot_auth.py +392 -0
  336. package/hermes_cli/cron.py +313 -0
  337. package/hermes_cli/curator.py +598 -0
  338. package/hermes_cli/curses_ui.py +472 -0
  339. package/hermes_cli/debug.py +747 -0
  340. package/hermes_cli/default_soul.py +11 -0
  341. package/hermes_cli/dep_ensure.py +107 -0
  342. package/hermes_cli/dingtalk_auth.py +293 -0
  343. package/hermes_cli/doctor.py +1863 -0
  344. package/hermes_cli/dump.py +326 -0
  345. package/hermes_cli/env_loader.py +175 -0
  346. package/hermes_cli/fallback_cmd.py +361 -0
  347. package/hermes_cli/gateway.py +5422 -0
  348. package/hermes_cli/gateway_windows.py +692 -0
  349. package/hermes_cli/goals.py +757 -0
  350. package/hermes_cli/hooks.py +385 -0
  351. package/hermes_cli/inventory.py +240 -0
  352. package/hermes_cli/kanban.py +2252 -0
  353. package/hermes_cli/kanban_db.py +4840 -0
  354. package/hermes_cli/kanban_diagnostics.py +776 -0
  355. package/hermes_cli/kanban_specify.py +266 -0
  356. package/hermes_cli/logs.py +391 -0
  357. package/hermes_cli/main.py +12396 -0
  358. package/hermes_cli/mcp_config.py +781 -0
  359. package/hermes_cli/memory_setup.py +465 -0
  360. package/hermes_cli/model_catalog.py +330 -0
  361. package/hermes_cli/model_normalize.py +473 -0
  362. package/hermes_cli/model_switch.py +1777 -0
  363. package/hermes_cli/models.py +3789 -0
  364. package/hermes_cli/nous_subscription.py +799 -0
  365. package/hermes_cli/oneshot.py +351 -0
  366. package/hermes_cli/pairing.py +115 -0
  367. package/hermes_cli/platforms.py +83 -0
  368. package/hermes_cli/plugins.py +1562 -0
  369. package/hermes_cli/plugins_cmd.py +1587 -0
  370. package/hermes_cli/profile_distribution.py +703 -0
  371. package/hermes_cli/profiles.py +1319 -0
  372. package/hermes_cli/providers.py +720 -0
  373. package/hermes_cli/proxy/__init__.py +20 -0
  374. package/hermes_cli/proxy/adapters/__init__.py +35 -0
  375. package/hermes_cli/proxy/adapters/base.py +94 -0
  376. package/hermes_cli/proxy/adapters/nous_portal.py +137 -0
  377. package/hermes_cli/proxy/cli.py +141 -0
  378. package/hermes_cli/proxy/server.py +265 -0
  379. package/hermes_cli/pt_input_extras.py +83 -0
  380. package/hermes_cli/pty_bridge.py +237 -0
  381. package/hermes_cli/relaunch.py +205 -0
  382. package/hermes_cli/runtime_provider.py +1428 -0
  383. package/hermes_cli/security_advisories.py +452 -0
  384. package/hermes_cli/setup.py +3559 -0
  385. package/hermes_cli/skills_config.py +177 -0
  386. package/hermes_cli/skills_hub.py +1595 -0
  387. package/hermes_cli/skin_engine.py +929 -0
  388. package/hermes_cli/slack_cli.py +160 -0
  389. package/hermes_cli/status.py +550 -0
  390. package/hermes_cli/stdio.py +252 -0
  391. package/hermes_cli/timeouts.py +82 -0
  392. package/hermes_cli/tips.py +487 -0
  393. package/hermes_cli/tools_config.py +3151 -0
  394. package/hermes_cli/uninstall.py +681 -0
  395. package/hermes_cli/vercel_auth.py +70 -0
  396. package/hermes_cli/voice.py +846 -0
  397. package/hermes_cli/web_server.py +4438 -0
  398. package/hermes_cli/webhook.py +275 -0
  399. package/locales/af.yaml +350 -0
  400. package/locales/de.yaml +350 -0
  401. package/locales/en.yaml +365 -0
  402. package/locales/es.yaml +350 -0
  403. package/locales/fr.yaml +350 -0
  404. package/locales/ga.yaml +354 -0
  405. package/locales/hu.yaml +350 -0
  406. package/locales/it.yaml +350 -0
  407. package/locales/ja.yaml +350 -0
  408. package/locales/ko.yaml +350 -0
  409. package/locales/pt.yaml +350 -0
  410. package/locales/ru.yaml +350 -0
  411. package/locales/tr.yaml +350 -0
  412. package/locales/uk.yaml +350 -0
  413. package/locales/zh-hant.yaml +350 -0
  414. package/locales/zh.yaml +350 -0
  415. package/mcp_serve.py +898 -0
  416. package/model_tools.py +899 -0
  417. package/optional-skills/DESCRIPTION.md +24 -0
  418. package/optional-skills/autonomous-ai-agents/DESCRIPTION.md +2 -0
  419. package/optional-skills/autonomous-ai-agents/blackbox/SKILL.md +144 -0
  420. package/optional-skills/autonomous-ai-agents/honcho/SKILL.md +431 -0
  421. package/optional-skills/blockchain/evm/SKILL.md +211 -0
  422. package/optional-skills/blockchain/evm/scripts/evm_client.py +1508 -0
  423. package/optional-skills/blockchain/hyperliquid/SKILL.md +211 -0
  424. package/optional-skills/blockchain/hyperliquid/scripts/hyperliquid_client.py +1660 -0
  425. package/optional-skills/blockchain/solana/SKILL.md +208 -0
  426. package/optional-skills/blockchain/solana/scripts/solana_client.py +698 -0
  427. package/optional-skills/communication/DESCRIPTION.md +1 -0
  428. package/optional-skills/communication/one-three-one-rule/SKILL.md +104 -0
  429. package/optional-skills/creative/blender-mcp/SKILL.md +117 -0
  430. package/optional-skills/creative/concept-diagrams/SKILL.md +362 -0
  431. package/optional-skills/creative/concept-diagrams/examples/apartment-floor-plan-conversion.md +244 -0
  432. package/optional-skills/creative/concept-diagrams/examples/automated-password-reset-flow.md +276 -0
  433. package/optional-skills/creative/concept-diagrams/examples/autonomous-llm-research-agent-flow.md +240 -0
  434. package/optional-skills/creative/concept-diagrams/examples/banana-journey-tree-to-smoothie.md +161 -0
  435. package/optional-skills/creative/concept-diagrams/examples/commercial-aircraft-structure.md +209 -0
  436. package/optional-skills/creative/concept-diagrams/examples/cpu-ooo-microarchitecture.md +236 -0
  437. package/optional-skills/creative/concept-diagrams/examples/electricity-grid-flow.md +182 -0
  438. package/optional-skills/creative/concept-diagrams/examples/feature-film-production-pipeline.md +172 -0
  439. package/optional-skills/creative/concept-diagrams/examples/hospital-emergency-department-flow.md +165 -0
  440. package/optional-skills/creative/concept-diagrams/examples/ml-benchmark-grouped-bar-chart.md +114 -0
  441. package/optional-skills/creative/concept-diagrams/examples/place-order-uml-sequence.md +325 -0
  442. package/optional-skills/creative/concept-diagrams/examples/smart-city-infrastructure.md +173 -0
  443. package/optional-skills/creative/concept-diagrams/examples/smartphone-layer-anatomy.md +154 -0
  444. package/optional-skills/creative/concept-diagrams/examples/sn2-reaction-mechanism.md +247 -0
  445. package/optional-skills/creative/concept-diagrams/examples/wind-turbine-structure.md +338 -0
  446. package/optional-skills/creative/concept-diagrams/references/dashboard-patterns.md +43 -0
  447. package/optional-skills/creative/concept-diagrams/references/infrastructure-patterns.md +144 -0
  448. package/optional-skills/creative/concept-diagrams/references/physical-shape-cookbook.md +42 -0
  449. package/optional-skills/creative/concept-diagrams/templates/template.html +174 -0
  450. package/optional-skills/creative/hyperframes/SKILL.md +191 -0
  451. package/optional-skills/creative/hyperframes/references/cli.md +185 -0
  452. package/optional-skills/creative/hyperframes/references/composition.md +129 -0
  453. package/optional-skills/creative/hyperframes/references/features.md +289 -0
  454. package/optional-skills/creative/hyperframes/references/gsap.md +136 -0
  455. package/optional-skills/creative/hyperframes/references/troubleshooting.md +137 -0
  456. package/optional-skills/creative/hyperframes/references/website-to-video.md +145 -0
  457. package/optional-skills/creative/hyperframes/scripts/setup.sh +135 -0
  458. package/optional-skills/creative/kanban-video-orchestrator/SKILL.md +207 -0
  459. package/optional-skills/creative/kanban-video-orchestrator/assets/brief.md.tmpl +79 -0
  460. package/optional-skills/creative/kanban-video-orchestrator/assets/setup.sh.tmpl +185 -0
  461. package/optional-skills/creative/kanban-video-orchestrator/assets/soul.md.tmpl +38 -0
  462. package/optional-skills/creative/kanban-video-orchestrator/references/examples.md +227 -0
  463. package/optional-skills/creative/kanban-video-orchestrator/references/intake.md +166 -0
  464. package/optional-skills/creative/kanban-video-orchestrator/references/kanban-setup.md +276 -0
  465. package/optional-skills/creative/kanban-video-orchestrator/references/monitoring.md +180 -0
  466. package/optional-skills/creative/kanban-video-orchestrator/references/role-archetypes.md +298 -0
  467. package/optional-skills/creative/kanban-video-orchestrator/references/tool-matrix.md +317 -0
  468. package/optional-skills/creative/kanban-video-orchestrator/scripts/bootstrap_pipeline.py +501 -0
  469. package/optional-skills/creative/kanban-video-orchestrator/scripts/monitor.py +195 -0
  470. package/optional-skills/creative/meme-generation/EXAMPLES.md +46 -0
  471. package/optional-skills/creative/meme-generation/SKILL.md +130 -0
  472. package/optional-skills/creative/meme-generation/scripts/generate_meme.py +471 -0
  473. package/optional-skills/creative/meme-generation/scripts/templates.json +97 -0
  474. package/optional-skills/devops/cli/SKILL.md +156 -0
  475. package/optional-skills/devops/cli/references/app-discovery.md +112 -0
  476. package/optional-skills/devops/cli/references/authentication.md +59 -0
  477. package/optional-skills/devops/cli/references/cli-reference.md +104 -0
  478. package/optional-skills/devops/cli/references/running-apps.md +171 -0
  479. package/optional-skills/devops/docker-management/SKILL.md +281 -0
  480. package/optional-skills/devops/pinggy-tunnel/SKILL.md +309 -0
  481. package/optional-skills/devops/watchers/SKILL.md +112 -0
  482. package/optional-skills/devops/watchers/scripts/_watermark.py +148 -0
  483. package/optional-skills/devops/watchers/scripts/watch_github.py +168 -0
  484. package/optional-skills/devops/watchers/scripts/watch_http_json.py +131 -0
  485. package/optional-skills/devops/watchers/scripts/watch_rss.py +121 -0
  486. package/optional-skills/dogfood/DESCRIPTION.md +3 -0
  487. package/optional-skills/dogfood/adversarial-ux-test/SKILL.md +191 -0
  488. package/optional-skills/email/agentmail/SKILL.md +126 -0
  489. package/optional-skills/finance/3-statement-model/SKILL.md +433 -0
  490. package/optional-skills/finance/3-statement-model/references/formatting.md +118 -0
  491. package/optional-skills/finance/3-statement-model/references/formulas.md +292 -0
  492. package/optional-skills/finance/3-statement-model/references/sec-filings.md +125 -0
  493. package/optional-skills/finance/comps-analysis/SKILL.md +662 -0
  494. package/optional-skills/finance/dcf-model/SKILL.md +1270 -0
  495. package/optional-skills/finance/dcf-model/TROUBLESHOOTING.md +40 -0
  496. package/optional-skills/finance/dcf-model/requirements.txt +7 -0
  497. package/optional-skills/finance/dcf-model/scripts/validate_dcf.py +292 -0
  498. package/optional-skills/finance/excel-author/SKILL.md +244 -0
  499. package/optional-skills/finance/excel-author/scripts/recalc.py +88 -0
  500. package/optional-skills/finance/lbo-model/SKILL.md +291 -0
  501. package/optional-skills/finance/merger-model/SKILL.md +144 -0
  502. package/optional-skills/finance/pptx-author/SKILL.md +173 -0
  503. package/optional-skills/finance/stocks/SKILL.md +95 -0
  504. package/optional-skills/finance/stocks/scripts/stocks_client.py +755 -0
  505. package/optional-skills/health/DESCRIPTION.md +1 -0
  506. package/optional-skills/health/fitness-nutrition/SKILL.md +256 -0
  507. package/optional-skills/health/fitness-nutrition/references/FORMULAS.md +100 -0
  508. package/optional-skills/health/fitness-nutrition/scripts/body_calc.py +210 -0
  509. package/optional-skills/health/fitness-nutrition/scripts/nutrition_search.py +86 -0
  510. package/optional-skills/health/neuroskill-bci/SKILL.md +459 -0
  511. package/optional-skills/health/neuroskill-bci/references/api.md +286 -0
  512. package/optional-skills/health/neuroskill-bci/references/metrics.md +220 -0
  513. package/optional-skills/health/neuroskill-bci/references/protocols.md +452 -0
  514. package/optional-skills/mcp/DESCRIPTION.md +3 -0
  515. package/optional-skills/mcp/fastmcp/SKILL.md +300 -0
  516. package/optional-skills/mcp/fastmcp/references/fastmcp-cli.md +110 -0
  517. package/optional-skills/mcp/fastmcp/scripts/scaffold_fastmcp.py +56 -0
  518. package/optional-skills/mcp/fastmcp/templates/api_wrapper.py +54 -0
  519. package/optional-skills/mcp/fastmcp/templates/database_server.py +77 -0
  520. package/optional-skills/mcp/fastmcp/templates/file_processor.py +55 -0
  521. package/optional-skills/mcp/mcporter/SKILL.md +123 -0
  522. package/optional-skills/migration/DESCRIPTION.md +2 -0
  523. package/optional-skills/migration/openclaw-migration/SKILL.md +298 -0
  524. package/optional-skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py +3136 -0
  525. package/optional-skills/mlops/accelerate/SKILL.md +336 -0
  526. package/optional-skills/mlops/accelerate/references/custom-plugins.md +453 -0
  527. package/optional-skills/mlops/accelerate/references/megatron-integration.md +489 -0
  528. package/optional-skills/mlops/accelerate/references/performance.md +525 -0
  529. package/optional-skills/mlops/chroma/SKILL.md +410 -0
  530. package/optional-skills/mlops/chroma/references/integration.md +38 -0
  531. package/optional-skills/mlops/clip/SKILL.md +257 -0
  532. package/optional-skills/mlops/clip/references/applications.md +207 -0
  533. package/optional-skills/mlops/faiss/SKILL.md +225 -0
  534. package/optional-skills/mlops/faiss/references/index_types.md +280 -0
  535. package/optional-skills/mlops/flash-attention/SKILL.md +367 -0
  536. package/optional-skills/mlops/flash-attention/references/benchmarks.md +215 -0
  537. package/optional-skills/mlops/flash-attention/references/transformers-integration.md +293 -0
  538. package/optional-skills/mlops/guidance/SKILL.md +576 -0
  539. package/optional-skills/mlops/guidance/references/backends.md +554 -0
  540. package/optional-skills/mlops/guidance/references/constraints.md +674 -0
  541. package/optional-skills/mlops/guidance/references/examples.md +767 -0
  542. package/optional-skills/mlops/huggingface-tokenizers/SKILL.md +520 -0
  543. package/optional-skills/mlops/huggingface-tokenizers/references/algorithms.md +653 -0
  544. package/optional-skills/mlops/huggingface-tokenizers/references/integration.md +637 -0
  545. package/optional-skills/mlops/huggingface-tokenizers/references/pipeline.md +723 -0
  546. package/optional-skills/mlops/huggingface-tokenizers/references/training.md +565 -0
  547. package/optional-skills/mlops/inference/outlines/SKILL.md +656 -0
  548. package/optional-skills/mlops/inference/outlines/references/backends.md +615 -0
  549. package/optional-skills/mlops/inference/outlines/references/examples.md +773 -0
  550. package/optional-skills/mlops/inference/outlines/references/json_generation.md +652 -0
  551. package/optional-skills/mlops/instructor/SKILL.md +744 -0
  552. package/optional-skills/mlops/instructor/references/examples.md +107 -0
  553. package/optional-skills/mlops/instructor/references/providers.md +70 -0
  554. package/optional-skills/mlops/instructor/references/validation.md +606 -0
  555. package/optional-skills/mlops/lambda-labs/SKILL.md +549 -0
  556. package/optional-skills/mlops/lambda-labs/references/advanced-usage.md +611 -0
  557. package/optional-skills/mlops/lambda-labs/references/troubleshooting.md +530 -0
  558. package/optional-skills/mlops/llava/SKILL.md +308 -0
  559. package/optional-skills/mlops/llava/references/training.md +197 -0
  560. package/optional-skills/mlops/modal/SKILL.md +345 -0
  561. package/optional-skills/mlops/modal/references/advanced-usage.md +503 -0
  562. package/optional-skills/mlops/modal/references/troubleshooting.md +494 -0
  563. package/optional-skills/mlops/nemo-curator/SKILL.md +387 -0
  564. package/optional-skills/mlops/nemo-curator/references/deduplication.md +87 -0
  565. package/optional-skills/mlops/nemo-curator/references/filtering.md +102 -0
  566. package/optional-skills/mlops/peft/SKILL.md +435 -0
  567. package/optional-skills/mlops/peft/references/advanced-usage.md +514 -0
  568. package/optional-skills/mlops/peft/references/troubleshooting.md +480 -0
  569. package/optional-skills/mlops/pinecone/SKILL.md +362 -0
  570. package/optional-skills/mlops/pinecone/references/deployment.md +181 -0
  571. package/optional-skills/mlops/pytorch-fsdp/SKILL.md +130 -0
  572. package/optional-skills/mlops/pytorch-fsdp/references/index.md +7 -0
  573. package/optional-skills/mlops/pytorch-fsdp/references/other.md +4261 -0
  574. package/optional-skills/mlops/pytorch-lightning/SKILL.md +350 -0
  575. package/optional-skills/mlops/pytorch-lightning/references/callbacks.md +436 -0
  576. package/optional-skills/mlops/pytorch-lightning/references/distributed.md +490 -0
  577. package/optional-skills/mlops/pytorch-lightning/references/hyperparameter-tuning.md +556 -0
  578. package/optional-skills/mlops/qdrant/SKILL.md +497 -0
  579. package/optional-skills/mlops/qdrant/references/advanced-usage.md +648 -0
  580. package/optional-skills/mlops/qdrant/references/troubleshooting.md +631 -0
  581. package/optional-skills/mlops/saelens/SKILL.md +390 -0
  582. package/optional-skills/mlops/saelens/references/README.md +69 -0
  583. package/optional-skills/mlops/saelens/references/api.md +333 -0
  584. package/optional-skills/mlops/saelens/references/tutorials.md +318 -0
  585. package/optional-skills/mlops/simpo/SKILL.md +223 -0
  586. package/optional-skills/mlops/simpo/references/datasets.md +478 -0
  587. package/optional-skills/mlops/simpo/references/hyperparameters.md +452 -0
  588. package/optional-skills/mlops/simpo/references/loss-functions.md +350 -0
  589. package/optional-skills/mlops/slime/SKILL.md +468 -0
  590. package/optional-skills/mlops/slime/references/api-reference.md +392 -0
  591. package/optional-skills/mlops/slime/references/troubleshooting.md +386 -0
  592. package/optional-skills/mlops/stable-diffusion/SKILL.md +523 -0
  593. package/optional-skills/mlops/stable-diffusion/references/advanced-usage.md +716 -0
  594. package/optional-skills/mlops/stable-diffusion/references/troubleshooting.md +555 -0
  595. package/optional-skills/mlops/tensorrt-llm/SKILL.md +191 -0
  596. package/optional-skills/mlops/tensorrt-llm/references/multi-gpu.md +298 -0
  597. package/optional-skills/mlops/tensorrt-llm/references/optimization.md +242 -0
  598. package/optional-skills/mlops/tensorrt-llm/references/serving.md +470 -0
  599. package/optional-skills/mlops/torchtitan/SKILL.md +362 -0
  600. package/optional-skills/mlops/torchtitan/references/checkpoint.md +181 -0
  601. package/optional-skills/mlops/torchtitan/references/custom-models.md +258 -0
  602. package/optional-skills/mlops/torchtitan/references/float8.md +133 -0
  603. package/optional-skills/mlops/torchtitan/references/fsdp.md +126 -0
  604. package/optional-skills/mlops/training/axolotl/SKILL.md +166 -0
  605. package/optional-skills/mlops/training/axolotl/references/api.md +5548 -0
  606. package/optional-skills/mlops/training/axolotl/references/dataset-formats.md +1029 -0
  607. package/optional-skills/mlops/training/axolotl/references/index.md +15 -0
  608. package/optional-skills/mlops/training/axolotl/references/other.md +3563 -0
  609. package/optional-skills/mlops/training/trl-fine-tuning/SKILL.md +463 -0
  610. package/optional-skills/mlops/training/trl-fine-tuning/references/dpo-variants.md +227 -0
  611. package/optional-skills/mlops/training/trl-fine-tuning/references/grpo-training.md +504 -0
  612. package/optional-skills/mlops/training/trl-fine-tuning/references/online-rl.md +82 -0
  613. package/optional-skills/mlops/training/trl-fine-tuning/references/reward-modeling.md +122 -0
  614. package/optional-skills/mlops/training/trl-fine-tuning/references/sft-training.md +168 -0
  615. package/optional-skills/mlops/training/trl-fine-tuning/templates/basic_grpo_training.py +228 -0
  616. package/optional-skills/mlops/training/unsloth/SKILL.md +84 -0
  617. package/optional-skills/mlops/training/unsloth/references/index.md +7 -0
  618. package/optional-skills/mlops/training/unsloth/references/llms-full.md +16799 -0
  619. package/optional-skills/mlops/training/unsloth/references/llms-txt.md +12044 -0
  620. package/optional-skills/mlops/training/unsloth/references/llms.md +82 -0
  621. package/optional-skills/mlops/whisper/SKILL.md +321 -0
  622. package/optional-skills/mlops/whisper/references/languages.md +189 -0
  623. package/optional-skills/productivity/canvas/SKILL.md +98 -0
  624. package/optional-skills/productivity/canvas/scripts/canvas_api.py +157 -0
  625. package/optional-skills/productivity/here-now/SKILL.md +217 -0
  626. package/optional-skills/productivity/here-now/scripts/drive.sh +406 -0
  627. package/optional-skills/productivity/here-now/scripts/publish.sh +445 -0
  628. package/optional-skills/productivity/memento-flashcards/SKILL.md +324 -0
  629. package/optional-skills/productivity/memento-flashcards/scripts/memento_cards.py +353 -0
  630. package/optional-skills/productivity/memento-flashcards/scripts/youtube_quiz.py +88 -0
  631. package/optional-skills/productivity/shop-app/SKILL.md +340 -0
  632. package/optional-skills/productivity/shopify/SKILL.md +373 -0
  633. package/optional-skills/productivity/siyuan/SKILL.md +298 -0
  634. package/optional-skills/productivity/telephony/SKILL.md +418 -0
  635. package/optional-skills/productivity/telephony/scripts/telephony.py +1343 -0
  636. package/optional-skills/research/bioinformatics/SKILL.md +235 -0
  637. package/optional-skills/research/darwinian-evolver/SKILL.md +199 -0
  638. package/optional-skills/research/darwinian-evolver/scripts/parrot_openrouter.py +218 -0
  639. package/optional-skills/research/darwinian-evolver/scripts/show_snapshot.py +69 -0
  640. package/optional-skills/research/darwinian-evolver/templates/custom_problem_template.py +240 -0
  641. package/optional-skills/research/domain-intel/SKILL.md +97 -0
  642. package/optional-skills/research/domain-intel/scripts/domain_intel.py +397 -0
  643. package/optional-skills/research/drug-discovery/SKILL.md +227 -0
  644. package/optional-skills/research/drug-discovery/references/ADMET_REFERENCE.md +66 -0
  645. package/optional-skills/research/drug-discovery/scripts/chembl_target.py +53 -0
  646. package/optional-skills/research/drug-discovery/scripts/ro5_screen.py +44 -0
  647. package/optional-skills/research/duckduckgo-search/SKILL.md +238 -0
  648. package/optional-skills/research/duckduckgo-search/scripts/duckduckgo.sh +28 -0
  649. package/optional-skills/research/gitnexus-explorer/SKILL.md +214 -0
  650. package/optional-skills/research/gitnexus-explorer/scripts/proxy.mjs +92 -0
  651. package/optional-skills/research/osint-investigation/SKILL.md +277 -0
  652. package/optional-skills/research/osint-investigation/references/sources/courtlistener.md +98 -0
  653. package/optional-skills/research/osint-investigation/references/sources/gdelt.md +104 -0
  654. package/optional-skills/research/osint-investigation/references/sources/icij-offshore.md +104 -0
  655. package/optional-skills/research/osint-investigation/references/sources/nyc-acris.md +90 -0
  656. package/optional-skills/research/osint-investigation/references/sources/ofac-sdn.md +92 -0
  657. package/optional-skills/research/osint-investigation/references/sources/opencorporates.md +103 -0
  658. package/optional-skills/research/osint-investigation/references/sources/sec-edgar.md +83 -0
  659. package/optional-skills/research/osint-investigation/references/sources/senate-ld.md +89 -0
  660. package/optional-skills/research/osint-investigation/references/sources/usaspending.md +97 -0
  661. package/optional-skills/research/osint-investigation/references/sources/wayback.md +93 -0
  662. package/optional-skills/research/osint-investigation/references/sources/wikipedia.md +107 -0
  663. package/optional-skills/research/osint-investigation/scripts/_http.py +82 -0
  664. package/optional-skills/research/osint-investigation/scripts/_normalize.py +67 -0
  665. package/optional-skills/research/osint-investigation/scripts/build_findings.py +221 -0
  666. package/optional-skills/research/osint-investigation/scripts/entity_resolution.py +228 -0
  667. package/optional-skills/research/osint-investigation/scripts/fetch_courtlistener.py +149 -0
  668. package/optional-skills/research/osint-investigation/scripts/fetch_gdelt.py +162 -0
  669. package/optional-skills/research/osint-investigation/scripts/fetch_icij_offshore.py +234 -0
  670. package/optional-skills/research/osint-investigation/scripts/fetch_nyc_acris.py +203 -0
  671. package/optional-skills/research/osint-investigation/scripts/fetch_ofac_sdn.py +175 -0
  672. package/optional-skills/research/osint-investigation/scripts/fetch_opencorporates.py +192 -0
  673. package/optional-skills/research/osint-investigation/scripts/fetch_sec_edgar.py +184 -0
  674. package/optional-skills/research/osint-investigation/scripts/fetch_senate_ld.py +146 -0
  675. package/optional-skills/research/osint-investigation/scripts/fetch_usaspending.py +170 -0
  676. package/optional-skills/research/osint-investigation/scripts/fetch_wayback.py +142 -0
  677. package/optional-skills/research/osint-investigation/scripts/fetch_wikipedia.py +267 -0
  678. package/optional-skills/research/osint-investigation/scripts/timing_analysis.py +253 -0
  679. package/optional-skills/research/osint-investigation/templates/source-template.md +59 -0
  680. package/optional-skills/research/parallel-cli/SKILL.md +391 -0
  681. package/optional-skills/research/qmd/SKILL.md +441 -0
  682. package/optional-skills/research/scrapling/SKILL.md +336 -0
  683. package/optional-skills/research/searxng-search/SKILL.md +212 -0
  684. package/optional-skills/research/searxng-search/scripts/searxng.sh +22 -0
  685. package/optional-skills/security/1password/SKILL.md +163 -0
  686. package/optional-skills/security/1password/references/cli-examples.md +31 -0
  687. package/optional-skills/security/1password/references/get-started.md +21 -0
  688. package/optional-skills/security/DESCRIPTION.md +3 -0
  689. package/optional-skills/security/oss-forensics/SKILL.md +423 -0
  690. package/optional-skills/security/oss-forensics/references/evidence-types.md +89 -0
  691. package/optional-skills/security/oss-forensics/references/github-archive-guide.md +184 -0
  692. package/optional-skills/security/oss-forensics/references/investigation-templates.md +131 -0
  693. package/optional-skills/security/oss-forensics/references/recovery-techniques.md +164 -0
  694. package/optional-skills/security/oss-forensics/scripts/evidence-store.py +313 -0
  695. package/optional-skills/security/oss-forensics/templates/forensic-report.md +151 -0
  696. package/optional-skills/security/oss-forensics/templates/malicious-package-report.md +43 -0
  697. package/optional-skills/security/sherlock/SKILL.md +193 -0
  698. package/optional-skills/software-development/rest-graphql-debug/SKILL.md +514 -0
  699. package/optional-skills/web-development/DESCRIPTION.md +5 -0
  700. package/optional-skills/web-development/page-agent/SKILL.md +190 -0
  701. package/package.json +78 -0
  702. package/plugins/__init__.py +1 -0
  703. package/plugins/__pycache__/__init__.cpython-312.pyc +0 -0
  704. package/plugins/context_engine/__init__.py +219 -0
  705. package/plugins/disk-cleanup/README.md +51 -0
  706. package/plugins/disk-cleanup/__init__.py +316 -0
  707. package/plugins/disk-cleanup/disk_cleanup.py +497 -0
  708. package/plugins/disk-cleanup/plugin.yaml +7 -0
  709. package/plugins/example-dashboard/dashboard/manifest.json +14 -0
  710. package/plugins/example-dashboard/dashboard/plugin_api.py +17 -0
  711. package/plugins/google_meet/README.md +131 -0
  712. package/plugins/google_meet/SKILL.md +148 -0
  713. package/plugins/google_meet/__init__.py +103 -0
  714. package/plugins/google_meet/audio_bridge.py +244 -0
  715. package/plugins/google_meet/cli.py +479 -0
  716. package/plugins/google_meet/meet_bot.py +852 -0
  717. package/plugins/google_meet/node/__init__.py +54 -0
  718. package/plugins/google_meet/node/cli.py +125 -0
  719. package/plugins/google_meet/node/client.py +107 -0
  720. package/plugins/google_meet/node/protocol.py +124 -0
  721. package/plugins/google_meet/node/registry.py +113 -0
  722. package/plugins/google_meet/node/server.py +201 -0
  723. package/plugins/google_meet/plugin.yaml +16 -0
  724. package/plugins/google_meet/process_manager.py +324 -0
  725. package/plugins/google_meet/realtime/__init__.py +10 -0
  726. package/plugins/google_meet/realtime/openai_client.py +332 -0
  727. package/plugins/google_meet/tools.py +348 -0
  728. package/plugins/hermes-achievements/LICENSE +21 -0
  729. package/plugins/hermes-achievements/README.md +150 -0
  730. package/plugins/hermes-achievements/dashboard/dist/index.js +732 -0
  731. package/plugins/hermes-achievements/dashboard/dist/style.css +146 -0
  732. package/plugins/hermes-achievements/dashboard/manifest.json +11 -0
  733. package/plugins/hermes-achievements/dashboard/plugin_api.py +1062 -0
  734. package/plugins/hermes-achievements/docs/achievements-performance-implementation-plan.md +157 -0
  735. package/plugins/hermes-achievements/docs/achievements-performance-implementation-spec.md +219 -0
  736. package/plugins/hermes-achievements/docs/achievements-performance-spec.md +174 -0
  737. package/plugins/hermes-achievements/docs/assets/achievements-dashboard-hd.png +0 -0
  738. package/plugins/hermes-achievements/docs/assets/achievements-tier-showcase-hd.png +0 -0
  739. package/plugins/hermes-achievements/tests/test_achievement_engine.py +156 -0
  740. package/plugins/image_gen/openai/__init__.py +303 -0
  741. package/plugins/image_gen/openai/__pycache__/__init__.cpython-312.pyc +0 -0
  742. package/plugins/image_gen/openai/plugin.yaml +7 -0
  743. package/plugins/image_gen/openai-codex/__init__.py +378 -0
  744. package/plugins/image_gen/openai-codex/__pycache__/__init__.cpython-312.pyc +0 -0
  745. package/plugins/image_gen/openai-codex/plugin.yaml +5 -0
  746. package/plugins/image_gen/xai/__init__.py +316 -0
  747. package/plugins/image_gen/xai/__pycache__/__init__.cpython-312.pyc +0 -0
  748. package/plugins/image_gen/xai/plugin.yaml +7 -0
  749. package/plugins/kanban/dashboard/dist/index.js +3143 -0
  750. package/plugins/kanban/dashboard/dist/style.css +1500 -0
  751. package/plugins/kanban/dashboard/manifest.json +14 -0
  752. package/plugins/kanban/dashboard/plugin_api.py +1612 -0
  753. package/plugins/kanban/systemd/hermes-kanban-dispatcher.service +32 -0
  754. package/plugins/memory/__init__.py +408 -0
  755. package/plugins/memory/byterover/README.md +41 -0
  756. package/plugins/memory/byterover/__init__.py +384 -0
  757. package/plugins/memory/byterover/plugin.yaml +9 -0
  758. package/plugins/memory/hindsight/README.md +138 -0
  759. package/plugins/memory/hindsight/__init__.py +1758 -0
  760. package/plugins/memory/hindsight/plugin.yaml +8 -0
  761. package/plugins/memory/holographic/README.md +36 -0
  762. package/plugins/memory/holographic/__init__.py +409 -0
  763. package/plugins/memory/holographic/holographic.py +203 -0
  764. package/plugins/memory/holographic/plugin.yaml +5 -0
  765. package/plugins/memory/holographic/retrieval.py +593 -0
  766. package/plugins/memory/holographic/store.py +579 -0
  767. package/plugins/memory/honcho/README.md +328 -0
  768. package/plugins/memory/honcho/__init__.py +1329 -0
  769. package/plugins/memory/honcho/cli.py +1452 -0
  770. package/plugins/memory/honcho/client.py +784 -0
  771. package/plugins/memory/honcho/plugin.yaml +7 -0
  772. package/plugins/memory/honcho/session.py +1255 -0
  773. package/plugins/memory/mem0/README.md +38 -0
  774. package/plugins/memory/mem0/__init__.py +374 -0
  775. package/plugins/memory/mem0/plugin.yaml +5 -0
  776. package/plugins/memory/openviking/README.md +40 -0
  777. package/plugins/memory/openviking/__init__.py +945 -0
  778. package/plugins/memory/openviking/plugin.yaml +9 -0
  779. package/plugins/memory/retaindb/README.md +40 -0
  780. package/plugins/memory/retaindb/__init__.py +767 -0
  781. package/plugins/memory/retaindb/plugin.yaml +7 -0
  782. package/plugins/memory/supermemory/README.md +99 -0
  783. package/plugins/memory/supermemory/__init__.py +792 -0
  784. package/plugins/memory/supermemory/plugin.yaml +5 -0
  785. package/plugins/model-providers/README.md +70 -0
  786. package/plugins/model-providers/ai-gateway/__init__.py +43 -0
  787. package/plugins/model-providers/ai-gateway/__pycache__/__init__.cpython-312.pyc +0 -0
  788. package/plugins/model-providers/ai-gateway/plugin.yaml +5 -0
  789. package/plugins/model-providers/alibaba/__init__.py +13 -0
  790. package/plugins/model-providers/alibaba/__pycache__/__init__.cpython-312.pyc +0 -0
  791. package/plugins/model-providers/alibaba/plugin.yaml +5 -0
  792. package/plugins/model-providers/alibaba-coding-plan/__init__.py +21 -0
  793. package/plugins/model-providers/alibaba-coding-plan/__pycache__/__init__.cpython-312.pyc +0 -0
  794. package/plugins/model-providers/alibaba-coding-plan/plugin.yaml +5 -0
  795. package/plugins/model-providers/anthropic/__init__.py +52 -0
  796. package/plugins/model-providers/anthropic/__pycache__/__init__.cpython-312.pyc +0 -0
  797. package/plugins/model-providers/anthropic/plugin.yaml +5 -0
  798. package/plugins/model-providers/arcee/__init__.py +13 -0
  799. package/plugins/model-providers/arcee/__pycache__/__init__.cpython-312.pyc +0 -0
  800. package/plugins/model-providers/arcee/plugin.yaml +5 -0
  801. package/plugins/model-providers/azure-foundry/__init__.py +21 -0
  802. package/plugins/model-providers/azure-foundry/__pycache__/__init__.cpython-312.pyc +0 -0
  803. package/plugins/model-providers/azure-foundry/plugin.yaml +5 -0
  804. package/plugins/model-providers/bedrock/__init__.py +29 -0
  805. package/plugins/model-providers/bedrock/__pycache__/__init__.cpython-312.pyc +0 -0
  806. package/plugins/model-providers/bedrock/plugin.yaml +5 -0
  807. package/plugins/model-providers/copilot/__init__.py +58 -0
  808. package/plugins/model-providers/copilot/__pycache__/__init__.cpython-312.pyc +0 -0
  809. package/plugins/model-providers/copilot/plugin.yaml +5 -0
  810. package/plugins/model-providers/copilot-acp/__init__.py +34 -0
  811. package/plugins/model-providers/copilot-acp/__pycache__/__init__.cpython-312.pyc +0 -0
  812. package/plugins/model-providers/copilot-acp/plugin.yaml +5 -0
  813. package/plugins/model-providers/custom/__init__.py +68 -0
  814. package/plugins/model-providers/custom/__pycache__/__init__.cpython-312.pyc +0 -0
  815. package/plugins/model-providers/custom/plugin.yaml +5 -0
  816. package/plugins/model-providers/deepseek/__init__.py +99 -0
  817. package/plugins/model-providers/deepseek/__pycache__/__init__.cpython-312.pyc +0 -0
  818. package/plugins/model-providers/deepseek/plugin.yaml +5 -0
  819. package/plugins/model-providers/gemini/__init__.py +72 -0
  820. package/plugins/model-providers/gemini/__pycache__/__init__.cpython-312.pyc +0 -0
  821. package/plugins/model-providers/gemini/plugin.yaml +5 -0
  822. package/plugins/model-providers/gmi/__init__.py +31 -0
  823. package/plugins/model-providers/gmi/__pycache__/__init__.cpython-312.pyc +0 -0
  824. package/plugins/model-providers/gmi/plugin.yaml +5 -0
  825. package/plugins/model-providers/huggingface/__init__.py +20 -0
  826. package/plugins/model-providers/huggingface/__pycache__/__init__.cpython-312.pyc +0 -0
  827. package/plugins/model-providers/huggingface/plugin.yaml +5 -0
  828. package/plugins/model-providers/kilocode/__init__.py +14 -0
  829. package/plugins/model-providers/kilocode/__pycache__/__init__.cpython-312.pyc +0 -0
  830. package/plugins/model-providers/kilocode/plugin.yaml +5 -0
  831. package/plugins/model-providers/kimi-coding/__init__.py +71 -0
  832. package/plugins/model-providers/kimi-coding/__pycache__/__init__.cpython-312.pyc +0 -0
  833. package/plugins/model-providers/kimi-coding/plugin.yaml +5 -0
  834. package/plugins/model-providers/minimax/__init__.py +45 -0
  835. package/plugins/model-providers/minimax/__pycache__/__init__.cpython-312.pyc +0 -0
  836. package/plugins/model-providers/minimax/plugin.yaml +5 -0
  837. package/plugins/model-providers/nous/__init__.py +54 -0
  838. package/plugins/model-providers/nous/__pycache__/__init__.cpython-312.pyc +0 -0
  839. package/plugins/model-providers/nous/plugin.yaml +5 -0
  840. package/plugins/model-providers/novita/__init__.py +27 -0
  841. package/plugins/model-providers/novita/__pycache__/__init__.cpython-312.pyc +0 -0
  842. package/plugins/model-providers/novita/plugin.yaml +5 -0
  843. package/plugins/model-providers/nvidia/__init__.py +21 -0
  844. package/plugins/model-providers/nvidia/__pycache__/__init__.cpython-312.pyc +0 -0
  845. package/plugins/model-providers/nvidia/plugin.yaml +5 -0
  846. package/plugins/model-providers/ollama-cloud/__init__.py +14 -0
  847. package/plugins/model-providers/ollama-cloud/__pycache__/__init__.cpython-312.pyc +0 -0
  848. package/plugins/model-providers/ollama-cloud/plugin.yaml +5 -0
  849. package/plugins/model-providers/openai-codex/__init__.py +15 -0
  850. package/plugins/model-providers/openai-codex/__pycache__/__init__.cpython-312.pyc +0 -0
  851. package/plugins/model-providers/openai-codex/plugin.yaml +5 -0
  852. package/plugins/model-providers/opencode-zen/__init__.py +30 -0
  853. package/plugins/model-providers/opencode-zen/__pycache__/__init__.cpython-312.pyc +0 -0
  854. package/plugins/model-providers/opencode-zen/plugin.yaml +5 -0
  855. package/plugins/model-providers/openrouter/__init__.py +115 -0
  856. package/plugins/model-providers/openrouter/__pycache__/__init__.cpython-312.pyc +0 -0
  857. package/plugins/model-providers/openrouter/plugin.yaml +5 -0
  858. package/plugins/model-providers/qwen-oauth/__init__.py +82 -0
  859. package/plugins/model-providers/qwen-oauth/__pycache__/__init__.cpython-312.pyc +0 -0
  860. package/plugins/model-providers/qwen-oauth/plugin.yaml +5 -0
  861. package/plugins/model-providers/stepfun/__init__.py +14 -0
  862. package/plugins/model-providers/stepfun/__pycache__/__init__.cpython-312.pyc +0 -0
  863. package/plugins/model-providers/stepfun/plugin.yaml +5 -0
  864. package/plugins/model-providers/xai/__init__.py +15 -0
  865. package/plugins/model-providers/xai/__pycache__/__init__.cpython-312.pyc +0 -0
  866. package/plugins/model-providers/xai/plugin.yaml +5 -0
  867. package/plugins/model-providers/xiaomi/__init__.py +14 -0
  868. package/plugins/model-providers/xiaomi/__pycache__/__init__.cpython-312.pyc +0 -0
  869. package/plugins/model-providers/xiaomi/plugin.yaml +5 -0
  870. package/plugins/model-providers/zai/__init__.py +21 -0
  871. package/plugins/model-providers/zai/__pycache__/__init__.cpython-312.pyc +0 -0
  872. package/plugins/model-providers/zai/plugin.yaml +5 -0
  873. package/plugins/observability/langfuse/README.md +53 -0
  874. package/plugins/observability/langfuse/__init__.py +1004 -0
  875. package/plugins/observability/langfuse/plugin.yaml +14 -0
  876. package/plugins/platforms/google_chat/__init__.py +3 -0
  877. package/plugins/platforms/google_chat/__pycache__/__init__.cpython-312.pyc +0 -0
  878. package/plugins/platforms/google_chat/__pycache__/adapter.cpython-312.pyc +0 -0
  879. package/plugins/platforms/google_chat/adapter.py +3343 -0
  880. package/plugins/platforms/google_chat/oauth.py +639 -0
  881. package/plugins/platforms/google_chat/plugin.yaml +39 -0
  882. package/plugins/platforms/irc/__init__.py +3 -0
  883. package/plugins/platforms/irc/__pycache__/__init__.cpython-312.pyc +0 -0
  884. package/plugins/platforms/irc/__pycache__/adapter.cpython-312.pyc +0 -0
  885. package/plugins/platforms/irc/adapter.py +969 -0
  886. package/plugins/platforms/irc/plugin.yaml +54 -0
  887. package/plugins/platforms/line/__init__.py +3 -0
  888. package/plugins/platforms/line/__pycache__/__init__.cpython-312.pyc +0 -0
  889. package/plugins/platforms/line/__pycache__/adapter.cpython-312.pyc +0 -0
  890. package/plugins/platforms/line/adapter.py +1639 -0
  891. package/plugins/platforms/line/plugin.yaml +65 -0
  892. package/plugins/platforms/simplex/__init__.py +3 -0
  893. package/plugins/platforms/simplex/__pycache__/__init__.cpython-312.pyc +0 -0
  894. package/plugins/platforms/simplex/__pycache__/adapter.cpython-312.pyc +0 -0
  895. package/plugins/platforms/simplex/adapter.py +746 -0
  896. package/plugins/platforms/simplex/plugin.yaml +37 -0
  897. package/plugins/platforms/teams/__init__.py +3 -0
  898. package/plugins/platforms/teams/__pycache__/__init__.cpython-312.pyc +0 -0
  899. package/plugins/platforms/teams/__pycache__/adapter.cpython-312.pyc +0 -0
  900. package/plugins/platforms/teams/adapter.py +1188 -0
  901. package/plugins/platforms/teams/plugin.yaml +48 -0
  902. package/plugins/spotify/__init__.py +66 -0
  903. package/plugins/spotify/__pycache__/__init__.cpython-312.pyc +0 -0
  904. package/plugins/spotify/__pycache__/client.cpython-312.pyc +0 -0
  905. package/plugins/spotify/__pycache__/tools.cpython-312.pyc +0 -0
  906. package/plugins/spotify/client.py +435 -0
  907. package/plugins/spotify/plugin.yaml +13 -0
  908. package/plugins/spotify/tools.py +454 -0
  909. package/plugins/teams_pipeline/__init__.py +23 -0
  910. package/plugins/teams_pipeline/cli.py +463 -0
  911. package/plugins/teams_pipeline/meetings.py +333 -0
  912. package/plugins/teams_pipeline/models.py +350 -0
  913. package/plugins/teams_pipeline/pipeline.py +692 -0
  914. package/plugins/teams_pipeline/plugin.yaml +9 -0
  915. package/plugins/teams_pipeline/runtime.py +135 -0
  916. package/plugins/teams_pipeline/store.py +194 -0
  917. package/plugins/teams_pipeline/subscriptions.py +249 -0
  918. package/plugins/video_gen/fal/__init__.py +523 -0
  919. package/plugins/video_gen/fal/__pycache__/__init__.cpython-312.pyc +0 -0
  920. package/plugins/video_gen/fal/plugin.yaml +7 -0
  921. package/plugins/video_gen/xai/__init__.py +441 -0
  922. package/plugins/video_gen/xai/__pycache__/__init__.cpython-312.pyc +0 -0
  923. package/plugins/video_gen/xai/plugin.yaml +7 -0
  924. package/plugins/web/__init__.py +7 -0
  925. package/plugins/web/__pycache__/__init__.cpython-312.pyc +0 -0
  926. package/plugins/web/brave_free/__init__.py +14 -0
  927. package/plugins/web/brave_free/__pycache__/__init__.cpython-312.pyc +0 -0
  928. package/plugins/web/brave_free/__pycache__/provider.cpython-312.pyc +0 -0
  929. package/plugins/web/brave_free/plugin.yaml +7 -0
  930. package/plugins/web/brave_free/provider.py +137 -0
  931. package/plugins/web/ddgs/__init__.py +15 -0
  932. package/plugins/web/ddgs/__pycache__/__init__.cpython-312.pyc +0 -0
  933. package/plugins/web/ddgs/__pycache__/provider.cpython-312.pyc +0 -0
  934. package/plugins/web/ddgs/plugin.yaml +7 -0
  935. package/plugins/web/ddgs/provider.py +104 -0
  936. package/plugins/web/exa/__init__.py +15 -0
  937. package/plugins/web/exa/__pycache__/__init__.cpython-312.pyc +0 -0
  938. package/plugins/web/exa/__pycache__/provider.cpython-312.pyc +0 -0
  939. package/plugins/web/exa/plugin.yaml +7 -0
  940. package/plugins/web/exa/provider.py +212 -0
  941. package/plugins/web/firecrawl/__init__.py +28 -0
  942. package/plugins/web/firecrawl/__pycache__/__init__.cpython-312.pyc +0 -0
  943. package/plugins/web/firecrawl/__pycache__/provider.cpython-312.pyc +0 -0
  944. package/plugins/web/firecrawl/plugin.yaml +7 -0
  945. package/plugins/web/firecrawl/provider.py +773 -0
  946. package/plugins/web/parallel/__init__.py +16 -0
  947. package/plugins/web/parallel/__pycache__/__init__.cpython-312.pyc +0 -0
  948. package/plugins/web/parallel/__pycache__/provider.cpython-312.pyc +0 -0
  949. package/plugins/web/parallel/plugin.yaml +7 -0
  950. package/plugins/web/parallel/provider.py +291 -0
  951. package/plugins/web/searxng/__init__.py +15 -0
  952. package/plugins/web/searxng/__pycache__/__init__.cpython-312.pyc +0 -0
  953. package/plugins/web/searxng/__pycache__/provider.cpython-312.pyc +0 -0
  954. package/plugins/web/searxng/plugin.yaml +7 -0
  955. package/plugins/web/searxng/provider.py +140 -0
  956. package/plugins/web/tavily/__init__.py +15 -0
  957. package/plugins/web/tavily/__pycache__/__init__.cpython-312.pyc +0 -0
  958. package/plugins/web/tavily/__pycache__/provider.cpython-312.pyc +0 -0
  959. package/plugins/web/tavily/plugin.yaml +7 -0
  960. package/plugins/web/tavily/provider.py +285 -0
  961. package/providers/README.md +78 -0
  962. package/providers/__init__.py +192 -0
  963. package/providers/__pycache__/__init__.cpython-312.pyc +0 -0
  964. package/providers/__pycache__/base.cpython-312.pyc +0 -0
  965. package/providers/base.py +184 -0
  966. package/pyproject.toml +255 -0
  967. package/run_agent.py +16409 -0
  968. package/scripts/benchmark_browser_eval.py +138 -0
  969. package/scripts/build_model_catalog.py +95 -0
  970. package/scripts/build_skills_index.py +325 -0
  971. package/scripts/check-windows-footguns.py +624 -0
  972. package/scripts/contributor_audit.py +473 -0
  973. package/scripts/discord-voice-doctor.py +396 -0
  974. package/scripts/hermes-gateway +416 -0
  975. package/scripts/install.cmd +28 -0
  976. package/scripts/install.ps1 +1611 -0
  977. package/scripts/install.sh +2007 -0
  978. package/scripts/install_psutil_android.py +117 -0
  979. package/scripts/keystroke_diagnostic.py +81 -0
  980. package/scripts/kill_modal.sh +34 -0
  981. package/scripts/lib/node-bootstrap.sh +238 -0
  982. package/scripts/lint_diff.py +207 -0
  983. package/scripts/postinstall.js +150 -0
  984. package/scripts/profile-tui.py +626 -0
  985. package/scripts/release.py +1680 -0
  986. package/scripts/run_tests.sh +129 -0
  987. package/scripts/sample_and_compress.py +409 -0
  988. package/scripts/setup_open_webui.sh +349 -0
  989. package/scripts/whatsapp-bridge/allowlist.js +88 -0
  990. package/scripts/whatsapp-bridge/allowlist.test.mjs +80 -0
  991. package/scripts/whatsapp-bridge/bridge.js +729 -0
  992. package/scripts/whatsapp-bridge/package-lock.json +2141 -0
  993. package/scripts/whatsapp-bridge/package.json +19 -0
  994. package/skills/apple/DESCRIPTION.md +2 -0
  995. package/skills/apple/apple-notes/SKILL.md +90 -0
  996. package/skills/apple/apple-reminders/SKILL.md +98 -0
  997. package/skills/apple/findmy/SKILL.md +131 -0
  998. package/skills/apple/imessage/SKILL.md +102 -0
  999. package/skills/apple/macos-computer-use/SKILL.md +201 -0
  1000. package/skills/autonomous-ai-agents/DESCRIPTION.md +3 -0
  1001. package/skills/autonomous-ai-agents/claude-code/SKILL.md +745 -0
  1002. package/skills/autonomous-ai-agents/codex/SKILL.md +130 -0
  1003. package/skills/autonomous-ai-agents/hermes-agent/SKILL.md +1014 -0
  1004. package/skills/autonomous-ai-agents/opencode/SKILL.md +219 -0
  1005. package/skills/creative/DESCRIPTION.md +3 -0
  1006. package/skills/creative/architecture-diagram/SKILL.md +148 -0
  1007. package/skills/creative/architecture-diagram/templates/template.html +319 -0
  1008. package/skills/creative/ascii-art/SKILL.md +322 -0
  1009. package/skills/creative/ascii-video/README.md +290 -0
  1010. package/skills/creative/ascii-video/SKILL.md +241 -0
  1011. package/skills/creative/ascii-video/references/architecture.md +802 -0
  1012. package/skills/creative/ascii-video/references/composition.md +892 -0
  1013. package/skills/creative/ascii-video/references/effects.md +1865 -0
  1014. package/skills/creative/ascii-video/references/inputs.md +685 -0
  1015. package/skills/creative/ascii-video/references/optimization.md +688 -0
  1016. package/skills/creative/ascii-video/references/scenes.md +1011 -0
  1017. package/skills/creative/ascii-video/references/shaders.md +1385 -0
  1018. package/skills/creative/ascii-video/references/troubleshooting.md +367 -0
  1019. package/skills/creative/baoyu-comic/PORT_NOTES.md +77 -0
  1020. package/skills/creative/baoyu-comic/SKILL.md +247 -0
  1021. package/skills/creative/baoyu-comic/references/analysis-framework.md +176 -0
  1022. package/skills/creative/baoyu-comic/references/art-styles/chalk.md +101 -0
  1023. package/skills/creative/baoyu-comic/references/art-styles/ink-brush.md +97 -0
  1024. package/skills/creative/baoyu-comic/references/art-styles/ligne-claire.md +75 -0
  1025. package/skills/creative/baoyu-comic/references/art-styles/manga.md +93 -0
  1026. package/skills/creative/baoyu-comic/references/art-styles/minimalist.md +84 -0
  1027. package/skills/creative/baoyu-comic/references/art-styles/realistic.md +89 -0
  1028. package/skills/creative/baoyu-comic/references/auto-selection.md +71 -0
  1029. package/skills/creative/baoyu-comic/references/base-prompt.md +98 -0
  1030. package/skills/creative/baoyu-comic/references/character-template.md +180 -0
  1031. package/skills/creative/baoyu-comic/references/layouts/cinematic.md +23 -0
  1032. package/skills/creative/baoyu-comic/references/layouts/dense.md +23 -0
  1033. package/skills/creative/baoyu-comic/references/layouts/four-panel.md +40 -0
  1034. package/skills/creative/baoyu-comic/references/layouts/mixed.md +23 -0
  1035. package/skills/creative/baoyu-comic/references/layouts/splash.md +23 -0
  1036. package/skills/creative/baoyu-comic/references/layouts/standard.md +23 -0
  1037. package/skills/creative/baoyu-comic/references/layouts/webtoon.md +30 -0
  1038. package/skills/creative/baoyu-comic/references/ohmsha-guide.md +85 -0
  1039. package/skills/creative/baoyu-comic/references/partial-workflows.md +106 -0
  1040. package/skills/creative/baoyu-comic/references/presets/concept-story.md +121 -0
  1041. package/skills/creative/baoyu-comic/references/presets/four-panel.md +107 -0
  1042. package/skills/creative/baoyu-comic/references/presets/ohmsha.md +114 -0
  1043. package/skills/creative/baoyu-comic/references/presets/shoujo.md +116 -0
  1044. package/skills/creative/baoyu-comic/references/presets/wuxia.md +110 -0
  1045. package/skills/creative/baoyu-comic/references/storyboard-template.md +143 -0
  1046. package/skills/creative/baoyu-comic/references/tones/action.md +110 -0
  1047. package/skills/creative/baoyu-comic/references/tones/dramatic.md +95 -0
  1048. package/skills/creative/baoyu-comic/references/tones/energetic.md +105 -0
  1049. package/skills/creative/baoyu-comic/references/tones/neutral.md +63 -0
  1050. package/skills/creative/baoyu-comic/references/tones/romantic.md +100 -0
  1051. package/skills/creative/baoyu-comic/references/tones/vintage.md +104 -0
  1052. package/skills/creative/baoyu-comic/references/tones/warm.md +94 -0
  1053. package/skills/creative/baoyu-comic/references/workflow.md +401 -0
  1054. package/skills/creative/baoyu-infographic/PORT_NOTES.md +43 -0
  1055. package/skills/creative/baoyu-infographic/SKILL.md +237 -0
  1056. package/skills/creative/baoyu-infographic/references/analysis-framework.md +182 -0
  1057. package/skills/creative/baoyu-infographic/references/base-prompt.md +43 -0
  1058. package/skills/creative/baoyu-infographic/references/layouts/bento-grid.md +41 -0
  1059. package/skills/creative/baoyu-infographic/references/layouts/binary-comparison.md +48 -0
  1060. package/skills/creative/baoyu-infographic/references/layouts/bridge.md +41 -0
  1061. package/skills/creative/baoyu-infographic/references/layouts/circular-flow.md +41 -0
  1062. package/skills/creative/baoyu-infographic/references/layouts/comic-strip.md +41 -0
  1063. package/skills/creative/baoyu-infographic/references/layouts/comparison-matrix.md +41 -0
  1064. package/skills/creative/baoyu-infographic/references/layouts/dashboard.md +41 -0
  1065. package/skills/creative/baoyu-infographic/references/layouts/dense-modules.md +72 -0
  1066. package/skills/creative/baoyu-infographic/references/layouts/funnel.md +41 -0
  1067. package/skills/creative/baoyu-infographic/references/layouts/hierarchical-layers.md +48 -0
  1068. package/skills/creative/baoyu-infographic/references/layouts/hub-spoke.md +41 -0
  1069. package/skills/creative/baoyu-infographic/references/layouts/iceberg.md +41 -0
  1070. package/skills/creative/baoyu-infographic/references/layouts/isometric-map.md +41 -0
  1071. package/skills/creative/baoyu-infographic/references/layouts/jigsaw.md +41 -0
  1072. package/skills/creative/baoyu-infographic/references/layouts/linear-progression.md +48 -0
  1073. package/skills/creative/baoyu-infographic/references/layouts/periodic-table.md +41 -0
  1074. package/skills/creative/baoyu-infographic/references/layouts/story-mountain.md +41 -0
  1075. package/skills/creative/baoyu-infographic/references/layouts/structural-breakdown.md +48 -0
  1076. package/skills/creative/baoyu-infographic/references/layouts/tree-branching.md +41 -0
  1077. package/skills/creative/baoyu-infographic/references/layouts/venn-diagram.md +41 -0
  1078. package/skills/creative/baoyu-infographic/references/layouts/winding-roadmap.md +41 -0
  1079. package/skills/creative/baoyu-infographic/references/structured-content-template.md +244 -0
  1080. package/skills/creative/baoyu-infographic/references/styles/aged-academia.md +36 -0
  1081. package/skills/creative/baoyu-infographic/references/styles/bold-graphic.md +36 -0
  1082. package/skills/creative/baoyu-infographic/references/styles/chalkboard.md +61 -0
  1083. package/skills/creative/baoyu-infographic/references/styles/claymation.md +29 -0
  1084. package/skills/creative/baoyu-infographic/references/styles/corporate-memphis.md +29 -0
  1085. package/skills/creative/baoyu-infographic/references/styles/craft-handmade.md +44 -0
  1086. package/skills/creative/baoyu-infographic/references/styles/cyberpunk-neon.md +29 -0
  1087. package/skills/creative/baoyu-infographic/references/styles/hand-drawn-edu.md +63 -0
  1088. package/skills/creative/baoyu-infographic/references/styles/ikea-manual.md +29 -0
  1089. package/skills/creative/baoyu-infographic/references/styles/kawaii.md +29 -0
  1090. package/skills/creative/baoyu-infographic/references/styles/knolling.md +29 -0
  1091. package/skills/creative/baoyu-infographic/references/styles/lego-brick.md +29 -0
  1092. package/skills/creative/baoyu-infographic/references/styles/morandi-journal.md +60 -0
  1093. package/skills/creative/baoyu-infographic/references/styles/origami.md +29 -0
  1094. package/skills/creative/baoyu-infographic/references/styles/pixel-art.md +29 -0
  1095. package/skills/creative/baoyu-infographic/references/styles/pop-laboratory.md +48 -0
  1096. package/skills/creative/baoyu-infographic/references/styles/retro-pop-grid.md +47 -0
  1097. package/skills/creative/baoyu-infographic/references/styles/storybook-watercolor.md +29 -0
  1098. package/skills/creative/baoyu-infographic/references/styles/subway-map.md +29 -0
  1099. package/skills/creative/baoyu-infographic/references/styles/technical-schematic.md +36 -0
  1100. package/skills/creative/baoyu-infographic/references/styles/ui-wireframe.md +29 -0
  1101. package/skills/creative/claude-design/SKILL.md +591 -0
  1102. package/skills/creative/comfyui/SKILL.md +612 -0
  1103. package/skills/creative/comfyui/references/official-cli.md +255 -0
  1104. package/skills/creative/comfyui/references/rest-api.md +312 -0
  1105. package/skills/creative/comfyui/references/template-integrity.md +243 -0
  1106. package/skills/creative/comfyui/references/workflow-format.md +226 -0
  1107. package/skills/creative/comfyui/scripts/_common.py +835 -0
  1108. package/skills/creative/comfyui/scripts/auto_fix_deps.py +225 -0
  1109. package/skills/creative/comfyui/scripts/check_deps.py +437 -0
  1110. package/skills/creative/comfyui/scripts/comfyui_setup.sh +286 -0
  1111. package/skills/creative/comfyui/scripts/extract_schema.py +315 -0
  1112. package/skills/creative/comfyui/scripts/fetch_logs.py +158 -0
  1113. package/skills/creative/comfyui/scripts/hardware_check.py +497 -0
  1114. package/skills/creative/comfyui/scripts/health_check.py +223 -0
  1115. package/skills/creative/comfyui/scripts/run_batch.py +243 -0
  1116. package/skills/creative/comfyui/scripts/run_workflow.py +796 -0
  1117. package/skills/creative/comfyui/scripts/ws_monitor.py +267 -0
  1118. package/skills/creative/comfyui/tests/README.md +50 -0
  1119. package/skills/creative/comfyui/tests/conftest.py +64 -0
  1120. package/skills/creative/comfyui/tests/pytest.ini +5 -0
  1121. package/skills/creative/comfyui/tests/test_check_deps.py +68 -0
  1122. package/skills/creative/comfyui/tests/test_cloud_integration.py +95 -0
  1123. package/skills/creative/comfyui/tests/test_common.py +447 -0
  1124. package/skills/creative/comfyui/tests/test_extract_schema.py +185 -0
  1125. package/skills/creative/comfyui/tests/test_run_workflow.py +213 -0
  1126. package/skills/creative/comfyui/workflows/README.md +86 -0
  1127. package/skills/creative/comfyui/workflows/animatediff_video.json +64 -0
  1128. package/skills/creative/comfyui/workflows/flux_dev_txt2img.json +78 -0
  1129. package/skills/creative/comfyui/workflows/sd15_txt2img.json +49 -0
  1130. package/skills/creative/comfyui/workflows/sdxl_img2img.json +54 -0
  1131. package/skills/creative/comfyui/workflows/sdxl_inpaint.json +59 -0
  1132. package/skills/creative/comfyui/workflows/sdxl_txt2img.json +49 -0
  1133. package/skills/creative/comfyui/workflows/upscale_4x.json +27 -0
  1134. package/skills/creative/comfyui/workflows/wan_video_t2v.json +69 -0
  1135. package/skills/creative/creative-ideation/SKILL.md +152 -0
  1136. package/skills/creative/creative-ideation/references/full-prompt-library.md +110 -0
  1137. package/skills/creative/design-md/SKILL.md +199 -0
  1138. package/skills/creative/design-md/templates/starter.md +99 -0
  1139. package/skills/creative/excalidraw/SKILL.md +199 -0
  1140. package/skills/creative/excalidraw/references/colors.md +44 -0
  1141. package/skills/creative/excalidraw/references/dark-mode.md +68 -0
  1142. package/skills/creative/excalidraw/references/examples.md +141 -0
  1143. package/skills/creative/excalidraw/scripts/upload.py +133 -0
  1144. package/skills/creative/humanizer/LICENSE +21 -0
  1145. package/skills/creative/humanizer/SKILL.md +578 -0
  1146. package/skills/creative/manim-video/README.md +23 -0
  1147. package/skills/creative/manim-video/SKILL.md +269 -0
  1148. package/skills/creative/manim-video/references/animation-design-thinking.md +161 -0
  1149. package/skills/creative/manim-video/references/animations.md +282 -0
  1150. package/skills/creative/manim-video/references/camera-and-3d.md +135 -0
  1151. package/skills/creative/manim-video/references/decorations.md +202 -0
  1152. package/skills/creative/manim-video/references/equations.md +216 -0
  1153. package/skills/creative/manim-video/references/graphs-and-data.md +163 -0
  1154. package/skills/creative/manim-video/references/mobjects.md +333 -0
  1155. package/skills/creative/manim-video/references/paper-explainer.md +255 -0
  1156. package/skills/creative/manim-video/references/production-quality.md +190 -0
  1157. package/skills/creative/manim-video/references/rendering.md +185 -0
  1158. package/skills/creative/manim-video/references/scene-planning.md +118 -0
  1159. package/skills/creative/manim-video/references/troubleshooting.md +135 -0
  1160. package/skills/creative/manim-video/references/updaters-and-trackers.md +260 -0
  1161. package/skills/creative/manim-video/references/visual-design.md +124 -0
  1162. package/skills/creative/manim-video/scripts/setup.sh +14 -0
  1163. package/skills/creative/p5js/README.md +64 -0
  1164. package/skills/creative/p5js/SKILL.md +556 -0
  1165. package/skills/creative/p5js/references/animation.md +439 -0
  1166. package/skills/creative/p5js/references/color-systems.md +352 -0
  1167. package/skills/creative/p5js/references/core-api.md +410 -0
  1168. package/skills/creative/p5js/references/export-pipeline.md +566 -0
  1169. package/skills/creative/p5js/references/interaction.md +398 -0
  1170. package/skills/creative/p5js/references/shapes-and-geometry.md +300 -0
  1171. package/skills/creative/p5js/references/troubleshooting.md +532 -0
  1172. package/skills/creative/p5js/references/typography.md +302 -0
  1173. package/skills/creative/p5js/references/visual-effects.md +895 -0
  1174. package/skills/creative/p5js/references/webgl-and-3d.md +423 -0
  1175. package/skills/creative/p5js/scripts/export-frames.js +179 -0
  1176. package/skills/creative/p5js/scripts/render.sh +108 -0
  1177. package/skills/creative/p5js/scripts/serve.sh +28 -0
  1178. package/skills/creative/p5js/scripts/setup.sh +87 -0
  1179. package/skills/creative/p5js/templates/viewer.html +395 -0
  1180. package/skills/creative/pixel-art/ATTRIBUTION.md +54 -0
  1181. package/skills/creative/pixel-art/SKILL.md +218 -0
  1182. package/skills/creative/pixel-art/references/palettes.md +49 -0
  1183. package/skills/creative/pixel-art/scripts/__init__.py +0 -0
  1184. package/skills/creative/pixel-art/scripts/palettes.py +167 -0
  1185. package/skills/creative/pixel-art/scripts/pixel_art.py +162 -0
  1186. package/skills/creative/pixel-art/scripts/pixel_art_video.py +345 -0
  1187. package/skills/creative/popular-web-designs/SKILL.md +214 -0
  1188. package/skills/creative/popular-web-designs/templates/airbnb.md +259 -0
  1189. package/skills/creative/popular-web-designs/templates/airtable.md +102 -0
  1190. package/skills/creative/popular-web-designs/templates/apple.md +326 -0
  1191. package/skills/creative/popular-web-designs/templates/bmw.md +193 -0
  1192. package/skills/creative/popular-web-designs/templates/cal.md +272 -0
  1193. package/skills/creative/popular-web-designs/templates/claude.md +325 -0
  1194. package/skills/creative/popular-web-designs/templates/clay.md +317 -0
  1195. package/skills/creative/popular-web-designs/templates/clickhouse.md +294 -0
  1196. package/skills/creative/popular-web-designs/templates/cohere.md +279 -0
  1197. package/skills/creative/popular-web-designs/templates/coinbase.md +142 -0
  1198. package/skills/creative/popular-web-designs/templates/composio.md +320 -0
  1199. package/skills/creative/popular-web-designs/templates/cursor.md +322 -0
  1200. package/skills/creative/popular-web-designs/templates/elevenlabs.md +278 -0
  1201. package/skills/creative/popular-web-designs/templates/expo.md +294 -0
  1202. package/skills/creative/popular-web-designs/templates/figma.md +233 -0
  1203. package/skills/creative/popular-web-designs/templates/framer.md +259 -0
  1204. package/skills/creative/popular-web-designs/templates/hashicorp.md +291 -0
  1205. package/skills/creative/popular-web-designs/templates/ibm.md +345 -0
  1206. package/skills/creative/popular-web-designs/templates/intercom.md +159 -0
  1207. package/skills/creative/popular-web-designs/templates/kraken.md +138 -0
  1208. package/skills/creative/popular-web-designs/templates/linear.app.md +380 -0
  1209. package/skills/creative/popular-web-designs/templates/lovable.md +311 -0
  1210. package/skills/creative/popular-web-designs/templates/minimax.md +270 -0
  1211. package/skills/creative/popular-web-designs/templates/mintlify.md +339 -0
  1212. package/skills/creative/popular-web-designs/templates/miro.md +121 -0
  1213. package/skills/creative/popular-web-designs/templates/mistral.ai.md +274 -0
  1214. package/skills/creative/popular-web-designs/templates/mongodb.md +279 -0
  1215. package/skills/creative/popular-web-designs/templates/notion.md +322 -0
  1216. package/skills/creative/popular-web-designs/templates/nvidia.md +306 -0
  1217. package/skills/creative/popular-web-designs/templates/ollama.md +280 -0
  1218. package/skills/creative/popular-web-designs/templates/opencode.ai.md +294 -0
  1219. package/skills/creative/popular-web-designs/templates/pinterest.md +243 -0
  1220. package/skills/creative/popular-web-designs/templates/posthog.md +269 -0
  1221. package/skills/creative/popular-web-designs/templates/raycast.md +281 -0
  1222. package/skills/creative/popular-web-designs/templates/replicate.md +274 -0
  1223. package/skills/creative/popular-web-designs/templates/resend.md +316 -0
  1224. package/skills/creative/popular-web-designs/templates/revolut.md +198 -0
  1225. package/skills/creative/popular-web-designs/templates/runwayml.md +257 -0
  1226. package/skills/creative/popular-web-designs/templates/sanity.md +370 -0
  1227. package/skills/creative/popular-web-designs/templates/sentry.md +275 -0
  1228. package/skills/creative/popular-web-designs/templates/spacex.md +207 -0
  1229. package/skills/creative/popular-web-designs/templates/spotify.md +259 -0
  1230. package/skills/creative/popular-web-designs/templates/stripe.md +335 -0
  1231. package/skills/creative/popular-web-designs/templates/supabase.md +268 -0
  1232. package/skills/creative/popular-web-designs/templates/superhuman.md +265 -0
  1233. package/skills/creative/popular-web-designs/templates/together.ai.md +276 -0
  1234. package/skills/creative/popular-web-designs/templates/uber.md +308 -0
  1235. package/skills/creative/popular-web-designs/templates/vercel.md +323 -0
  1236. package/skills/creative/popular-web-designs/templates/voltagent.md +336 -0
  1237. package/skills/creative/popular-web-designs/templates/warp.md +266 -0
  1238. package/skills/creative/popular-web-designs/templates/webflow.md +105 -0
  1239. package/skills/creative/popular-web-designs/templates/wise.md +186 -0
  1240. package/skills/creative/popular-web-designs/templates/x.ai.md +270 -0
  1241. package/skills/creative/popular-web-designs/templates/zapier.md +341 -0
  1242. package/skills/creative/pretext/SKILL.md +220 -0
  1243. package/skills/creative/pretext/references/patterns.md +258 -0
  1244. package/skills/creative/pretext/templates/donut-orbit.html +1468 -0
  1245. package/skills/creative/pretext/templates/hello-orb-flow.html +95 -0
  1246. package/skills/creative/sketch/SKILL.md +218 -0
  1247. package/skills/creative/songwriting-and-ai-music/SKILL.md +287 -0
  1248. package/skills/creative/touchdesigner-mcp/SKILL.md +356 -0
  1249. package/skills/creative/touchdesigner-mcp/references/3d-scene.md +275 -0
  1250. package/skills/creative/touchdesigner-mcp/references/animation.md +221 -0
  1251. package/skills/creative/touchdesigner-mcp/references/audio-reactive.md +175 -0
  1252. package/skills/creative/touchdesigner-mcp/references/dat-scripting.md +352 -0
  1253. package/skills/creative/touchdesigner-mcp/references/external-data.md +322 -0
  1254. package/skills/creative/touchdesigner-mcp/references/geometry-comp.md +121 -0
  1255. package/skills/creative/touchdesigner-mcp/references/glsl.md +151 -0
  1256. package/skills/creative/touchdesigner-mcp/references/layout-compositor.md +131 -0
  1257. package/skills/creative/touchdesigner-mcp/references/mcp-tools.md +382 -0
  1258. package/skills/creative/touchdesigner-mcp/references/midi-osc.md +211 -0
  1259. package/skills/creative/touchdesigner-mcp/references/network-patterns.md +966 -0
  1260. package/skills/creative/touchdesigner-mcp/references/operator-tips.md +106 -0
  1261. package/skills/creative/touchdesigner-mcp/references/operators.md +239 -0
  1262. package/skills/creative/touchdesigner-mcp/references/panel-ui.md +281 -0
  1263. package/skills/creative/touchdesigner-mcp/references/particles.md +245 -0
  1264. package/skills/creative/touchdesigner-mcp/references/pitfalls.md +704 -0
  1265. package/skills/creative/touchdesigner-mcp/references/postfx.md +183 -0
  1266. package/skills/creative/touchdesigner-mcp/references/projection-mapping.md +211 -0
  1267. package/skills/creative/touchdesigner-mcp/references/python-api.md +463 -0
  1268. package/skills/creative/touchdesigner-mcp/references/replicator.md +198 -0
  1269. package/skills/creative/touchdesigner-mcp/references/troubleshooting.md +244 -0
  1270. package/skills/creative/touchdesigner-mcp/scripts/setup.sh +115 -0
  1271. package/skills/data-science/DESCRIPTION.md +3 -0
  1272. package/skills/data-science/jupyter-live-kernel/SKILL.md +167 -0
  1273. package/skills/devops/kanban-orchestrator/SKILL.md +189 -0
  1274. package/skills/devops/kanban-worker/SKILL.md +184 -0
  1275. package/skills/devops/webhook-subscriptions/SKILL.md +204 -0
  1276. package/skills/diagramming/DESCRIPTION.md +3 -0
  1277. package/skills/dogfood/SKILL.md +162 -0
  1278. package/skills/dogfood/references/issue-taxonomy.md +109 -0
  1279. package/skills/dogfood/templates/dogfood-report-template.md +86 -0
  1280. package/skills/domain/DESCRIPTION.md +24 -0
  1281. package/skills/email/DESCRIPTION.md +3 -0
  1282. package/skills/email/himalaya/SKILL.md +299 -0
  1283. package/skills/email/himalaya/references/configuration.md +227 -0
  1284. package/skills/email/himalaya/references/message-composition.md +199 -0
  1285. package/skills/gaming/DESCRIPTION.md +3 -0
  1286. package/skills/gaming/minecraft-modpack-server/SKILL.md +187 -0
  1287. package/skills/gaming/pokemon-player/SKILL.md +216 -0
  1288. package/skills/gifs/DESCRIPTION.md +3 -0
  1289. package/skills/github/DESCRIPTION.md +3 -0
  1290. package/skills/github/codebase-inspection/SKILL.md +116 -0
  1291. package/skills/github/github-auth/SKILL.md +247 -0
  1292. package/skills/github/github-auth/scripts/gh-env.sh +66 -0
  1293. package/skills/github/github-code-review/SKILL.md +481 -0
  1294. package/skills/github/github-code-review/references/review-output-template.md +74 -0
  1295. package/skills/github/github-issues/SKILL.md +370 -0
  1296. package/skills/github/github-issues/templates/bug-report.md +35 -0
  1297. package/skills/github/github-issues/templates/feature-request.md +31 -0
  1298. package/skills/github/github-pr-workflow/SKILL.md +367 -0
  1299. package/skills/github/github-pr-workflow/references/ci-troubleshooting.md +183 -0
  1300. package/skills/github/github-pr-workflow/references/conventional-commits.md +71 -0
  1301. package/skills/github/github-pr-workflow/templates/pr-body-bugfix.md +35 -0
  1302. package/skills/github/github-pr-workflow/templates/pr-body-feature.md +33 -0
  1303. package/skills/github/github-repo-management/SKILL.md +516 -0
  1304. package/skills/github/github-repo-management/references/github-api-cheatsheet.md +161 -0
  1305. package/skills/index-cache/anthropics_skills_skills_.json +1 -0
  1306. package/skills/index-cache/claude_marketplace_anthropics_skills.json +1 -0
  1307. package/skills/index-cache/lobehub_index.json +1 -0
  1308. package/skills/index-cache/openai_skills_skills_.json +1 -0
  1309. package/skills/inference-sh/DESCRIPTION.md +19 -0
  1310. package/skills/mcp/DESCRIPTION.md +3 -0
  1311. package/skills/mcp/native-mcp/SKILL.md +357 -0
  1312. package/skills/media/DESCRIPTION.md +3 -0
  1313. package/skills/media/gif-search/SKILL.md +91 -0
  1314. package/skills/media/heartmula/SKILL.md +171 -0
  1315. package/skills/media/songsee/SKILL.md +83 -0
  1316. package/skills/media/spotify/SKILL.md +135 -0
  1317. package/skills/media/youtube-content/SKILL.md +73 -0
  1318. package/skills/media/youtube-content/references/output-formats.md +56 -0
  1319. package/skills/media/youtube-content/scripts/fetch_transcript.py +124 -0
  1320. package/skills/mlops/DESCRIPTION.md +3 -0
  1321. package/skills/mlops/evaluation/DESCRIPTION.md +3 -0
  1322. package/skills/mlops/evaluation/lm-evaluation-harness/SKILL.md +498 -0
  1323. package/skills/mlops/evaluation/lm-evaluation-harness/references/api-evaluation.md +490 -0
  1324. package/skills/mlops/evaluation/lm-evaluation-harness/references/benchmark-guide.md +488 -0
  1325. package/skills/mlops/evaluation/lm-evaluation-harness/references/custom-tasks.md +602 -0
  1326. package/skills/mlops/evaluation/lm-evaluation-harness/references/distributed-eval.md +519 -0
  1327. package/skills/mlops/evaluation/weights-and-biases/SKILL.md +594 -0
  1328. package/skills/mlops/evaluation/weights-and-biases/references/artifacts.md +584 -0
  1329. package/skills/mlops/evaluation/weights-and-biases/references/integrations.md +700 -0
  1330. package/skills/mlops/evaluation/weights-and-biases/references/sweeps.md +847 -0
  1331. package/skills/mlops/huggingface-hub/SKILL.md +81 -0
  1332. package/skills/mlops/inference/DESCRIPTION.md +3 -0
  1333. package/skills/mlops/inference/llama-cpp/SKILL.md +249 -0
  1334. package/skills/mlops/inference/llama-cpp/references/advanced-usage.md +504 -0
  1335. package/skills/mlops/inference/llama-cpp/references/hub-discovery.md +168 -0
  1336. package/skills/mlops/inference/llama-cpp/references/optimization.md +89 -0
  1337. package/skills/mlops/inference/llama-cpp/references/quantization.md +243 -0
  1338. package/skills/mlops/inference/llama-cpp/references/server.md +150 -0
  1339. package/skills/mlops/inference/llama-cpp/references/troubleshooting.md +442 -0
  1340. package/skills/mlops/inference/obliteratus/SKILL.md +342 -0
  1341. package/skills/mlops/inference/obliteratus/references/analysis-modules.md +166 -0
  1342. package/skills/mlops/inference/obliteratus/references/methods-guide.md +141 -0
  1343. package/skills/mlops/inference/obliteratus/templates/abliteration-config.yaml +33 -0
  1344. package/skills/mlops/inference/obliteratus/templates/analysis-study.yaml +40 -0
  1345. package/skills/mlops/inference/obliteratus/templates/batch-abliteration.yaml +41 -0
  1346. package/skills/mlops/inference/vllm/SKILL.md +372 -0
  1347. package/skills/mlops/inference/vllm/references/optimization.md +226 -0
  1348. package/skills/mlops/inference/vllm/references/quantization.md +284 -0
  1349. package/skills/mlops/inference/vllm/references/server-deployment.md +255 -0
  1350. package/skills/mlops/inference/vllm/references/troubleshooting.md +447 -0
  1351. package/skills/mlops/models/DESCRIPTION.md +3 -0
  1352. package/skills/mlops/models/audiocraft/SKILL.md +568 -0
  1353. package/skills/mlops/models/audiocraft/references/advanced-usage.md +666 -0
  1354. package/skills/mlops/models/audiocraft/references/troubleshooting.md +504 -0
  1355. package/skills/mlops/models/segment-anything/SKILL.md +506 -0
  1356. package/skills/mlops/models/segment-anything/references/advanced-usage.md +589 -0
  1357. package/skills/mlops/models/segment-anything/references/troubleshooting.md +484 -0
  1358. package/skills/mlops/research/DESCRIPTION.md +3 -0
  1359. package/skills/mlops/research/dspy/SKILL.md +594 -0
  1360. package/skills/mlops/research/dspy/references/examples.md +663 -0
  1361. package/skills/mlops/research/dspy/references/modules.md +475 -0
  1362. package/skills/mlops/research/dspy/references/optimizers.md +566 -0
  1363. package/skills/mlops/training/DESCRIPTION.md +3 -0
  1364. package/skills/mlops/vector-databases/DESCRIPTION.md +3 -0
  1365. package/skills/note-taking/DESCRIPTION.md +3 -0
  1366. package/skills/note-taking/obsidian/SKILL.md +61 -0
  1367. package/skills/productivity/DESCRIPTION.md +3 -0
  1368. package/skills/productivity/airtable/SKILL.md +229 -0
  1369. package/skills/productivity/google-workspace/SKILL.md +335 -0
  1370. package/skills/productivity/google-workspace/references/gmail-search-syntax.md +63 -0
  1371. package/skills/productivity/google-workspace/scripts/_hermes_home.py +43 -0
  1372. package/skills/productivity/google-workspace/scripts/google_api.py +1221 -0
  1373. package/skills/productivity/google-workspace/scripts/gws_bridge.py +108 -0
  1374. package/skills/productivity/google-workspace/scripts/setup.py +454 -0
  1375. package/skills/productivity/linear/SKILL.md +380 -0
  1376. package/skills/productivity/linear/scripts/linear_api.py +445 -0
  1377. package/skills/productivity/maps/SKILL.md +195 -0
  1378. package/skills/productivity/maps/scripts/maps_client.py +1298 -0
  1379. package/skills/productivity/nano-pdf/SKILL.md +52 -0
  1380. package/skills/productivity/notion/SKILL.md +448 -0
  1381. package/skills/productivity/notion/references/block-types.md +112 -0
  1382. package/skills/productivity/ocr-and-documents/DESCRIPTION.md +3 -0
  1383. package/skills/productivity/ocr-and-documents/SKILL.md +172 -0
  1384. package/skills/productivity/ocr-and-documents/scripts/extract_marker.py +87 -0
  1385. package/skills/productivity/ocr-and-documents/scripts/extract_pymupdf.py +98 -0
  1386. package/skills/productivity/powerpoint/LICENSE.txt +30 -0
  1387. package/skills/productivity/powerpoint/SKILL.md +237 -0
  1388. package/skills/productivity/powerpoint/editing.md +205 -0
  1389. package/skills/productivity/powerpoint/pptxgenjs.md +420 -0
  1390. package/skills/productivity/powerpoint/scripts/__init__.py +0 -0
  1391. package/skills/productivity/powerpoint/scripts/add_slide.py +195 -0
  1392. package/skills/productivity/powerpoint/scripts/clean.py +286 -0
  1393. package/skills/productivity/powerpoint/scripts/office/helpers/__init__.py +0 -0
  1394. package/skills/productivity/powerpoint/scripts/office/helpers/merge_runs.py +199 -0
  1395. package/skills/productivity/powerpoint/scripts/office/helpers/simplify_redlines.py +197 -0
  1396. package/skills/productivity/powerpoint/scripts/office/pack.py +159 -0
  1397. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  1398. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  1399. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  1400. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  1401. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  1402. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  1403. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  1404. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  1405. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  1406. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  1407. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  1408. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  1409. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  1410. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  1411. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  1412. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  1413. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  1414. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  1415. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  1416. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  1417. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  1418. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  1419. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  1420. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  1421. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  1422. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  1423. package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  1424. package/skills/productivity/powerpoint/scripts/office/schemas/ecma/fourth-edition/opc-contentTypes.xsd +42 -0
  1425. package/skills/productivity/powerpoint/scripts/office/schemas/ecma/fourth-edition/opc-coreProperties.xsd +50 -0
  1426. package/skills/productivity/powerpoint/scripts/office/schemas/ecma/fourth-edition/opc-digSig.xsd +49 -0
  1427. package/skills/productivity/powerpoint/scripts/office/schemas/ecma/fourth-edition/opc-relationships.xsd +33 -0
  1428. package/skills/productivity/powerpoint/scripts/office/schemas/mce/mc.xsd +75 -0
  1429. package/skills/productivity/powerpoint/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  1430. package/skills/productivity/powerpoint/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  1431. package/skills/productivity/powerpoint/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  1432. package/skills/productivity/powerpoint/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  1433. package/skills/productivity/powerpoint/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  1434. package/skills/productivity/powerpoint/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  1435. package/skills/productivity/powerpoint/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  1436. package/skills/productivity/teams-meeting-pipeline/SKILL.md +116 -0
  1437. package/skills/red-teaming/godmode/SKILL.md +404 -0
  1438. package/skills/red-teaming/godmode/references/jailbreak-templates.md +128 -0
  1439. package/skills/red-teaming/godmode/references/refusal-detection.md +142 -0
  1440. package/skills/red-teaming/godmode/scripts/auto_jailbreak.py +769 -0
  1441. package/skills/red-teaming/godmode/scripts/godmode_race.py +530 -0
  1442. package/skills/red-teaming/godmode/scripts/load_godmode.py +45 -0
  1443. package/skills/red-teaming/godmode/scripts/parseltongue.py +550 -0
  1444. package/skills/red-teaming/godmode/templates/prefill-subtle.json +10 -0
  1445. package/skills/red-teaming/godmode/templates/prefill.json +18 -0
  1446. package/skills/research/DESCRIPTION.md +3 -0
  1447. package/skills/research/arxiv/SKILL.md +282 -0
  1448. package/skills/research/arxiv/scripts/search_arxiv.py +114 -0
  1449. package/skills/research/blogwatcher/SKILL.md +137 -0
  1450. package/skills/research/llm-wiki/SKILL.md +507 -0
  1451. package/skills/research/polymarket/SKILL.md +77 -0
  1452. package/skills/research/polymarket/references/api-endpoints.md +220 -0
  1453. package/skills/research/polymarket/scripts/polymarket.py +284 -0
  1454. package/skills/research/research-paper-writing/SKILL.md +2377 -0
  1455. package/skills/research/research-paper-writing/references/autoreason-methodology.md +394 -0
  1456. package/skills/research/research-paper-writing/references/checklists.md +434 -0
  1457. package/skills/research/research-paper-writing/references/citation-workflow.md +564 -0
  1458. package/skills/research/research-paper-writing/references/experiment-patterns.md +728 -0
  1459. package/skills/research/research-paper-writing/references/human-evaluation.md +476 -0
  1460. package/skills/research/research-paper-writing/references/paper-types.md +481 -0
  1461. package/skills/research/research-paper-writing/references/reviewer-guidelines.md +433 -0
  1462. package/skills/research/research-paper-writing/references/sources.md +191 -0
  1463. package/skills/research/research-paper-writing/references/writing-guide.md +474 -0
  1464. package/skills/research/research-paper-writing/templates/README.md +251 -0
  1465. package/skills/research/research-paper-writing/templates/aaai2026/README.md +534 -0
  1466. package/skills/research/research-paper-writing/templates/aaai2026/aaai2026-unified-supp.tex +144 -0
  1467. package/skills/research/research-paper-writing/templates/aaai2026/aaai2026-unified-template.tex +952 -0
  1468. package/skills/research/research-paper-writing/templates/aaai2026/aaai2026.bib +111 -0
  1469. package/skills/research/research-paper-writing/templates/aaai2026/aaai2026.bst +1493 -0
  1470. package/skills/research/research-paper-writing/templates/aaai2026/aaai2026.sty +315 -0
  1471. package/skills/research/research-paper-writing/templates/acl/README.md +50 -0
  1472. package/skills/research/research-paper-writing/templates/acl/acl.sty +312 -0
  1473. package/skills/research/research-paper-writing/templates/acl/acl_latex.tex +377 -0
  1474. package/skills/research/research-paper-writing/templates/acl/acl_lualatex.tex +101 -0
  1475. package/skills/research/research-paper-writing/templates/acl/acl_natbib.bst +1940 -0
  1476. package/skills/research/research-paper-writing/templates/acl/anthology.bib.txt +26 -0
  1477. package/skills/research/research-paper-writing/templates/acl/custom.bib +70 -0
  1478. package/skills/research/research-paper-writing/templates/acl/formatting.md +326 -0
  1479. package/skills/research/research-paper-writing/templates/colm2025/README.md +3 -0
  1480. package/skills/research/research-paper-writing/templates/colm2025/colm2025_conference.bib +11 -0
  1481. package/skills/research/research-paper-writing/templates/colm2025/colm2025_conference.bst +1440 -0
  1482. package/skills/research/research-paper-writing/templates/colm2025/colm2025_conference.pdf +0 -0
  1483. package/skills/research/research-paper-writing/templates/colm2025/colm2025_conference.sty +218 -0
  1484. package/skills/research/research-paper-writing/templates/colm2025/colm2025_conference.tex +305 -0
  1485. package/skills/research/research-paper-writing/templates/colm2025/fancyhdr.sty +485 -0
  1486. package/skills/research/research-paper-writing/templates/colm2025/math_commands.tex +508 -0
  1487. package/skills/research/research-paper-writing/templates/colm2025/natbib.sty +1246 -0
  1488. package/skills/research/research-paper-writing/templates/iclr2026/fancyhdr.sty +485 -0
  1489. package/skills/research/research-paper-writing/templates/iclr2026/iclr2026_conference.bib +24 -0
  1490. package/skills/research/research-paper-writing/templates/iclr2026/iclr2026_conference.bst +1440 -0
  1491. package/skills/research/research-paper-writing/templates/iclr2026/iclr2026_conference.pdf +0 -0
  1492. package/skills/research/research-paper-writing/templates/iclr2026/iclr2026_conference.sty +246 -0
  1493. package/skills/research/research-paper-writing/templates/iclr2026/iclr2026_conference.tex +414 -0
  1494. package/skills/research/research-paper-writing/templates/iclr2026/math_commands.tex +508 -0
  1495. package/skills/research/research-paper-writing/templates/iclr2026/natbib.sty +1246 -0
  1496. package/skills/research/research-paper-writing/templates/icml2026/algorithm.sty +79 -0
  1497. package/skills/research/research-paper-writing/templates/icml2026/algorithmic.sty +201 -0
  1498. package/skills/research/research-paper-writing/templates/icml2026/example_paper.bib +75 -0
  1499. package/skills/research/research-paper-writing/templates/icml2026/example_paper.pdf +0 -0
  1500. package/skills/research/research-paper-writing/templates/icml2026/example_paper.tex +662 -0
  1501. package/skills/research/research-paper-writing/templates/icml2026/fancyhdr.sty +864 -0
  1502. package/skills/research/research-paper-writing/templates/icml2026/icml2026.bst +1443 -0
  1503. package/skills/research/research-paper-writing/templates/icml2026/icml2026.sty +767 -0
  1504. package/skills/research/research-paper-writing/templates/icml2026/icml_numpapers.pdf +0 -0
  1505. package/skills/research/research-paper-writing/templates/neurips2025/Makefile +36 -0
  1506. package/skills/research/research-paper-writing/templates/neurips2025/extra_pkgs.tex +53 -0
  1507. package/skills/research/research-paper-writing/templates/neurips2025/main.tex +38 -0
  1508. package/skills/research/research-paper-writing/templates/neurips2025/neurips.sty +382 -0
  1509. package/skills/smart-home/DESCRIPTION.md +3 -0
  1510. package/skills/smart-home/openhue/SKILL.md +109 -0
  1511. package/skills/social-media/DESCRIPTION.md +3 -0
  1512. package/skills/social-media/xurl/SKILL.md +414 -0
  1513. package/skills/software-development/debugging-hermes-tui-commands/SKILL.md +152 -0
  1514. package/skills/software-development/hermes-agent-skill-authoring/SKILL.md +165 -0
  1515. package/skills/software-development/node-inspect-debugger/SKILL.md +319 -0
  1516. package/skills/software-development/plan/SKILL.md +58 -0
  1517. package/skills/software-development/python-debugpy/SKILL.md +375 -0
  1518. package/skills/software-development/requesting-code-review/SKILL.md +280 -0
  1519. package/skills/software-development/spike/SKILL.md +197 -0
  1520. package/skills/software-development/subagent-driven-development/SKILL.md +352 -0
  1521. package/skills/software-development/subagent-driven-development/references/context-budget-discipline.md +53 -0
  1522. package/skills/software-development/subagent-driven-development/references/gates-taxonomy.md +93 -0
  1523. package/skills/software-development/systematic-debugging/SKILL.md +367 -0
  1524. package/skills/software-development/test-driven-development/SKILL.md +343 -0
  1525. package/skills/software-development/writing-plans/SKILL.md +297 -0
  1526. package/skills/yuanbao/SKILL.md +108 -0
  1527. package/tools/__init__.py +25 -0
  1528. package/tools/__pycache__/__init__.cpython-312.pyc +0 -0
  1529. package/tools/__pycache__/approval.cpython-312.pyc +0 -0
  1530. package/tools/__pycache__/binary_extensions.cpython-312.pyc +0 -0
  1531. package/tools/__pycache__/browser_camofox.cpython-312.pyc +0 -0
  1532. package/tools/__pycache__/browser_camofox_state.cpython-312.pyc +0 -0
  1533. package/tools/__pycache__/browser_cdp_tool.cpython-312.pyc +0 -0
  1534. package/tools/__pycache__/browser_dialog_tool.cpython-312.pyc +0 -0
  1535. package/tools/__pycache__/browser_supervisor.cpython-312.pyc +0 -0
  1536. package/tools/__pycache__/browser_tool.cpython-312.pyc +0 -0
  1537. package/tools/__pycache__/budget_config.cpython-312.pyc +0 -0
  1538. package/tools/__pycache__/checkpoint_manager.cpython-312.pyc +0 -0
  1539. package/tools/__pycache__/clarify_gateway.cpython-312.pyc +0 -0
  1540. package/tools/__pycache__/clarify_tool.cpython-312.pyc +0 -0
  1541. package/tools/__pycache__/code_execution_tool.cpython-312.pyc +0 -0
  1542. package/tools/__pycache__/computer_use_tool.cpython-312.pyc +0 -0
  1543. package/tools/__pycache__/cronjob_tools.cpython-312.pyc +0 -0
  1544. package/tools/__pycache__/debug_helpers.cpython-312.pyc +0 -0
  1545. package/tools/__pycache__/delegate_tool.cpython-312.pyc +0 -0
  1546. package/tools/__pycache__/discord_tool.cpython-312.pyc +0 -0
  1547. package/tools/__pycache__/feishu_doc_tool.cpython-312.pyc +0 -0
  1548. package/tools/__pycache__/feishu_drive_tool.cpython-312.pyc +0 -0
  1549. package/tools/__pycache__/file_operations.cpython-312.pyc +0 -0
  1550. package/tools/__pycache__/file_state.cpython-312.pyc +0 -0
  1551. package/tools/__pycache__/file_tools.cpython-312.pyc +0 -0
  1552. package/tools/__pycache__/homeassistant_tool.cpython-312.pyc +0 -0
  1553. package/tools/__pycache__/image_generation_tool.cpython-312.pyc +0 -0
  1554. package/tools/__pycache__/interrupt.cpython-312.pyc +0 -0
  1555. package/tools/__pycache__/kanban_tools.cpython-312.pyc +0 -0
  1556. package/tools/__pycache__/lazy_deps.cpython-312.pyc +0 -0
  1557. package/tools/__pycache__/managed_tool_gateway.cpython-312.pyc +0 -0
  1558. package/tools/__pycache__/mcp_tool.cpython-312.pyc +0 -0
  1559. package/tools/__pycache__/memory_tool.cpython-312.pyc +0 -0
  1560. package/tools/__pycache__/mixture_of_agents_tool.cpython-312.pyc +0 -0
  1561. package/tools/__pycache__/openrouter_client.cpython-312.pyc +0 -0
  1562. package/tools/__pycache__/process_registry.cpython-312.pyc +0 -0
  1563. package/tools/__pycache__/registry.cpython-312.pyc +0 -0
  1564. package/tools/__pycache__/schema_sanitizer.cpython-312.pyc +0 -0
  1565. package/tools/__pycache__/send_message_tool.cpython-312.pyc +0 -0
  1566. package/tools/__pycache__/session_search_tool.cpython-312.pyc +0 -0
  1567. package/tools/__pycache__/skill_manager_tool.cpython-312.pyc +0 -0
  1568. package/tools/__pycache__/skill_provenance.cpython-312.pyc +0 -0
  1569. package/tools/__pycache__/skill_usage.cpython-312.pyc +0 -0
  1570. package/tools/__pycache__/skills_guard.cpython-312.pyc +0 -0
  1571. package/tools/__pycache__/skills_sync.cpython-312.pyc +0 -0
  1572. package/tools/__pycache__/skills_tool.cpython-312.pyc +0 -0
  1573. package/tools/__pycache__/slash_confirm.cpython-312.pyc +0 -0
  1574. package/tools/__pycache__/terminal_tool.cpython-312.pyc +0 -0
  1575. package/tools/__pycache__/tirith_security.cpython-312.pyc +0 -0
  1576. package/tools/__pycache__/todo_tool.cpython-312.pyc +0 -0
  1577. package/tools/__pycache__/tool_backend_helpers.cpython-312.pyc +0 -0
  1578. package/tools/__pycache__/tool_result_storage.cpython-312.pyc +0 -0
  1579. package/tools/__pycache__/tts_tool.cpython-312.pyc +0 -0
  1580. package/tools/__pycache__/url_safety.cpython-312.pyc +0 -0
  1581. package/tools/__pycache__/video_generation_tool.cpython-312.pyc +0 -0
  1582. package/tools/__pycache__/vision_tools.cpython-312.pyc +0 -0
  1583. package/tools/__pycache__/voice_mode.cpython-312.pyc +0 -0
  1584. package/tools/__pycache__/web_tools.cpython-312.pyc +0 -0
  1585. package/tools/__pycache__/website_policy.cpython-312.pyc +0 -0
  1586. package/tools/__pycache__/x_search_tool.cpython-312.pyc +0 -0
  1587. package/tools/__pycache__/xai_http.cpython-312.pyc +0 -0
  1588. package/tools/__pycache__/yuanbao_tools.cpython-312.pyc +0 -0
  1589. package/tools/ansi_strip.py +44 -0
  1590. package/tools/approval.py +1392 -0
  1591. package/tools/binary_extensions.py +42 -0
  1592. package/tools/browser_camofox.py +700 -0
  1593. package/tools/browser_camofox_state.py +48 -0
  1594. package/tools/browser_cdp_tool.py +569 -0
  1595. package/tools/browser_dialog_tool.py +148 -0
  1596. package/tools/browser_providers/__init__.py +10 -0
  1597. package/tools/browser_providers/__pycache__/__init__.cpython-312.pyc +0 -0
  1598. package/tools/browser_providers/__pycache__/base.cpython-312.pyc +0 -0
  1599. package/tools/browser_providers/__pycache__/browser_use.cpython-312.pyc +0 -0
  1600. package/tools/browser_providers/__pycache__/browserbase.cpython-312.pyc +0 -0
  1601. package/tools/browser_providers/__pycache__/firecrawl.cpython-312.pyc +0 -0
  1602. package/tools/browser_providers/base.py +59 -0
  1603. package/tools/browser_providers/browser_use.py +225 -0
  1604. package/tools/browser_providers/browserbase.py +222 -0
  1605. package/tools/browser_providers/firecrawl.py +112 -0
  1606. package/tools/browser_supervisor.py +1457 -0
  1607. package/tools/browser_tool.py +3676 -0
  1608. package/tools/budget_config.py +51 -0
  1609. package/tools/checkpoint_manager.py +1639 -0
  1610. package/tools/clarify_gateway.py +278 -0
  1611. package/tools/clarify_tool.py +141 -0
  1612. package/tools/code_execution_tool.py +1782 -0
  1613. package/tools/computer_use/__init__.py +43 -0
  1614. package/tools/computer_use/__pycache__/__init__.cpython-312.pyc +0 -0
  1615. package/tools/computer_use/__pycache__/backend.cpython-312.pyc +0 -0
  1616. package/tools/computer_use/__pycache__/schema.cpython-312.pyc +0 -0
  1617. package/tools/computer_use/__pycache__/tool.cpython-312.pyc +0 -0
  1618. package/tools/computer_use/backend.py +150 -0
  1619. package/tools/computer_use/cua_backend.py +682 -0
  1620. package/tools/computer_use/schema.py +191 -0
  1621. package/tools/computer_use/tool.py +521 -0
  1622. package/tools/computer_use_tool.py +39 -0
  1623. package/tools/credential_files.py +437 -0
  1624. package/tools/cronjob_tools.py +719 -0
  1625. package/tools/debug_helpers.py +106 -0
  1626. package/tools/delegate_tool.py +2797 -0
  1627. package/tools/discord_tool.py +959 -0
  1628. package/tools/env_passthrough.py +145 -0
  1629. package/tools/environments/__init__.py +14 -0
  1630. package/tools/environments/__pycache__/__init__.cpython-312.pyc +0 -0
  1631. package/tools/environments/__pycache__/base.cpython-312.pyc +0 -0
  1632. package/tools/environments/__pycache__/docker.cpython-312.pyc +0 -0
  1633. package/tools/environments/__pycache__/file_sync.cpython-312.pyc +0 -0
  1634. package/tools/environments/__pycache__/local.cpython-312.pyc +0 -0
  1635. package/tools/environments/__pycache__/managed_modal.cpython-312.pyc +0 -0
  1636. package/tools/environments/__pycache__/modal.cpython-312.pyc +0 -0
  1637. package/tools/environments/__pycache__/modal_utils.cpython-312.pyc +0 -0
  1638. package/tools/environments/__pycache__/singularity.cpython-312.pyc +0 -0
  1639. package/tools/environments/__pycache__/ssh.cpython-312.pyc +0 -0
  1640. package/tools/environments/base.py +844 -0
  1641. package/tools/environments/daytona.py +270 -0
  1642. package/tools/environments/docker.py +656 -0
  1643. package/tools/environments/file_sync.py +400 -0
  1644. package/tools/environments/local.py +658 -0
  1645. package/tools/environments/managed_modal.py +282 -0
  1646. package/tools/environments/modal.py +479 -0
  1647. package/tools/environments/modal_utils.py +199 -0
  1648. package/tools/environments/singularity.py +263 -0
  1649. package/tools/environments/ssh.py +295 -0
  1650. package/tools/environments/vercel_sandbox.py +655 -0
  1651. package/tools/feishu_doc_tool.py +138 -0
  1652. package/tools/feishu_drive_tool.py +431 -0
  1653. package/tools/file_operations.py +1825 -0
  1654. package/tools/file_state.py +332 -0
  1655. package/tools/file_tools.py +1172 -0
  1656. package/tools/fuzzy_match.py +703 -0
  1657. package/tools/homeassistant_tool.py +513 -0
  1658. package/tools/image_generation_tool.py +1098 -0
  1659. package/tools/interrupt.py +98 -0
  1660. package/tools/kanban_tools.py +1139 -0
  1661. package/tools/lazy_deps.py +608 -0
  1662. package/tools/managed_tool_gateway.py +168 -0
  1663. package/tools/mcp_oauth.py +633 -0
  1664. package/tools/mcp_oauth_manager.py +607 -0
  1665. package/tools/mcp_tool.py +3483 -0
  1666. package/tools/memory_tool.py +584 -0
  1667. package/tools/microsoft_graph_auth.py +245 -0
  1668. package/tools/microsoft_graph_client.py +408 -0
  1669. package/tools/mixture_of_agents_tool.py +542 -0
  1670. package/tools/neutts_samples/jo.txt +1 -0
  1671. package/tools/neutts_samples/jo.wav +0 -0
  1672. package/tools/neutts_synth.py +104 -0
  1673. package/tools/openrouter_client.py +33 -0
  1674. package/tools/osv_check.py +155 -0
  1675. package/tools/patch_parser.py +592 -0
  1676. package/tools/path_security.py +43 -0
  1677. package/tools/process_registry.py +1534 -0
  1678. package/tools/registry.py +589 -0
  1679. package/tools/schema_sanitizer.py +370 -0
  1680. package/tools/send_message_tool.py +1900 -0
  1681. package/tools/session_search_tool.py +613 -0
  1682. package/tools/skill_manager_tool.py +932 -0
  1683. package/tools/skill_provenance.py +78 -0
  1684. package/tools/skill_usage.py +610 -0
  1685. package/tools/skills_guard.py +932 -0
  1686. package/tools/skills_hub.py +3263 -0
  1687. package/tools/skills_sync.py +432 -0
  1688. package/tools/skills_tool.py +1569 -0
  1689. package/tools/slash_confirm.py +167 -0
  1690. package/tools/terminal_tool.py +2376 -0
  1691. package/tools/tirith_security.py +775 -0
  1692. package/tools/todo_tool.py +277 -0
  1693. package/tools/tool_backend_helpers.py +144 -0
  1694. package/tools/tool_output_limits.py +92 -0
  1695. package/tools/tool_result_storage.py +232 -0
  1696. package/tools/transcription_tools.py +936 -0
  1697. package/tools/tts_tool.py +2285 -0
  1698. package/tools/url_safety.py +330 -0
  1699. package/tools/video_generation_tool.py +561 -0
  1700. package/tools/vision_tools.py +1422 -0
  1701. package/tools/voice_mode.py +1019 -0
  1702. package/tools/web_tools.py +1551 -0
  1703. package/tools/website_policy.py +283 -0
  1704. package/tools/x_search_tool.py +424 -0
  1705. package/tools/xai_http.py +83 -0
  1706. package/tools/yuanbao_tools.py +736 -0
  1707. package/toolset_distributions.py +364 -0
  1708. package/toolsets.py +866 -0
  1709. package/trajectory_compressor.py +1509 -0
  1710. package/tui_gateway/__init__.py +0 -0
  1711. package/tui_gateway/entry.py +251 -0
  1712. package/tui_gateway/event_publisher.py +126 -0
  1713. package/tui_gateway/render.py +49 -0
  1714. package/tui_gateway/server.py +6623 -0
  1715. package/tui_gateway/slash_worker.py +76 -0
  1716. package/tui_gateway/transport.py +219 -0
  1717. package/tui_gateway/ws.py +178 -0
  1718. package/utils.py +361 -0
@@ -0,0 +1,2285 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Text-to-Speech Tool Module
4
+
5
+ Built-in TTS providers:
6
+ - Edge TTS (default, free, no API key): Microsoft Edge neural voices
7
+ - ElevenLabs (premium): High-quality voices, needs ELEVENLABS_API_KEY
8
+ - OpenAI TTS: Good quality, needs OPENAI_API_KEY
9
+ - MiniMax TTS: High-quality with voice cloning, needs MINIMAX_API_KEY
10
+ - Mistral (Voxtral TTS): Multilingual, native Opus, needs MISTRAL_API_KEY
11
+ - Google Gemini TTS: Controllable, 30 prebuilt voices, needs GEMINI_API_KEY
12
+ - xAI TTS: Grok voices, uses xAI Grok OAuth credentials or XAI_API_KEY
13
+ - NeuTTS (local, free, no API key): On-device TTS via neutts
14
+ - KittenTTS (local, free, no API key): On-device 25MB model
15
+ - Piper (local, free, no API key): OHF-Voice/piper1-gpl neural VITS, 44 languages
16
+
17
+ Custom command providers:
18
+ - Users can declare any number of named providers with ``type: command``
19
+ under ``tts.providers.<name>`` in ``~/.hermes/config.yaml``. Hermes
20
+ writes the input text to a temp file and runs the configured shell
21
+ command, which must produce the audio file at the expected path.
22
+ See the Local Command section of ``website/docs/user-guide/features/tts.md``.
23
+
24
+ Output formats:
25
+ - Opus (.ogg) for Telegram voice bubbles (requires ffmpeg for Edge TTS)
26
+ - MP3 (.mp3) for everything else (CLI, Discord, WhatsApp)
27
+
28
+ Configuration is loaded from ~/.hermes/config.yaml under the 'tts:' key.
29
+ The user chooses the provider and voice; the model just sends text.
30
+
31
+ Usage:
32
+ from tools.tts_tool import text_to_speech_tool, check_tts_requirements
33
+
34
+ result = text_to_speech_tool(text="Hello world")
35
+ """
36
+
37
+ import asyncio
38
+ import base64
39
+ import datetime
40
+ import json
41
+ import logging
42
+ import os
43
+ import queue
44
+ import re
45
+ import shlex
46
+ import shutil
47
+ import signal
48
+ import subprocess
49
+ import tempfile
50
+ import threading
51
+ import uuid
52
+ from pathlib import Path
53
+ from typing import Callable, Dict, Any, Optional
54
+ from urllib.parse import urljoin
55
+
56
+ from calvyn_constants import display_hermes_home
57
+
58
+ logger = logging.getLogger(__name__)
59
+ def get_env_value(name, default=None):
60
+ """Read env values through the live config module.
61
+
62
+ Tests may monkeypatch and later restore ``hermes_cli.config.get_env_value``
63
+ before this module is imported. Resolve the helper at call time so TTS does
64
+ not keep a stale imported function for the rest of the test process.
65
+ """
66
+ try:
67
+ from hermes_cli.config import get_env_value as _get_env_value
68
+ except ImportError:
69
+ return os.getenv(name, default)
70
+ value = _get_env_value(name)
71
+ return default if value is None else value
72
+ from tools.managed_tool_gateway import resolve_managed_tool_gateway
73
+ from tools.tool_backend_helpers import managed_nous_tools_enabled, prefers_gateway, resolve_openai_audio_api_key
74
+ from tools.xai_http import hermes_xai_user_agent
75
+
76
+ # ---------------------------------------------------------------------------
77
+ # Lazy imports -- providers are imported only when actually used to avoid
78
+ # crashing in headless environments (SSH, Docker, WSL, no PortAudio).
79
+ # ---------------------------------------------------------------------------
80
+
81
+ def _import_edge_tts():
82
+ """Lazy import edge_tts. Returns the module or raises ImportError."""
83
+ try:
84
+ from tools.lazy_deps import ensure as _lazy_ensure
85
+ _lazy_ensure("tts.edge", prompt=False)
86
+ except ImportError:
87
+ pass
88
+ except Exception as e:
89
+ raise ImportError(str(e))
90
+ import edge_tts
91
+ return edge_tts
92
+
93
+ def _import_elevenlabs():
94
+ """Lazy import ElevenLabs client. Returns the class or raises ImportError.
95
+
96
+ Calls :func:`tools.lazy_deps.ensure` first so the SDK gets installed on
97
+ demand if the user picked ElevenLabs as their TTS provider but never ran
98
+ the post-setup hook (e.g. enabled it by editing config.yaml directly).
99
+ Raises ``ImportError`` on lazy-install failure so existing callers'
100
+ error-handling paths keep working.
101
+ """
102
+ try:
103
+ from tools.lazy_deps import FeatureUnavailable, ensure
104
+ ensure("tts.elevenlabs", prompt=False)
105
+ except ImportError:
106
+ # lazy_deps module itself missing — fall through to the raw import
107
+ # so older code paths still get a clean ImportError.
108
+ pass
109
+ except Exception as e: # FeatureUnavailable or any unexpected error
110
+ raise ImportError(str(e))
111
+ from elevenlabs.client import ElevenLabs
112
+ return ElevenLabs
113
+
114
+ def _import_openai_client():
115
+ """Lazy import OpenAI client. Returns the class or raises ImportError."""
116
+ from openai import OpenAI as OpenAIClient
117
+ return OpenAIClient
118
+
119
+ def _import_mistral_client():
120
+ """Lazy import Mistral client. Returns the class or raises ImportError."""
121
+ from mistralai.client import Mistral
122
+ return Mistral
123
+
124
+ def _import_sounddevice():
125
+ """Lazy import sounddevice. Returns the module or raises ImportError/OSError."""
126
+ import sounddevice as sd
127
+ return sd
128
+
129
+
130
+ def _import_kittentts():
131
+ """Lazy import KittenTTS. Returns the class or raises ImportError."""
132
+ from kittentts import KittenTTS
133
+ return KittenTTS
134
+
135
+
136
+ def _import_piper():
137
+ """Lazy import Piper. Returns the PiperVoice class or raises ImportError.
138
+
139
+ Piper is an optional, fully-local neural TTS engine (Home Assistant /
140
+ Open Home Foundation). ``pip install piper-tts`` provides cross-platform
141
+ wheels (Linux / macOS / Windows, x86_64 + ARM64) with embedded espeak-ng.
142
+ Voice models (.onnx + .onnx.json) are downloaded on first use.
143
+ """
144
+ from piper import PiperVoice
145
+ return PiperVoice
146
+
147
+
148
+ # ===========================================================================
149
+ # Defaults
150
+ # ===========================================================================
151
+ DEFAULT_PROVIDER = "edge"
152
+ DEFAULT_EDGE_VOICE = "en-US-AriaNeural"
153
+ DEFAULT_ELEVENLABS_VOICE_ID = "pNInz6obpgDQGcFmaJgB" # Adam
154
+ DEFAULT_ELEVENLABS_MODEL_ID = "eleven_multilingual_v2"
155
+ DEFAULT_ELEVENLABS_STREAMING_MODEL_ID = "eleven_flash_v2_5"
156
+ DEFAULT_OPENAI_MODEL = "gpt-4o-mini-tts"
157
+ DEFAULT_KITTENTTS_MODEL = "KittenML/kitten-tts-nano-0.8-int8" # 25MB
158
+ DEFAULT_KITTENTTS_VOICE = "Jasper"
159
+ DEFAULT_PIPER_VOICE = "en_US-lessac-medium" # balanced size/quality
160
+ DEFAULT_OPENAI_VOICE = "alloy"
161
+ DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1"
162
+ DEFAULT_MINIMAX_MODEL = "speech-02-hd"
163
+ DEFAULT_MINIMAX_VOICE_ID = "English_expressive_narrator"
164
+ DEFAULT_MINIMAX_BASE_URL = "https://api.minimax.io/v1/t2a_v2"
165
+ DEFAULT_MISTRAL_TTS_MODEL = "voxtral-mini-tts-2603"
166
+ DEFAULT_MISTRAL_TTS_VOICE_ID = "c69964a6-ab8b-4f8a-9465-ec0925096ec8" # Paul - Neutral
167
+ DEFAULT_XAI_VOICE_ID = "eve"
168
+ DEFAULT_XAI_LANGUAGE = "en"
169
+ DEFAULT_XAI_SAMPLE_RATE = 24000
170
+ DEFAULT_XAI_BIT_RATE = 128000
171
+ DEFAULT_XAI_BASE_URL = "https://api.x.ai/v1"
172
+ DEFAULT_GEMINI_TTS_MODEL = "gemini-2.5-flash-preview-tts"
173
+ DEFAULT_GEMINI_TTS_VOICE = "Kore"
174
+ DEFAULT_GEMINI_TTS_BASE_URL = "https://generativelanguage.googleapis.com/v1beta"
175
+ # PCM output specs for Gemini TTS (fixed by the API)
176
+ GEMINI_TTS_SAMPLE_RATE = 24000
177
+ GEMINI_TTS_CHANNELS = 1
178
+ GEMINI_TTS_SAMPLE_WIDTH = 2 # 16-bit PCM (L16)
179
+
180
+ def _get_default_output_dir() -> str:
181
+ from calvyn_constants import get_hermes_dir
182
+ return str(get_hermes_dir("cache/audio", "audio_cache"))
183
+
184
+ DEFAULT_OUTPUT_DIR = _get_default_output_dir()
185
+
186
+ # ---------------------------------------------------------------------------
187
+ # Per-provider input-character limits (from official provider docs).
188
+ # A single global cap was wrong: OpenAI is 4096, xAI is 15k, MiniMax is 10k,
189
+ # ElevenLabs is model-dependent (5k / 10k / 30k / 40k), Gemini caps at ~8k
190
+ # input tokens. Users can override any of these via
191
+ # ``tts.<provider>.max_text_length`` in config.yaml.
192
+ # ---------------------------------------------------------------------------
193
+ PROVIDER_MAX_TEXT_LENGTH: Dict[str, int] = {
194
+ "edge": 5000, # edge-tts practical sync limit
195
+ "openai": 4096, # https://platform.openai.com/docs/guides/text-to-speech
196
+ "xai": 15000, # https://docs.x.ai/developers/model-capabilities/audio/text-to-speech
197
+ "minimax": 10000, # https://platform.minimax.io/docs/api-reference/speech-t2a-http (sync)
198
+ "mistral": 4000, # conservative; no published per-request cap
199
+ "gemini": 5000, # Gemini TTS caps at ~8k input tokens / ~655s audio
200
+ "elevenlabs": 10000, # fallback when model-aware lookup can't resolve (multilingual_v2)
201
+ "neutts": 2000, # local model, quality falls off on long text
202
+ "kittentts": 2000, # local 25MB model
203
+ "piper": 5000, # local VITS model, phoneme-based; practical cap
204
+ }
205
+
206
+ # ElevenLabs caps vary by model_id. https://elevenlabs.io/docs/overview/models
207
+ ELEVENLABS_MODEL_MAX_TEXT_LENGTH: Dict[str, int] = {
208
+ "eleven_v3": 5000,
209
+ "eleven_ttv_v3": 5000,
210
+ "eleven_multilingual_v2": 10000,
211
+ "eleven_multilingual_v1": 10000,
212
+ "eleven_english_sts_v2": 10000,
213
+ "eleven_english_sts_v1": 10000,
214
+ "eleven_flash_v2": 30000,
215
+ "eleven_flash_v2_5": 40000,
216
+ }
217
+
218
+ # Final fallback when provider isn't recognised at all.
219
+ FALLBACK_MAX_TEXT_LENGTH = 4000
220
+
221
+ # Back-compat alias. Prefer ``_resolve_max_text_length()`` for new code.
222
+ MAX_TEXT_LENGTH = FALLBACK_MAX_TEXT_LENGTH
223
+
224
+
225
+ def _resolve_max_text_length(
226
+ provider: Optional[str],
227
+ tts_config: Optional[Dict[str, Any]] = None,
228
+ ) -> int:
229
+ """Return the input-character cap for *provider*.
230
+
231
+ Resolution order:
232
+ 1. ``tts.<provider>.max_text_length`` (user override in config.yaml)
233
+ 2. ``tts.providers.<provider>.max_text_length`` for user-declared
234
+ command providers
235
+ 3. ElevenLabs model-aware table (keyed on configured ``model_id``)
236
+ 4. ``PROVIDER_MAX_TEXT_LENGTH`` default
237
+ 5. ``DEFAULT_COMMAND_TTS_MAX_TEXT_LENGTH`` when the provider is a
238
+ command-type user provider without an explicit cap
239
+ 6. ``FALLBACK_MAX_TEXT_LENGTH`` (4000)
240
+
241
+ Non-positive or non-integer overrides fall through to the default so a
242
+ broken config can't accidentally disable truncation entirely.
243
+ """
244
+ if not provider:
245
+ return FALLBACK_MAX_TEXT_LENGTH
246
+ key = provider.lower().strip()
247
+ cfg = tts_config or {}
248
+
249
+ # Built-in-style override at tts.<provider>.max_text_length wins first,
250
+ # matching historical behavior.
251
+ prov_cfg = cfg.get(key) if isinstance(cfg.get(key), dict) else {}
252
+ override = prov_cfg.get("max_text_length") if prov_cfg else None
253
+ if isinstance(override, bool):
254
+ override = None
255
+ if isinstance(override, int) and override > 0:
256
+ return override
257
+
258
+ if key == "elevenlabs":
259
+ model_id = (prov_cfg or {}).get("model_id") or DEFAULT_ELEVENLABS_MODEL_ID
260
+ mapped = ELEVENLABS_MODEL_MAX_TEXT_LENGTH.get(str(model_id).strip())
261
+ if mapped:
262
+ return mapped
263
+
264
+ if key in PROVIDER_MAX_TEXT_LENGTH:
265
+ return PROVIDER_MAX_TEXT_LENGTH[key]
266
+
267
+ # User-declared command provider (under tts.providers.<name>)
268
+ if key not in BUILTIN_TTS_PROVIDERS:
269
+ named = _get_named_provider_config(cfg, key)
270
+ if _is_command_provider_config(named):
271
+ named_override = named.get("max_text_length")
272
+ if isinstance(named_override, bool):
273
+ named_override = None
274
+ if isinstance(named_override, int) and named_override > 0:
275
+ return named_override
276
+ return DEFAULT_COMMAND_TTS_MAX_TEXT_LENGTH
277
+
278
+ return FALLBACK_MAX_TEXT_LENGTH
279
+
280
+
281
+ # ===========================================================================
282
+ # Config loader -- reads tts: section from ~/.hermes/config.yaml
283
+ # ===========================================================================
284
+ def _load_tts_config() -> Dict[str, Any]:
285
+ """
286
+ Load TTS configuration from ~/.hermes/config.yaml.
287
+
288
+ Returns a dict with provider settings. Falls back to defaults
289
+ for any missing fields.
290
+ """
291
+ try:
292
+ from hermes_cli.config import load_config
293
+ config = load_config()
294
+ return config.get("tts", {})
295
+ except ImportError:
296
+ logger.debug("hermes_cli.config not available, using default TTS config")
297
+ return {}
298
+ except Exception as e:
299
+ logger.warning("Failed to load TTS config: %s", e, exc_info=True)
300
+ return {}
301
+
302
+
303
+ def _get_provider(tts_config: Dict[str, Any]) -> str:
304
+ """Get the configured TTS provider name."""
305
+ return (tts_config.get("provider") or DEFAULT_PROVIDER).lower().strip()
306
+
307
+
308
+ # ===========================================================================
309
+ # Custom command providers (type: command under tts.providers.<name>)
310
+ # ===========================================================================
311
+ #
312
+ # Users can declare any number of command-type providers alongside the
313
+ # built-ins so they can plug any local CLI (Piper, VoxCPM, Kokoro CLIs,
314
+ # custom voice-cloning scripts, etc.) into Hermes without any Python code
315
+ # changes. The config shape is::
316
+ #
317
+ # tts:
318
+ # provider: piper-en
319
+ # providers:
320
+ # piper-en:
321
+ # type: command
322
+ # command: "piper -m ~/model.onnx -f {output_path} < {input_path}"
323
+ # output_format: wav
324
+ #
325
+ # Hermes writes the input text to a temp UTF-8 file, runs the command with
326
+ # placeholder substitution, and reads the audio file the command wrote to
327
+ # ``{output_path}``. Supported placeholders: ``{input_path}``,
328
+ # ``{text_path}`` (alias for input_path), ``{output_path}``, ``{format}``,
329
+ # ``{voice}``, ``{model}``, ``{speed}``. Use ``{{`` / ``}}`` for literal braces.
330
+ #
331
+ # Built-in provider names always win over an entry with the same name under
332
+ # ``tts.providers``, so user config can't silently shadow ``edge`` etc.
333
+ #
334
+ # Placeholder values are shell-quoted for their surrounding context
335
+ # (bare / single / double quote), so paths with spaces work transparently.
336
+
337
+ # Built-in provider names. Any ``tts.provider`` value NOT in this set is
338
+ # interpreted as a reference to ``tts.providers.<name>``.
339
+ BUILTIN_TTS_PROVIDERS = frozenset({
340
+ "edge",
341
+ "elevenlabs",
342
+ "openai",
343
+ "minimax",
344
+ "xai",
345
+ "mistral",
346
+ "gemini",
347
+ "neutts",
348
+ "kittentts",
349
+ "piper",
350
+ })
351
+
352
+ DEFAULT_COMMAND_TTS_TIMEOUT_SECONDS = 120
353
+ DEFAULT_COMMAND_TTS_OUTPUT_FORMAT = "mp3"
354
+ COMMAND_TTS_OUTPUT_FORMATS = frozenset({"mp3", "wav", "ogg", "flac"})
355
+ DEFAULT_COMMAND_TTS_MAX_TEXT_LENGTH = 5000
356
+
357
+
358
+ def _get_provider_section(tts_config: Dict[str, Any], name: str) -> Dict[str, Any]:
359
+ """Return a provider config block if it's a dict, else an empty dict."""
360
+ if not isinstance(tts_config, dict):
361
+ return {}
362
+ section = tts_config.get(name)
363
+ return section if isinstance(section, dict) else {}
364
+
365
+
366
+ def _get_named_provider_config(
367
+ tts_config: Dict[str, Any],
368
+ name: str,
369
+ ) -> Dict[str, Any]:
370
+ """Return the config dict for a user-declared provider.
371
+
372
+ Looks up ``tts.providers.<name>`` first (the canonical location), and
373
+ falls back to ``tts.<name>`` so users who followed the built-in layout
374
+ still work. Returns an empty dict when the provider is not declared.
375
+ """
376
+ providers = _get_provider_section(tts_config, "providers")
377
+ section = providers.get(name) if isinstance(providers, dict) else None
378
+ if isinstance(section, dict):
379
+ return section
380
+ # Back-compat: allow ``tts.<name>`` for user-declared providers too,
381
+ # but only when the name is not a built-in (so a user's ``tts.openai``
382
+ # block still means the OpenAI provider, not a custom command).
383
+ if name.lower() not in BUILTIN_TTS_PROVIDERS:
384
+ legacy = _get_provider_section(tts_config, name)
385
+ if legacy:
386
+ return legacy
387
+ return {}
388
+
389
+
390
+ def _is_command_provider_config(config: Dict[str, Any]) -> bool:
391
+ """Return True when *config* declares a command-type provider."""
392
+ if not isinstance(config, dict):
393
+ return False
394
+ ptype = str(config.get("type") or "").strip().lower()
395
+ if ptype and ptype != "command":
396
+ return False
397
+ command = config.get("command")
398
+ return isinstance(command, str) and bool(command.strip())
399
+
400
+
401
+ def _resolve_command_provider_config(
402
+ provider: str,
403
+ tts_config: Dict[str, Any],
404
+ ) -> Optional[Dict[str, Any]]:
405
+ """Return the provider config if *provider* resolves to a command type.
406
+
407
+ Built-in provider names are rejected (they have native handlers).
408
+ Returns None when the name is a built-in, unknown, or not a command
409
+ type.
410
+ """
411
+ if not provider:
412
+ return None
413
+ key = provider.lower().strip()
414
+ if key in BUILTIN_TTS_PROVIDERS:
415
+ return None
416
+ config = _get_named_provider_config(tts_config, key)
417
+ if _is_command_provider_config(config):
418
+ return config
419
+ return None
420
+
421
+
422
+ def _iter_command_providers(tts_config: Dict[str, Any]):
423
+ """Yield (name, config) pairs for every declared command-type provider."""
424
+ if not isinstance(tts_config, dict):
425
+ return
426
+ providers = _get_provider_section(tts_config, "providers")
427
+ for name, cfg in (providers or {}).items():
428
+ if isinstance(name, str) and name.lower() not in BUILTIN_TTS_PROVIDERS:
429
+ if _is_command_provider_config(cfg):
430
+ yield name, cfg
431
+
432
+
433
+ def _get_command_tts_timeout(config: Dict[str, Any]) -> float:
434
+ """Return timeout in seconds, falling back when invalid."""
435
+ raw = config.get("timeout", config.get("timeout_seconds", DEFAULT_COMMAND_TTS_TIMEOUT_SECONDS))
436
+ try:
437
+ value = float(raw)
438
+ except (TypeError, ValueError):
439
+ return float(DEFAULT_COMMAND_TTS_TIMEOUT_SECONDS)
440
+ if value <= 0:
441
+ return float(DEFAULT_COMMAND_TTS_TIMEOUT_SECONDS)
442
+ return value
443
+
444
+
445
+ def _get_command_tts_output_format(
446
+ config: Dict[str, Any],
447
+ output_path: Optional[str] = None,
448
+ ) -> str:
449
+ """Return the validated output format (mp3/wav/ogg/flac)."""
450
+ if output_path:
451
+ suffix = Path(output_path).suffix.lower().strip().lstrip(".")
452
+ if suffix in COMMAND_TTS_OUTPUT_FORMATS:
453
+ return suffix
454
+ raw = (
455
+ config.get("format")
456
+ or config.get("output_format")
457
+ or DEFAULT_COMMAND_TTS_OUTPUT_FORMAT
458
+ )
459
+ fmt = str(raw).lower().strip().lstrip(".")
460
+ return fmt if fmt in COMMAND_TTS_OUTPUT_FORMATS else DEFAULT_COMMAND_TTS_OUTPUT_FORMAT
461
+
462
+
463
+ def _is_command_tts_voice_compatible(config: Dict[str, Any]) -> bool:
464
+ """Return True only when the user explicitly opted in to voice delivery."""
465
+ value = config.get("voice_compatible", False)
466
+ if isinstance(value, str):
467
+ return value.strip().lower() in {"1", "true", "yes", "on"}
468
+ return bool(value)
469
+
470
+
471
+ def _shell_quote_context(command_template: str, position: int) -> Optional[str]:
472
+ """Return the shell quote character active right before *position*.
473
+
474
+ Returns ``"'"`` / ``'"'`` when inside a single- / double-quoted region
475
+ of the template, ``None`` for bare context.
476
+ """
477
+ quote: Optional[str] = None
478
+ escaped = False
479
+ i = 0
480
+ while i < position:
481
+ char = command_template[i]
482
+ if quote == "'":
483
+ if char == "'":
484
+ quote = None
485
+ elif quote == '"':
486
+ if escaped:
487
+ escaped = False
488
+ elif char == "\\":
489
+ escaped = True
490
+ elif char == '"':
491
+ quote = None
492
+ elif char == "'":
493
+ quote = "'"
494
+ elif char == '"':
495
+ quote = '"'
496
+ elif char == "\\":
497
+ i += 1
498
+ i += 1
499
+ return quote
500
+
501
+
502
+ def _quote_command_tts_placeholder(value: str, quote_context: Optional[str]) -> str:
503
+ """Quote a placeholder value for its position in a shell command template."""
504
+ if quote_context == "'":
505
+ return value.replace("'", r"'\''")
506
+ if quote_context == '"':
507
+ return (
508
+ value
509
+ .replace("\\", "\\\\")
510
+ .replace('"', r'\"')
511
+ .replace("$", r"\$")
512
+ .replace("`", r"\`")
513
+ )
514
+ if os.name == "nt":
515
+ return subprocess.list2cmdline([value])
516
+ return shlex.quote(value)
517
+
518
+
519
+ def _render_command_tts_template(
520
+ command_template: str,
521
+ placeholders: Dict[str, str],
522
+ ) -> str:
523
+ """Replace supported placeholders while preserving ``{{`` / ``}}``."""
524
+ names = "|".join(re.escape(name) for name in placeholders)
525
+ pattern = re.compile(
526
+ rf"(?<!\$)(?:\{{\{{(?P<double>{names})\}}\}}|\{{(?P<single>{names})\}})"
527
+ )
528
+ replacements: list[tuple[str, str]] = []
529
+
530
+ def replace_match(match: re.Match[str]) -> str:
531
+ name = match.group("double") or match.group("single")
532
+ token = f"__HERMES_TTS_PLACEHOLDER_{len(replacements)}__"
533
+ replacements.append((
534
+ token,
535
+ _quote_command_tts_placeholder(
536
+ placeholders[name],
537
+ _shell_quote_context(command_template, match.start()),
538
+ ),
539
+ ))
540
+ return token
541
+
542
+ rendered = pattern.sub(replace_match, command_template)
543
+ rendered = rendered.replace("{{", "{").replace("}}", "}")
544
+ for token, value in replacements:
545
+ rendered = rendered.replace(token, value)
546
+ return rendered
547
+
548
+
549
+ def _terminate_command_tts_process_tree(proc: subprocess.Popen) -> None:
550
+ """Best-effort termination of a shell process and all of its children."""
551
+ if proc.poll() is not None:
552
+ return
553
+
554
+ if os.name == "nt":
555
+ try:
556
+ subprocess.run(
557
+ ["taskkill", "/F", "/T", "/PID", str(proc.pid)],
558
+ stdout=subprocess.DEVNULL,
559
+ stderr=subprocess.DEVNULL,
560
+ timeout=5,
561
+ )
562
+ except Exception:
563
+ proc.kill()
564
+ return
565
+
566
+ import psutil
567
+ try:
568
+ parent = psutil.Process(proc.pid)
569
+ for child in parent.children(recursive=True):
570
+ try:
571
+ child.terminate()
572
+ except psutil.NoSuchProcess:
573
+ pass
574
+ parent.terminate()
575
+ except psutil.NoSuchProcess:
576
+ return
577
+ except Exception:
578
+ proc.terminate()
579
+
580
+ try:
581
+ proc.wait(timeout=2)
582
+ return
583
+ except subprocess.TimeoutExpired:
584
+ pass
585
+
586
+ try:
587
+ parent = psutil.Process(proc.pid)
588
+ for child in parent.children(recursive=True):
589
+ try:
590
+ child.kill()
591
+ except psutil.NoSuchProcess:
592
+ pass
593
+ parent.kill()
594
+ except psutil.NoSuchProcess:
595
+ return
596
+ except Exception:
597
+ proc.kill()
598
+
599
+
600
+ def _run_command_tts(command: str, timeout: float) -> subprocess.CompletedProcess:
601
+ """Run a command-provider shell command with process-tree timeout cleanup."""
602
+ popen_kwargs: Dict[str, Any] = {
603
+ "shell": True,
604
+ "stdout": subprocess.PIPE,
605
+ "stderr": subprocess.PIPE,
606
+ "text": True,
607
+ }
608
+ if os.name == "nt":
609
+ popen_kwargs["creationflags"] = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)
610
+ else:
611
+ popen_kwargs["start_new_session"] = True
612
+
613
+ proc = subprocess.Popen(command, **popen_kwargs)
614
+ try:
615
+ stdout, stderr = proc.communicate(timeout=timeout)
616
+ except subprocess.TimeoutExpired as exc:
617
+ _terminate_command_tts_process_tree(proc)
618
+ try:
619
+ stdout, stderr = proc.communicate(timeout=1)
620
+ except Exception:
621
+ stdout = getattr(exc, "output", None)
622
+ stderr = getattr(exc, "stderr", None)
623
+ raise subprocess.TimeoutExpired(
624
+ command,
625
+ timeout,
626
+ output=stdout,
627
+ stderr=stderr,
628
+ ) from exc
629
+
630
+ if proc.returncode:
631
+ raise subprocess.CalledProcessError(
632
+ proc.returncode,
633
+ command,
634
+ output=stdout,
635
+ stderr=stderr,
636
+ )
637
+ return subprocess.CompletedProcess(command, proc.returncode, stdout, stderr)
638
+
639
+
640
+ def _configured_command_tts_output_path(path: Path, config: Dict[str, Any]) -> Path:
641
+ """Return an output path whose extension matches the provider's output_format."""
642
+ fmt = _get_command_tts_output_format(config)
643
+ return path.with_suffix(f".{fmt}")
644
+
645
+
646
+ def _generate_command_tts(
647
+ text: str,
648
+ output_path: str,
649
+ provider_name: str,
650
+ config: Dict[str, Any],
651
+ tts_config: Dict[str, Any],
652
+ ) -> str:
653
+ """Generate speech by running a user-configured shell command.
654
+
655
+ Returns the absolute path of the audio file the command wrote.
656
+ Raises ``ValueError`` when the provider config is invalid, and
657
+ ``RuntimeError`` for timeouts / non-zero exits / empty output.
658
+ """
659
+ command_template = str(config.get("command") or "").strip()
660
+ if not command_template:
661
+ raise ValueError(
662
+ f"tts.providers.{provider_name}.command is not configured"
663
+ )
664
+
665
+ output = Path(output_path).expanduser()
666
+ output.parent.mkdir(parents=True, exist_ok=True)
667
+ if output.exists():
668
+ output.unlink()
669
+
670
+ timeout = _get_command_tts_timeout(config)
671
+ output_format = _get_command_tts_output_format(config, str(output))
672
+ speed = config.get("speed", tts_config.get("speed", ""))
673
+
674
+ with tempfile.TemporaryDirectory() as tmpdir:
675
+ text_path = Path(tmpdir) / "input.txt"
676
+ text_path.write_text(text, encoding="utf-8")
677
+
678
+ placeholders = {
679
+ "input_path": str(text_path),
680
+ "text_path": str(text_path),
681
+ "output_path": str(output),
682
+ "format": output_format,
683
+ "voice": str(config.get("voice", "")),
684
+ "model": str(config.get("model", "")),
685
+ "speed": str(speed),
686
+ }
687
+ command = _render_command_tts_template(command_template, placeholders)
688
+
689
+ try:
690
+ _run_command_tts(command, timeout)
691
+ except subprocess.TimeoutExpired as exc:
692
+ raise RuntimeError(
693
+ f"TTS provider '{provider_name}' timed out after {timeout:g}s"
694
+ ) from exc
695
+ except subprocess.CalledProcessError as exc:
696
+ detail_parts = []
697
+ if exc.stderr:
698
+ detail_parts.append(f"stderr: {exc.stderr.strip()}")
699
+ if exc.stdout:
700
+ detail_parts.append(f"stdout: {exc.stdout.strip()}")
701
+ detail = "; ".join(detail_parts) or "no command output"
702
+ raise RuntimeError(
703
+ f"TTS provider '{provider_name}' exited with code "
704
+ f"{exc.returncode}: {detail}"
705
+ ) from exc
706
+
707
+ if not output.exists() or output.stat().st_size <= 0:
708
+ raise RuntimeError(
709
+ f"TTS provider '{provider_name}' produced no output at {output}"
710
+ )
711
+ return str(output)
712
+
713
+
714
+ def _has_any_command_tts_provider(tts_config: Optional[Dict[str, Any]] = None) -> bool:
715
+ """Return True when any command-type TTS provider is configured."""
716
+ if tts_config is None:
717
+ tts_config = _load_tts_config()
718
+ for _name, _cfg in _iter_command_providers(tts_config):
719
+ return True
720
+ return False
721
+
722
+
723
+ # ===========================================================================
724
+ # ffmpeg Opus conversion (Edge TTS MP3 -> OGG Opus for Telegram)
725
+ # ===========================================================================
726
+ def _has_ffmpeg() -> bool:
727
+ """Check if ffmpeg is available on the system."""
728
+ return shutil.which("ffmpeg") is not None
729
+
730
+
731
+ def _convert_to_opus(mp3_path: str) -> Optional[str]:
732
+ """
733
+ Convert an MP3 file to OGG Opus format for Telegram voice bubbles.
734
+
735
+ Args:
736
+ mp3_path: Path to the input MP3 file.
737
+
738
+ Returns:
739
+ Path to the .ogg file, or None if conversion fails.
740
+ """
741
+ if not _has_ffmpeg():
742
+ return None
743
+
744
+ ogg_path = mp3_path.rsplit(".", 1)[0] + ".ogg"
745
+ try:
746
+ result = subprocess.run(
747
+ ["ffmpeg", "-i", mp3_path, "-acodec", "libopus",
748
+ "-ac", "1", "-b:a", "64k", "-vbr", "off", ogg_path, "-y"],
749
+ capture_output=True, timeout=30,
750
+ )
751
+ if result.returncode != 0:
752
+ logger.warning("ffmpeg conversion failed with return code %d: %s",
753
+ result.returncode, result.stderr.decode('utf-8', errors='ignore')[:200])
754
+ return None
755
+ if os.path.exists(ogg_path) and os.path.getsize(ogg_path) > 0:
756
+ return ogg_path
757
+ except subprocess.TimeoutExpired:
758
+ logger.warning("ffmpeg OGG conversion timed out after 30s")
759
+ except FileNotFoundError:
760
+ logger.warning("ffmpeg not found in PATH")
761
+ except Exception as e:
762
+ logger.warning("ffmpeg OGG conversion failed: %s", e, exc_info=True)
763
+ return None
764
+
765
+
766
+ # ===========================================================================
767
+ # Provider: Edge TTS (free)
768
+ # ===========================================================================
769
+ async def _generate_edge_tts(text: str, output_path: str, tts_config: Dict[str, Any]) -> str:
770
+ """
771
+ Generate audio using Edge TTS.
772
+
773
+ Args:
774
+ text: Text to convert.
775
+ output_path: Where to save the MP3 file.
776
+ tts_config: TTS config dict.
777
+
778
+ Returns:
779
+ Path to the saved audio file.
780
+ """
781
+ _edge_tts = _import_edge_tts()
782
+ edge_config = tts_config.get("edge", {})
783
+ voice = edge_config.get("voice", DEFAULT_EDGE_VOICE)
784
+ speed = float(edge_config.get("speed", tts_config.get("speed", 1.0)))
785
+
786
+ kwargs = {"voice": voice}
787
+ if speed != 1.0:
788
+ pct = round((speed - 1.0) * 100)
789
+ kwargs["rate"] = f"{pct:+d}%"
790
+
791
+ communicate = _edge_tts.Communicate(text, **kwargs)
792
+ await communicate.save(output_path)
793
+ return output_path
794
+
795
+
796
+ # ===========================================================================
797
+ # Provider: ElevenLabs (premium)
798
+ # ===========================================================================
799
+ def _generate_elevenlabs(text: str, output_path: str, tts_config: Dict[str, Any]) -> str:
800
+ """
801
+ Generate audio using ElevenLabs.
802
+
803
+ Args:
804
+ text: Text to convert.
805
+ output_path: Where to save the audio file.
806
+ tts_config: TTS config dict.
807
+
808
+ Returns:
809
+ Path to the saved audio file.
810
+ """
811
+ api_key = (get_env_value("ELEVENLABS_API_KEY") or "")
812
+ if not api_key:
813
+ raise ValueError("ELEVENLABS_API_KEY not set. Get one at https://elevenlabs.io/")
814
+
815
+ el_config = tts_config.get("elevenlabs", {})
816
+ voice_id = el_config.get("voice_id", DEFAULT_ELEVENLABS_VOICE_ID)
817
+ model_id = el_config.get("model_id", DEFAULT_ELEVENLABS_MODEL_ID)
818
+
819
+ # Determine output format based on file extension
820
+ if output_path.endswith(".ogg"):
821
+ output_format = "opus_48000_64"
822
+ else:
823
+ output_format = "mp3_44100_128"
824
+
825
+ ElevenLabs = _import_elevenlabs()
826
+ client = ElevenLabs(api_key=api_key)
827
+ audio_generator = client.text_to_speech.convert(
828
+ text=text,
829
+ voice_id=voice_id,
830
+ model_id=model_id,
831
+ output_format=output_format,
832
+ )
833
+
834
+ # audio_generator yields chunks -- write them all
835
+ with open(output_path, "wb") as f:
836
+ for chunk in audio_generator:
837
+ f.write(chunk)
838
+
839
+ return output_path
840
+
841
+
842
+ # ===========================================================================
843
+ # Provider: OpenAI TTS
844
+ # ===========================================================================
845
+ def _generate_openai_tts(text: str, output_path: str, tts_config: Dict[str, Any]) -> str:
846
+ """
847
+ Generate audio using OpenAI TTS.
848
+
849
+ Args:
850
+ text: Text to convert.
851
+ output_path: Where to save the audio file.
852
+ tts_config: TTS config dict.
853
+
854
+ Returns:
855
+ Path to the saved audio file.
856
+ """
857
+ api_key, base_url = _resolve_openai_audio_client_config()
858
+
859
+ oai_config = tts_config.get("openai", {})
860
+ model = oai_config.get("model", DEFAULT_OPENAI_MODEL)
861
+ voice = oai_config.get("voice", DEFAULT_OPENAI_VOICE)
862
+ base_url = oai_config.get("base_url", base_url)
863
+ speed = float(oai_config.get("speed", tts_config.get("speed", 1.0)))
864
+
865
+ # Determine response format from extension
866
+ if output_path.endswith(".ogg"):
867
+ response_format = "opus"
868
+ else:
869
+ response_format = "mp3"
870
+
871
+ OpenAIClient = _import_openai_client()
872
+ client = OpenAIClient(api_key=api_key, base_url=base_url)
873
+ try:
874
+ create_kwargs = {
875
+ "model": model,
876
+ "voice": voice,
877
+ "input": text,
878
+ "response_format": response_format,
879
+ "extra_headers": {"x-idempotency-key": str(uuid.uuid4())},
880
+ }
881
+ if speed != 1.0:
882
+ create_kwargs["speed"] = max(0.25, min(4.0, speed))
883
+ response = client.audio.speech.create(**create_kwargs)
884
+
885
+ response.stream_to_file(output_path)
886
+ return output_path
887
+ finally:
888
+ close = getattr(client, "close", None)
889
+ if callable(close):
890
+ close()
891
+
892
+
893
+ # ===========================================================================
894
+ # Provider: xAI TTS
895
+ # ===========================================================================
896
+ def _generate_xai_tts(text: str, output_path: str, tts_config: Dict[str, Any]) -> str:
897
+ """
898
+ Generate audio using xAI TTS.
899
+
900
+ xAI exposes a dedicated /v1/tts endpoint instead of the OpenAI audio.speech
901
+ API shape, so this is implemented as a separate backend.
902
+ """
903
+ import requests
904
+
905
+ from tools.xai_http import resolve_xai_http_credentials
906
+
907
+ creds = resolve_xai_http_credentials()
908
+ api_key = str(creds.get("api_key") or "").strip()
909
+ if not api_key:
910
+ raise ValueError("No xAI credentials found. Configure xAI OAuth in `hermes model` or set XAI_API_KEY.")
911
+
912
+ xai_config = tts_config.get("xai", {})
913
+ voice_id = str(xai_config.get("voice_id", DEFAULT_XAI_VOICE_ID)).strip() or DEFAULT_XAI_VOICE_ID
914
+ language = str(xai_config.get("language", DEFAULT_XAI_LANGUAGE)).strip() or DEFAULT_XAI_LANGUAGE
915
+ sample_rate = int(xai_config.get("sample_rate", DEFAULT_XAI_SAMPLE_RATE))
916
+ bit_rate = int(xai_config.get("bit_rate", DEFAULT_XAI_BIT_RATE))
917
+ base_url = str(
918
+ xai_config.get("base_url")
919
+ or creds.get("base_url")
920
+ or get_env_value("XAI_BASE_URL")
921
+ or DEFAULT_XAI_BASE_URL
922
+ ).strip().rstrip("/")
923
+
924
+ # Match the documented minimal POST /v1/tts shape by default. Only send
925
+ # output_format when Hermes actually needs a non-default format/override.
926
+ codec = "wav" if output_path.endswith(".wav") else "mp3"
927
+ payload: Dict[str, Any] = {
928
+ "text": text,
929
+ "voice_id": voice_id,
930
+ "language": language,
931
+ }
932
+ if (
933
+ codec != "mp3"
934
+ or sample_rate != DEFAULT_XAI_SAMPLE_RATE
935
+ or (codec == "mp3" and bit_rate != DEFAULT_XAI_BIT_RATE)
936
+ ):
937
+ output_format: Dict[str, Any] = {"codec": codec}
938
+ if sample_rate:
939
+ output_format["sample_rate"] = sample_rate
940
+ if codec == "mp3" and bit_rate:
941
+ output_format["bit_rate"] = bit_rate
942
+ payload["output_format"] = output_format
943
+
944
+ response = requests.post(
945
+ f"{base_url}/tts",
946
+ headers={
947
+ "Authorization": f"Bearer {api_key}",
948
+ "Content-Type": "application/json",
949
+ "User-Agent": hermes_xai_user_agent(),
950
+ },
951
+ json=payload,
952
+ timeout=60,
953
+ )
954
+ response.raise_for_status()
955
+
956
+ with open(output_path, "wb") as f:
957
+ f.write(response.content)
958
+
959
+ return output_path
960
+
961
+
962
+ # ===========================================================================
963
+ # Provider: MiniMax TTS
964
+ # ===========================================================================
965
+ def _generate_minimax_tts(text: str, output_path: str, tts_config: Dict[str, Any]) -> str:
966
+ """
967
+ Generate audio using MiniMax TTS API.
968
+
969
+ Supports two endpoints:
970
+ - v1/text_to_speech: simple payload, returns raw audio (Content-Type: audio/mpeg)
971
+ - v1/t2a_v2: nested voice_setting/audio_setting, returns JSON with hex-encoded audio
972
+
973
+ Args:
974
+ text: Text to convert (max 10,000 characters).
975
+ output_path: Where to save the audio file.
976
+ tts_config: TTS config dict.
977
+
978
+ Returns:
979
+ Path to the saved audio file.
980
+ """
981
+ import requests
982
+
983
+ api_key = (get_env_value("MINIMAX_API_KEY") or "")
984
+ if not api_key:
985
+ raise ValueError("MINIMAX_API_KEY not set. Get one at https://platform.minimax.io/")
986
+
987
+ mm_config = tts_config.get("minimax", {})
988
+ model = mm_config.get("model", DEFAULT_MINIMAX_MODEL)
989
+ voice_id = mm_config.get("voice_id", DEFAULT_MINIMAX_VOICE_ID)
990
+ base_url = mm_config.get("base_url", DEFAULT_MINIMAX_BASE_URL)
991
+ speed = mm_config.get("speed", 1.0)
992
+ vol = mm_config.get("vol", 1.0)
993
+ pitch = mm_config.get("pitch", 0)
994
+ emotion = mm_config.get("emotion", "neutral")
995
+ sample_rate = mm_config.get("sample_rate", 32000)
996
+ bitrate = mm_config.get("bitrate", 128000)
997
+
998
+ # MiniMax accounts scope TTS requests by GroupId. When present, the docs
999
+ # show it as a ?GroupId=<id> query param on the t2a_v2 URL. Accept it
1000
+ # from config or from the MINIMAX_GROUP_ID env var; only attach when the
1001
+ # URL doesn't already carry one.
1002
+ group_id = (
1003
+ str(mm_config.get("group_id") or "").strip()
1004
+ or (get_env_value("MINIMAX_GROUP_ID") or "").strip()
1005
+ )
1006
+ if group_id and "GroupId=" not in base_url:
1007
+ sep = "&" if "?" in base_url else "?"
1008
+ base_url = f"{base_url}{sep}GroupId={group_id}"
1009
+
1010
+ headers = {
1011
+ "Content-Type": "application/json",
1012
+ "Authorization": f"Bearer {api_key}",
1013
+ }
1014
+
1015
+ # Detect endpoint from URL
1016
+ is_t2a_v2 = "t2a_v2" in base_url
1017
+
1018
+ if is_t2a_v2:
1019
+ # t2a_v2 endpoint: nested voice_setting/audio_setting structure
1020
+ payload = {
1021
+ "model": model,
1022
+ "text": text,
1023
+ "voice_setting": {
1024
+ "voice_id": voice_id,
1025
+ "speed": speed,
1026
+ "vol": vol,
1027
+ "pitch": pitch,
1028
+ "emotion": emotion,
1029
+ },
1030
+ "audio_setting": {
1031
+ "sample_rate": sample_rate,
1032
+ "bitrate": bitrate,
1033
+ "format": "mp3",
1034
+ "channel": 1,
1035
+ },
1036
+ }
1037
+ else:
1038
+ # text_to_speech endpoint: flat payload
1039
+ payload = {
1040
+ "model": model,
1041
+ "text": text,
1042
+ "voice_id": voice_id,
1043
+ }
1044
+
1045
+ response = requests.post(base_url, json=payload, headers=headers, timeout=60)
1046
+
1047
+ if is_t2a_v2:
1048
+ # t2a_v2 returns JSON with hex-encoded audio
1049
+ result = response.json()
1050
+ base_resp = result.get("base_resp", {})
1051
+ status_code = base_resp.get("status_code", -1)
1052
+
1053
+ if status_code != 0:
1054
+ status_msg = base_resp.get("status_msg", "unknown error")
1055
+ raise RuntimeError(f"MiniMax TTS API error (code {status_code}): {status_msg}")
1056
+
1057
+ hex_audio = result.get("data", {}).get("audio", "")
1058
+ if not hex_audio:
1059
+ raise RuntimeError("MiniMax TTS returned empty audio data")
1060
+
1061
+ audio_bytes = bytes.fromhex(hex_audio)
1062
+ with open(output_path, "wb") as f:
1063
+ f.write(audio_bytes)
1064
+ return output_path
1065
+
1066
+ else:
1067
+ # text_to_speech returns raw audio directly
1068
+ content_type = response.headers.get("Content-Type", "")
1069
+
1070
+ if "audio/" in content_type:
1071
+ with open(output_path, "wb") as f:
1072
+ f.write(response.content)
1073
+ return output_path
1074
+
1075
+ # Fallback: try parsing as JSON
1076
+ try:
1077
+ result = response.json()
1078
+ base_resp = result.get("base_resp", {})
1079
+ status_code = base_resp.get("status_code", -1)
1080
+ if status_code != 0:
1081
+ status_msg = base_resp.get("status_msg", "unknown error")
1082
+ raise RuntimeError(f"MiniMax TTS API error (code {status_code}): {status_msg}")
1083
+ except Exception:
1084
+ response.raise_for_status()
1085
+ raise RuntimeError(
1086
+ f"MiniMax TTS returned unexpected Content-Type '{content_type}' "
1087
+ f"({len(response.content)} bytes)"
1088
+ )
1089
+
1090
+ raise RuntimeError("MiniMax TTS returned no audio data")
1091
+
1092
+
1093
+ # ===========================================================================
1094
+ # Provider: Mistral (Voxtral TTS)
1095
+ # ===========================================================================
1096
+ def _generate_mistral_tts(text: str, output_path: str, tts_config: Dict[str, Any]) -> str:
1097
+ """Generate audio using Mistral Voxtral TTS API.
1098
+
1099
+ The API returns base64-encoded audio; this function decodes it
1100
+ and writes the raw bytes to *output_path*.
1101
+ Supports native Opus output for Telegram voice bubbles.
1102
+ """
1103
+ api_key = (get_env_value("MISTRAL_API_KEY") or "")
1104
+ if not api_key:
1105
+ raise ValueError("MISTRAL_API_KEY not set. Get one at https://console.mistral.ai/")
1106
+
1107
+ mi_config = tts_config.get("mistral", {})
1108
+ model = mi_config.get("model", DEFAULT_MISTRAL_TTS_MODEL)
1109
+ voice_id = mi_config.get("voice_id") or DEFAULT_MISTRAL_TTS_VOICE_ID
1110
+
1111
+ if output_path.endswith(".ogg"):
1112
+ response_format = "opus"
1113
+ elif output_path.endswith(".wav"):
1114
+ response_format = "wav"
1115
+ elif output_path.endswith(".flac"):
1116
+ response_format = "flac"
1117
+ else:
1118
+ response_format = "mp3"
1119
+
1120
+ Mistral = _import_mistral_client()
1121
+ try:
1122
+ with Mistral(api_key=api_key) as client:
1123
+ response = client.audio.speech.complete(
1124
+ model=model,
1125
+ input=text,
1126
+ voice_id=voice_id,
1127
+ response_format=response_format,
1128
+ )
1129
+ audio_bytes = base64.b64decode(response.audio_data)
1130
+ except ValueError:
1131
+ raise
1132
+ except Exception as e:
1133
+ logger.error("Mistral TTS failed: %s", e, exc_info=True)
1134
+ raise RuntimeError(f"Mistral TTS failed: {type(e).__name__}") from e
1135
+
1136
+ with open(output_path, "wb") as f:
1137
+ f.write(audio_bytes)
1138
+
1139
+ return output_path
1140
+
1141
+
1142
+ # ===========================================================================
1143
+ # Provider: Google Gemini TTS
1144
+ # ===========================================================================
1145
+ def _wrap_pcm_as_wav(
1146
+ pcm_bytes: bytes,
1147
+ sample_rate: int = GEMINI_TTS_SAMPLE_RATE,
1148
+ channels: int = GEMINI_TTS_CHANNELS,
1149
+ sample_width: int = GEMINI_TTS_SAMPLE_WIDTH,
1150
+ ) -> bytes:
1151
+ """Wrap raw signed-little-endian PCM with a standard WAV RIFF header.
1152
+
1153
+ Gemini TTS returns audio/L16;codec=pcm;rate=24000 -- raw PCM samples with
1154
+ no container. We add a minimal WAV header so the file is playable and
1155
+ ffmpeg can re-encode it to MP3/Opus downstream.
1156
+ """
1157
+ import struct
1158
+
1159
+ byte_rate = sample_rate * channels * sample_width
1160
+ block_align = channels * sample_width
1161
+ data_size = len(pcm_bytes)
1162
+ fmt_chunk = struct.pack(
1163
+ "<4sIHHIIHH",
1164
+ b"fmt ",
1165
+ 16, # fmt chunk size (PCM)
1166
+ 1, # audio format (PCM)
1167
+ channels,
1168
+ sample_rate,
1169
+ byte_rate,
1170
+ block_align,
1171
+ sample_width * 8,
1172
+ )
1173
+ data_chunk_header = struct.pack("<4sI", b"data", data_size)
1174
+ riff_size = 4 + len(fmt_chunk) + len(data_chunk_header) + data_size
1175
+ riff_header = struct.pack("<4sI4s", b"RIFF", riff_size, b"WAVE")
1176
+ return riff_header + fmt_chunk + data_chunk_header + pcm_bytes
1177
+
1178
+
1179
+ def _generate_gemini_tts(text: str, output_path: str, tts_config: Dict[str, Any]) -> str:
1180
+ """Generate audio using Google Gemini TTS.
1181
+
1182
+ Gemini's generateContent endpoint with responseModalities=["AUDIO"] returns
1183
+ raw 24kHz mono 16-bit PCM (L16) as base64. We wrap it with a WAV RIFF
1184
+ header to produce a playable file, then ffmpeg-convert to MP3 / Opus if
1185
+ the caller requested those formats (same pattern as NeuTTS).
1186
+
1187
+ Args:
1188
+ text: Text to convert (prompt-style; supports inline direction like
1189
+ "Say cheerfully:" and audio tags like [whispers]).
1190
+ output_path: Where to save the audio file (.wav, .mp3, or .ogg).
1191
+ tts_config: TTS config dict.
1192
+
1193
+ Returns:
1194
+ Path to the saved audio file.
1195
+ """
1196
+ import requests
1197
+
1198
+ api_key = (get_env_value("GEMINI_API_KEY") or get_env_value("GOOGLE_API_KEY") or "").strip()
1199
+ if not api_key:
1200
+ raise ValueError(
1201
+ "GEMINI_API_KEY not set. Get one at https://aistudio.google.com/app/apikey"
1202
+ )
1203
+
1204
+ gemini_config = tts_config.get("gemini", {})
1205
+ model = str(gemini_config.get("model", DEFAULT_GEMINI_TTS_MODEL)).strip() or DEFAULT_GEMINI_TTS_MODEL
1206
+ voice = str(gemini_config.get("voice", DEFAULT_GEMINI_TTS_VOICE)).strip() or DEFAULT_GEMINI_TTS_VOICE
1207
+ base_url = str(
1208
+ gemini_config.get("base_url")
1209
+ or get_env_value("GEMINI_BASE_URL")
1210
+ or DEFAULT_GEMINI_TTS_BASE_URL
1211
+ ).strip().rstrip("/")
1212
+
1213
+ payload: Dict[str, Any] = {
1214
+ "contents": [{"parts": [{"text": text}]}],
1215
+ "generationConfig": {
1216
+ "responseModalities": ["AUDIO"],
1217
+ "speechConfig": {
1218
+ "voiceConfig": {
1219
+ "prebuiltVoiceConfig": {"voiceName": voice},
1220
+ },
1221
+ },
1222
+ },
1223
+ }
1224
+
1225
+ endpoint = f"{base_url}/models/{model}:generateContent"
1226
+ response = requests.post(
1227
+ endpoint,
1228
+ params={"key": api_key},
1229
+ headers={"Content-Type": "application/json"},
1230
+ json=payload,
1231
+ timeout=60,
1232
+ )
1233
+ if response.status_code != 200:
1234
+ # Surface the API error message when present
1235
+ try:
1236
+ err = response.json().get("error", {})
1237
+ detail = err.get("message") or response.text[:300]
1238
+ except Exception:
1239
+ detail = response.text[:300]
1240
+ raise RuntimeError(
1241
+ f"Gemini TTS API error (HTTP {response.status_code}): {detail}"
1242
+ )
1243
+
1244
+ try:
1245
+ data = response.json()
1246
+ parts = data["candidates"][0]["content"]["parts"]
1247
+ audio_part = next((p for p in parts if "inlineData" in p or "inline_data" in p), None)
1248
+ if audio_part is None:
1249
+ raise RuntimeError("Gemini TTS response contained no audio data")
1250
+ inline = audio_part.get("inlineData") or audio_part.get("inline_data") or {}
1251
+ audio_b64 = inline.get("data", "")
1252
+ except (KeyError, IndexError, TypeError) as e:
1253
+ raise RuntimeError(f"Gemini TTS response was malformed: {e}") from e
1254
+
1255
+ if not audio_b64:
1256
+ raise RuntimeError("Gemini TTS returned empty audio data")
1257
+
1258
+ pcm_bytes = base64.b64decode(audio_b64)
1259
+ wav_bytes = _wrap_pcm_as_wav(pcm_bytes)
1260
+
1261
+ # Fast path: caller wants WAV directly, just write.
1262
+ if output_path.lower().endswith(".wav"):
1263
+ with open(output_path, "wb") as f:
1264
+ f.write(wav_bytes)
1265
+ return output_path
1266
+
1267
+ # Otherwise write WAV to a temp file and ffmpeg-convert to the target
1268
+ # format (.mp3 or .ogg). If ffmpeg is missing, fall back to renaming the
1269
+ # WAV -- this matches the NeuTTS behavior and keeps the tool usable on
1270
+ # systems without ffmpeg (audio still plays, just with a misleading
1271
+ # extension).
1272
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp:
1273
+ tmp.write(wav_bytes)
1274
+ wav_path = tmp.name
1275
+
1276
+ try:
1277
+ ffmpeg = shutil.which("ffmpeg")
1278
+ if ffmpeg:
1279
+ # For .ogg output, force libopus encoding (Telegram voice bubbles
1280
+ # require Opus specifically; ffmpeg's default for .ogg is Vorbis).
1281
+ if output_path.lower().endswith(".ogg"):
1282
+ cmd = [
1283
+ ffmpeg, "-i", wav_path,
1284
+ "-acodec", "libopus", "-ac", "1",
1285
+ "-b:a", "64k", "-vbr", "off",
1286
+ "-y", "-loglevel", "error",
1287
+ output_path,
1288
+ ]
1289
+ else:
1290
+ cmd = [ffmpeg, "-i", wav_path, "-y", "-loglevel", "error", output_path]
1291
+ result = subprocess.run(cmd, capture_output=True, timeout=30)
1292
+ if result.returncode != 0:
1293
+ stderr = result.stderr.decode("utf-8", errors="ignore")[:300]
1294
+ raise RuntimeError(f"ffmpeg conversion failed: {stderr}")
1295
+ else:
1296
+ logger.warning(
1297
+ "ffmpeg not found; writing raw WAV to %s (extension may be misleading)",
1298
+ output_path,
1299
+ )
1300
+ shutil.copyfile(wav_path, output_path)
1301
+ finally:
1302
+ try:
1303
+ os.remove(wav_path)
1304
+ except OSError:
1305
+ pass
1306
+
1307
+ return output_path
1308
+
1309
+
1310
+ # ===========================================================================
1311
+ # NeuTTS (local, on-device TTS via neutts_cli)
1312
+ # ===========================================================================
1313
+
1314
+ def _check_neutts_available() -> bool:
1315
+ """Check if the neutts engine is importable (installed locally)."""
1316
+ try:
1317
+ import importlib.util
1318
+ return importlib.util.find_spec("neutts") is not None
1319
+ except Exception:
1320
+ return False
1321
+
1322
+
1323
+ def _check_kittentts_available() -> bool:
1324
+ """Check if the kittentts engine is importable (installed locally)."""
1325
+ try:
1326
+ import importlib.util
1327
+ return importlib.util.find_spec("kittentts") is not None
1328
+ except Exception:
1329
+ return False
1330
+
1331
+
1332
+ def _default_neutts_ref_audio() -> str:
1333
+ """Return path to the bundled default voice reference audio."""
1334
+ return str(Path(__file__).parent / "neutts_samples" / "jo.wav")
1335
+
1336
+
1337
+ def _default_neutts_ref_text() -> str:
1338
+ """Return path to the bundled default voice reference transcript."""
1339
+ return str(Path(__file__).parent / "neutts_samples" / "jo.txt")
1340
+
1341
+
1342
+ def _generate_neutts(text: str, output_path: str, tts_config: Dict[str, Any]) -> str:
1343
+ """Generate speech using the local NeuTTS engine.
1344
+
1345
+ Runs synthesis in a subprocess via tools/neutts_synth.py to keep the
1346
+ ~500MB model in a separate process that exits after synthesis.
1347
+ Outputs WAV; the caller handles conversion for Telegram if needed.
1348
+ """
1349
+ import sys
1350
+
1351
+ neutts_config = tts_config.get("neutts", {})
1352
+ ref_audio = neutts_config.get("ref_audio", "") or _default_neutts_ref_audio()
1353
+ ref_text = neutts_config.get("ref_text", "") or _default_neutts_ref_text()
1354
+ model = neutts_config.get("model", "neuphonic/neutts-air-q4-gguf")
1355
+ device = neutts_config.get("device", "cpu")
1356
+
1357
+ # NeuTTS outputs WAV natively — use a .wav path for generation,
1358
+ # let the caller convert to the final format afterward.
1359
+ wav_path = output_path
1360
+ if not output_path.endswith(".wav"):
1361
+ wav_path = output_path.rsplit(".", 1)[0] + ".wav"
1362
+
1363
+ synth_script = str(Path(__file__).parent / "neutts_synth.py")
1364
+ cmd = [
1365
+ sys.executable, synth_script,
1366
+ "--text", text,
1367
+ "--out", wav_path,
1368
+ "--ref-audio", ref_audio,
1369
+ "--ref-text", ref_text,
1370
+ "--model", model,
1371
+ "--device", device,
1372
+ ]
1373
+
1374
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
1375
+ if result.returncode != 0:
1376
+ stderr = result.stderr.strip()
1377
+ # Filter out the "OK:" line from stderr
1378
+ error_lines = [l for l in stderr.splitlines() if not l.startswith("OK:")]
1379
+ raise RuntimeError(f"NeuTTS synthesis failed: {chr(10).join(error_lines) or 'unknown error'}")
1380
+
1381
+ # If the caller wanted .mp3 or .ogg, convert from WAV
1382
+ if wav_path != output_path:
1383
+ ffmpeg = shutil.which("ffmpeg")
1384
+ if ffmpeg:
1385
+ conv_cmd = [ffmpeg, "-i", wav_path, "-y", "-loglevel", "error", output_path]
1386
+ subprocess.run(conv_cmd, check=True, timeout=30)
1387
+ os.remove(wav_path)
1388
+ else:
1389
+ # No ffmpeg — just rename the WAV to the expected path
1390
+ os.rename(wav_path, output_path)
1391
+
1392
+ return output_path
1393
+
1394
+
1395
+ # ===========================================================================
1396
+ # Provider: Piper (local, neural VITS, 44 languages)
1397
+ # ===========================================================================
1398
+
1399
+ # Module-level cache for Piper voice instances. Voices are keyed on their
1400
+ # absolute .onnx model path so switching voices doesn't invalidate older
1401
+ # cached voices.
1402
+ _piper_voice_cache: Dict[str, Any] = {}
1403
+
1404
+
1405
+ def _check_piper_available() -> bool:
1406
+ """Check whether the piper-tts package is importable."""
1407
+ try:
1408
+ import importlib.util
1409
+ return importlib.util.find_spec("piper") is not None
1410
+ except Exception:
1411
+ return False
1412
+
1413
+
1414
+ def _get_piper_voices_dir() -> Path:
1415
+ """Return the directory where Hermes caches Piper voice models.
1416
+
1417
+ Resolves to ``~/.hermes/cache/piper-voices/`` under the active
1418
+ HERMES_HOME so voice downloads follow profile boundaries.
1419
+ """
1420
+ from calvyn_constants import get_hermes_dir
1421
+ root = Path(get_hermes_dir("cache/piper-voices", "piper_voices_cache"))
1422
+ root.mkdir(parents=True, exist_ok=True)
1423
+ return root
1424
+
1425
+
1426
+ def _resolve_piper_voice_path(voice: str, download_dir: Path) -> str:
1427
+ """Resolve *voice* (a model name or path) to a concrete .onnx file path.
1428
+
1429
+ Accepts any of:
1430
+ - Absolute / expanded path to an .onnx file the user already has
1431
+ - A voice *name* like ``en_US-lessac-medium`` (downloads to
1432
+ ``download_dir`` on first use via ``python -m piper.download_voices``)
1433
+
1434
+ Raises RuntimeError if the model can't be located or downloaded.
1435
+ """
1436
+ if not voice:
1437
+ voice = DEFAULT_PIPER_VOICE
1438
+
1439
+ # Case 1: user gave a direct file path.
1440
+ candidate = Path(voice).expanduser()
1441
+ if candidate.suffix.lower() == ".onnx" and candidate.exists():
1442
+ return str(candidate)
1443
+
1444
+ # Case 2: user gave a voice *name*. See if it's already downloaded.
1445
+ cached = download_dir / f"{voice}.onnx"
1446
+ if cached.exists() and (download_dir / f"{voice}.onnx.json").exists():
1447
+ return str(cached)
1448
+
1449
+ # Case 3: download the voice. piper ships a download helper module.
1450
+ import sys as _sys
1451
+ logger.info("[Piper] Downloading voice '%s' to %s (first use)", voice, download_dir)
1452
+ try:
1453
+ result = subprocess.run(
1454
+ [_sys.executable, "-m", "piper.download_voices", voice,
1455
+ "--download-dir", str(download_dir)],
1456
+ capture_output=True, text=True, timeout=300,
1457
+ )
1458
+ except subprocess.TimeoutExpired as exc:
1459
+ raise RuntimeError(
1460
+ f"Piper voice download timed out after 300s for '{voice}'"
1461
+ ) from exc
1462
+
1463
+ if result.returncode != 0:
1464
+ stderr = (result.stderr or "").strip() or "no stderr output"
1465
+ raise RuntimeError(
1466
+ f"Piper voice download failed for '{voice}': {stderr[:400]}"
1467
+ )
1468
+
1469
+ if not cached.exists():
1470
+ raise RuntimeError(
1471
+ f"Piper voice download completed but {cached} is missing — "
1472
+ f"check voice name (see: https://github.com/OHF-Voice/piper1-gpl/"
1473
+ f"blob/main/docs/VOICES.md)"
1474
+ )
1475
+ return str(cached)
1476
+
1477
+
1478
+ def _generate_piper_tts(text: str, output_path: str, tts_config: Dict[str, Any]) -> str:
1479
+ """Generate speech using the local Piper engine.
1480
+
1481
+ Loads the voice model once per process (cached by absolute path) and
1482
+ writes a WAV file. Caller is responsible for converting to MP3/Opus
1483
+ via ffmpeg when a different output format is required.
1484
+ """
1485
+ PiperVoice = _import_piper()
1486
+ import wave
1487
+
1488
+ piper_config = tts_config.get("piper", {}) if isinstance(tts_config, dict) else {}
1489
+ voice_name = piper_config.get("voice") or DEFAULT_PIPER_VOICE
1490
+ download_dir = Path(piper_config.get("voices_dir") or _get_piper_voices_dir()).expanduser()
1491
+ download_dir.mkdir(parents=True, exist_ok=True)
1492
+ use_cuda = bool(piper_config.get("use_cuda", False))
1493
+
1494
+ model_path = _resolve_piper_voice_path(voice_name, download_dir)
1495
+
1496
+ cache_key = f"{model_path}::cuda={use_cuda}"
1497
+ global _piper_voice_cache
1498
+ if cache_key not in _piper_voice_cache:
1499
+ logger.info("[Piper] Loading voice: %s", model_path)
1500
+ _piper_voice_cache[cache_key] = PiperVoice.load(model_path, use_cuda=use_cuda)
1501
+ logger.info("[Piper] Voice loaded")
1502
+ voice = _piper_voice_cache[cache_key]
1503
+
1504
+ # Optional synthesis knobs — only pass a SynthesisConfig when at least
1505
+ # one advanced knob is configured, so we don't depend on a newer Piper
1506
+ # version than the user's installed one unless we need to.
1507
+ syn_config = None
1508
+ has_advanced = any(
1509
+ k in piper_config
1510
+ for k in ("length_scale", "noise_scale", "noise_w_scale", "volume", "normalize_audio")
1511
+ )
1512
+ if has_advanced:
1513
+ try:
1514
+ from piper import SynthesisConfig # type: ignore
1515
+ syn_config = SynthesisConfig(
1516
+ length_scale=float(piper_config.get("length_scale", 1.0)),
1517
+ noise_scale=float(piper_config.get("noise_scale", 0.667)),
1518
+ noise_w_scale=float(piper_config.get("noise_w_scale", 0.8)),
1519
+ volume=float(piper_config.get("volume", 1.0)),
1520
+ normalize_audio=bool(piper_config.get("normalize_audio", True)),
1521
+ )
1522
+ except ImportError:
1523
+ logger.warning(
1524
+ "[Piper] SynthesisConfig not available in this piper-tts "
1525
+ "version — advanced knobs ignored"
1526
+ )
1527
+
1528
+ # Piper outputs WAV. Caller handles downstream MP3/Opus conversion.
1529
+ wav_path = output_path
1530
+ if not output_path.endswith(".wav"):
1531
+ wav_path = output_path.rsplit(".", 1)[0] + ".wav"
1532
+
1533
+ with wave.open(wav_path, "wb") as wav_file:
1534
+ if syn_config is not None:
1535
+ voice.synthesize_wav(text, wav_file, syn_config=syn_config)
1536
+ else:
1537
+ voice.synthesize_wav(text, wav_file)
1538
+
1539
+ # Convert to desired format if caller requested mp3/ogg
1540
+ if wav_path != output_path:
1541
+ ffmpeg = shutil.which("ffmpeg")
1542
+ if ffmpeg:
1543
+ conv_cmd = [ffmpeg, "-i", wav_path, "-y", "-loglevel", "error", output_path]
1544
+ subprocess.run(conv_cmd, check=True, timeout=30)
1545
+ try:
1546
+ os.remove(wav_path)
1547
+ except OSError:
1548
+ pass
1549
+ else:
1550
+ # No ffmpeg — keep WAV and return that path
1551
+ os.rename(wav_path, output_path)
1552
+
1553
+ return output_path
1554
+
1555
+
1556
+ # ===========================================================================
1557
+ # Provider: KittenTTS (local, lightweight)
1558
+ # ===========================================================================
1559
+
1560
+ # Module-level cache for KittenTTS model instance
1561
+ _kittentts_model_cache: Dict[str, Any] = {}
1562
+
1563
+
1564
+ def _generate_kittentts(text: str, output_path: str, tts_config: Dict[str, Any]) -> str:
1565
+ """Generate speech using KittenTTS local ONNX model.
1566
+
1567
+ KittenTTS is a lightweight TTS engine (25-80MB models) that runs
1568
+ entirely on CPU without requiring a GPU or API key.
1569
+
1570
+ Args:
1571
+ text: Text to convert to speech.
1572
+ output_path: Where to save the audio file.
1573
+ tts_config: TTS config dict.
1574
+
1575
+ Returns:
1576
+ Path to the saved audio file.
1577
+ """
1578
+ KittenTTS = _import_kittentts()
1579
+ kt_config = tts_config.get("kittentts", {})
1580
+ model_name = kt_config.get("model", DEFAULT_KITTENTTS_MODEL)
1581
+ voice = kt_config.get("voice", DEFAULT_KITTENTTS_VOICE)
1582
+ speed = kt_config.get("speed", 1.0)
1583
+ clean_text = kt_config.get("clean_text", True)
1584
+
1585
+ # Use cached model instance if available
1586
+ global _kittentts_model_cache
1587
+ if model_name not in _kittentts_model_cache:
1588
+ logger.info("[KittenTTS] Loading model: %s", model_name)
1589
+ _kittentts_model_cache[model_name] = KittenTTS(model_name)
1590
+ logger.info("[KittenTTS] Model loaded successfully")
1591
+
1592
+ model = _kittentts_model_cache[model_name]
1593
+
1594
+ # Generate audio (returns numpy array at 24kHz)
1595
+ audio = model.generate(text, voice=voice, speed=speed, clean_text=clean_text)
1596
+
1597
+ # Save as WAV
1598
+ import soundfile as sf
1599
+ wav_path = output_path
1600
+ if not output_path.endswith(".wav"):
1601
+ wav_path = output_path.rsplit(".", 1)[0] + ".wav"
1602
+
1603
+ sf.write(wav_path, audio, 24000)
1604
+
1605
+ # Convert to desired format if needed
1606
+ if wav_path != output_path:
1607
+ ffmpeg = shutil.which("ffmpeg")
1608
+ if ffmpeg:
1609
+ conv_cmd = [ffmpeg, "-i", wav_path, "-y", "-loglevel", "error", output_path]
1610
+ subprocess.run(conv_cmd, check=True, timeout=30)
1611
+ os.remove(wav_path)
1612
+ else:
1613
+ # No ffmpeg — rename the WAV to the expected path
1614
+ os.rename(wav_path, output_path)
1615
+
1616
+ return output_path
1617
+
1618
+
1619
+ # ===========================================================================
1620
+ # Main tool function
1621
+ # ===========================================================================
1622
+ def text_to_speech_tool(
1623
+ text: str,
1624
+ output_path: Optional[str] = None,
1625
+ ) -> str:
1626
+ """
1627
+ Convert text to speech audio.
1628
+
1629
+ Reads provider/voice config from ~/.hermes/config.yaml (tts: section).
1630
+ The model sends text; the user configures voice and provider.
1631
+
1632
+ On messaging platforms, the returned MEDIA:<path> tag is intercepted
1633
+ by the send pipeline and delivered as a native voice message.
1634
+ In CLI mode, the file is saved to ~/voice-memos/.
1635
+
1636
+ Args:
1637
+ text: The text to convert to speech.
1638
+ output_path: Optional custom save path. Defaults to ~/voice-memos/<timestamp>.mp3
1639
+
1640
+ Returns:
1641
+ str: JSON result with success, file_path, and optionally MEDIA tag.
1642
+ """
1643
+ if not text or not text.strip():
1644
+ return tool_error("Text is required", success=False)
1645
+
1646
+ tts_config = _load_tts_config()
1647
+ provider = _get_provider(tts_config)
1648
+
1649
+ # User-declared command provider (type: command under tts.providers.<name>)
1650
+ # resolves BEFORE the built-in dispatch. Built-in names short-circuit here
1651
+ # so a user's ``tts.providers.openai.command`` can't override the real
1652
+ # OpenAI handler.
1653
+ command_provider_config = _resolve_command_provider_config(provider, tts_config)
1654
+
1655
+ # Truncate very long text with a warning. The cap is per-provider
1656
+ # (OpenAI 4096, xAI 15k, MiniMax 10k, ElevenLabs model-aware, etc.).
1657
+ max_len = _resolve_max_text_length(provider, tts_config)
1658
+ if len(text) > max_len:
1659
+ logger.warning(
1660
+ "TTS text too long for provider %s (%d chars), truncating to %d",
1661
+ provider, len(text), max_len,
1662
+ )
1663
+ text = text[:max_len]
1664
+
1665
+ # Detect platform from gateway env var to choose the best output format.
1666
+ # Telegram voice bubbles require Opus (.ogg); OpenAI and ElevenLabs can
1667
+ # produce Opus natively (no ffmpeg needed). Edge TTS always outputs MP3
1668
+ # and needs ffmpeg for conversion.
1669
+ from gateway.session_context import get_session_env
1670
+ platform = get_session_env("HERMES_SESSION_PLATFORM", "").lower()
1671
+ want_opus = (platform == "telegram")
1672
+
1673
+ # Determine output path
1674
+ if output_path:
1675
+ file_path = Path(output_path).expanduser()
1676
+ if command_provider_config is not None:
1677
+ # Respect caller-supplied path but align the extension with the
1678
+ # provider's configured output_format so the command writes to a
1679
+ # path the caller actually expects.
1680
+ file_path = _configured_command_tts_output_path(
1681
+ file_path, command_provider_config
1682
+ )
1683
+ else:
1684
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
1685
+ out_dir = Path(DEFAULT_OUTPUT_DIR)
1686
+ out_dir.mkdir(parents=True, exist_ok=True)
1687
+ if command_provider_config is not None:
1688
+ fmt = _get_command_tts_output_format(command_provider_config)
1689
+ file_path = out_dir / f"tts_{timestamp}.{fmt}"
1690
+ # Use .ogg for Telegram with providers that support native Opus output,
1691
+ # otherwise fall back to .mp3 (Edge TTS will attempt ffmpeg conversion later).
1692
+ elif want_opus and provider in {"openai", "elevenlabs", "mistral", "gemini"}:
1693
+ file_path = out_dir / f"tts_{timestamp}.ogg"
1694
+ else:
1695
+ file_path = out_dir / f"tts_{timestamp}.mp3"
1696
+
1697
+ # Ensure parent directory exists
1698
+ file_path.parent.mkdir(parents=True, exist_ok=True)
1699
+ file_str = str(file_path)
1700
+
1701
+ try:
1702
+ # Generate audio with the configured provider
1703
+ if command_provider_config is not None:
1704
+ logger.info(
1705
+ "Generating speech with command TTS provider '%s'...", provider,
1706
+ )
1707
+ file_str = _generate_command_tts(
1708
+ text, file_str, provider, command_provider_config, tts_config,
1709
+ )
1710
+
1711
+ elif provider == "elevenlabs":
1712
+ try:
1713
+ _import_elevenlabs()
1714
+ except ImportError:
1715
+ return json.dumps({
1716
+ "success": False,
1717
+ "error": "ElevenLabs provider selected but 'elevenlabs' package not installed. Run: pip install elevenlabs"
1718
+ }, ensure_ascii=False)
1719
+ logger.info("Generating speech with ElevenLabs...")
1720
+ _generate_elevenlabs(text, file_str, tts_config)
1721
+
1722
+ elif provider == "openai":
1723
+ try:
1724
+ _import_openai_client()
1725
+ except ImportError:
1726
+ return json.dumps({
1727
+ "success": False,
1728
+ "error": "OpenAI provider selected but 'openai' package not installed."
1729
+ }, ensure_ascii=False)
1730
+ logger.info("Generating speech with OpenAI TTS...")
1731
+ _generate_openai_tts(text, file_str, tts_config)
1732
+
1733
+ elif provider == "minimax":
1734
+ logger.info("Generating speech with MiniMax TTS...")
1735
+ _generate_minimax_tts(text, file_str, tts_config)
1736
+
1737
+ elif provider == "xai":
1738
+ logger.info("Generating speech with xAI TTS...")
1739
+ _generate_xai_tts(text, file_str, tts_config)
1740
+
1741
+ elif provider == "mistral":
1742
+ # `mistralai` PyPI package was quarantined on 2026-05-12 after a
1743
+ # malicious 2.4.6 release. Surface a clear status message instead
1744
+ # of attempting an import that would either fail or pull a stale
1745
+ # cached package.
1746
+ return json.dumps({
1747
+ "success": False,
1748
+ "error": (
1749
+ "Mistral Voxtral TTS is temporarily disabled. The "
1750
+ "`mistralai` PyPI package was quarantined on 2026-05-12 "
1751
+ "after a malicious 2.4.6 release. Switch tts.provider in "
1752
+ "config.yaml to 'edge', 'elevenlabs', 'openai', 'minimax', "
1753
+ "'gemini', 'xai', 'neutts', or 'kittentts'. Mistral "
1754
+ "support will return once PyPI un-quarantines the package."
1755
+ ),
1756
+ }, ensure_ascii=False)
1757
+
1758
+ elif provider == "gemini":
1759
+ logger.info("Generating speech with Google Gemini TTS...")
1760
+ _generate_gemini_tts(text, file_str, tts_config)
1761
+
1762
+ elif provider == "neutts":
1763
+ if not _check_neutts_available():
1764
+ return json.dumps({
1765
+ "success": False,
1766
+ "error": "NeuTTS provider selected but neutts is not installed. "
1767
+ "Run hermes setup and choose NeuTTS, or install espeak-ng and run python -m pip install -U neutts[all]."
1768
+ }, ensure_ascii=False)
1769
+ logger.info("Generating speech with NeuTTS (local)...")
1770
+ _generate_neutts(text, file_str, tts_config)
1771
+
1772
+ elif provider == "kittentts":
1773
+ try:
1774
+ _import_kittentts()
1775
+ except ImportError:
1776
+ return json.dumps({
1777
+ "success": False,
1778
+ "error": "KittenTTS provider selected but 'kittentts' package not installed. "
1779
+ "Run 'hermes setup tts' and choose KittenTTS, or install manually: "
1780
+ "pip install https://github.com/KittenML/KittenTTS/releases/download/0.8.1/kittentts-0.8.1-py3-none-any.whl"
1781
+ }, ensure_ascii=False)
1782
+ logger.info("Generating speech with KittenTTS (local, ~25MB)...")
1783
+ _generate_kittentts(text, file_str, tts_config)
1784
+
1785
+ elif provider == "piper":
1786
+ try:
1787
+ _import_piper()
1788
+ except ImportError:
1789
+ return json.dumps({
1790
+ "success": False,
1791
+ "error": "Piper provider selected but 'piper-tts' package not installed. "
1792
+ "Run 'hermes tools' and select Piper under TTS, or install manually: "
1793
+ "pip install piper-tts",
1794
+ }, ensure_ascii=False)
1795
+ logger.info("Generating speech with Piper (local)...")
1796
+ _generate_piper_tts(text, file_str, tts_config)
1797
+
1798
+ else:
1799
+ # Default: Edge TTS (free), with NeuTTS as local fallback
1800
+ edge_available = True
1801
+ try:
1802
+ _import_edge_tts()
1803
+ except ImportError:
1804
+ edge_available = False
1805
+
1806
+ if edge_available:
1807
+ logger.info("Generating speech with Edge TTS...")
1808
+ try:
1809
+ import concurrent.futures
1810
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
1811
+ pool.submit(
1812
+ lambda: asyncio.run(_generate_edge_tts(text, file_str, tts_config))
1813
+ ).result(timeout=60)
1814
+ except RuntimeError:
1815
+ asyncio.run(_generate_edge_tts(text, file_str, tts_config))
1816
+ elif _check_neutts_available():
1817
+ logger.info("Edge TTS not available, falling back to NeuTTS (local)...")
1818
+ provider = "neutts"
1819
+ _generate_neutts(text, file_str, tts_config)
1820
+ else:
1821
+ return json.dumps({
1822
+ "success": False,
1823
+ "error": "No TTS provider available. Install edge-tts (pip install edge-tts) "
1824
+ "or set up NeuTTS for local synthesis."
1825
+ }, ensure_ascii=False)
1826
+
1827
+ # Check the file was actually created
1828
+ if not os.path.exists(file_str) or os.path.getsize(file_str) == 0:
1829
+ return json.dumps({
1830
+ "success": False,
1831
+ "error": f"TTS generation produced no output (provider: {provider})"
1832
+ }, ensure_ascii=False)
1833
+
1834
+ # Try Opus conversion for Telegram compatibility
1835
+ # Edge TTS outputs MP3, NeuTTS/KittenTTS output WAV — all need ffmpeg conversion
1836
+ voice_compatible = False
1837
+ if command_provider_config is not None:
1838
+ # Command providers are documents by default. Voice-bubble
1839
+ # delivery only kicks in when the user explicitly opts in
1840
+ # via ``voice_compatible: true`` in their provider config.
1841
+ if _is_command_tts_voice_compatible(command_provider_config):
1842
+ if not file_str.endswith(".ogg"):
1843
+ opus_path = _convert_to_opus(file_str)
1844
+ if opus_path:
1845
+ file_str = opus_path
1846
+ voice_compatible = file_str.endswith(".ogg")
1847
+ elif provider in {"edge", "neutts", "minimax", "xai", "kittentts", "piper"} and not file_str.endswith(".ogg"):
1848
+ opus_path = _convert_to_opus(file_str)
1849
+ if opus_path:
1850
+ file_str = opus_path
1851
+ voice_compatible = True
1852
+ elif provider in {"elevenlabs", "openai", "mistral", "gemini"}:
1853
+ voice_compatible = file_str.endswith(".ogg")
1854
+
1855
+ file_size = os.path.getsize(file_str)
1856
+ logger.info("TTS audio saved: %s (%s bytes, provider: %s)", file_str, f"{file_size:,}", provider)
1857
+
1858
+ # Build response with MEDIA tag for platform delivery
1859
+ media_tag = f"MEDIA:{file_str}"
1860
+ if voice_compatible:
1861
+ media_tag = f"[[audio_as_voice]]\n{media_tag}"
1862
+
1863
+ return json.dumps({
1864
+ "success": True,
1865
+ "file_path": file_str,
1866
+ "media_tag": media_tag,
1867
+ "provider": provider,
1868
+ "voice_compatible": voice_compatible,
1869
+ }, ensure_ascii=False)
1870
+
1871
+ except ValueError as e:
1872
+ # Configuration errors (missing API keys, etc.)
1873
+ error_msg = f"TTS configuration error ({provider}): {e}"
1874
+ logger.error("%s", error_msg)
1875
+ return tool_error(error_msg, success=False)
1876
+ except FileNotFoundError as e:
1877
+ # Missing dependencies or files
1878
+ error_msg = f"TTS dependency missing ({provider}): {e}"
1879
+ logger.error("%s", error_msg, exc_info=True)
1880
+ return tool_error(error_msg, success=False)
1881
+ except Exception as e:
1882
+ # Unexpected errors
1883
+ error_msg = f"TTS generation failed ({provider}): {e}"
1884
+ logger.error("%s", error_msg, exc_info=True)
1885
+ return tool_error(error_msg, success=False)
1886
+
1887
+
1888
+ # ===========================================================================
1889
+ # Requirements check
1890
+ # ===========================================================================
1891
+ def check_tts_requirements() -> bool:
1892
+ """
1893
+ Check if at least one TTS provider is available.
1894
+
1895
+ Edge TTS needs no API key and is the default, so if the package
1896
+ is installed, TTS is available. A user-declared command provider
1897
+ also satisfies the requirement.
1898
+
1899
+ Returns:
1900
+ bool: True if at least one provider can work.
1901
+ """
1902
+ # Any configured command provider counts as available.
1903
+ if _has_any_command_tts_provider():
1904
+ return True
1905
+ try:
1906
+ _import_edge_tts()
1907
+ return True
1908
+ except ImportError:
1909
+ pass
1910
+ try:
1911
+ _import_elevenlabs()
1912
+ if get_env_value("ELEVENLABS_API_KEY"):
1913
+ return True
1914
+ except ImportError:
1915
+ pass
1916
+ try:
1917
+ _import_openai_client()
1918
+ if _has_openai_audio_backend():
1919
+ return True
1920
+ except ImportError:
1921
+ pass
1922
+ if get_env_value("MINIMAX_API_KEY"):
1923
+ return True
1924
+ try:
1925
+ from tools.xai_http import resolve_xai_http_credentials
1926
+
1927
+ if resolve_xai_http_credentials().get("api_key"):
1928
+ return True
1929
+ except Exception:
1930
+ pass
1931
+ if get_env_value("GEMINI_API_KEY") or get_env_value("GOOGLE_API_KEY"):
1932
+ return True
1933
+ try:
1934
+ _import_mistral_client()
1935
+ if get_env_value("MISTRAL_API_KEY"):
1936
+ return True
1937
+ except ImportError:
1938
+ pass
1939
+ if _check_neutts_available():
1940
+ return True
1941
+ if _check_kittentts_available():
1942
+ return True
1943
+ if _check_piper_available():
1944
+ return True
1945
+ return False
1946
+
1947
+
1948
+ def _resolve_openai_audio_client_config() -> tuple[str, str]:
1949
+ """Return direct OpenAI audio config or a managed gateway fallback.
1950
+
1951
+ When ``tts.use_gateway`` is set in config, the Tool Gateway is preferred
1952
+ even if direct OpenAI credentials are present.
1953
+ """
1954
+ direct_api_key = resolve_openai_audio_api_key()
1955
+ if direct_api_key and not prefers_gateway("tts"):
1956
+ return direct_api_key, DEFAULT_OPENAI_BASE_URL
1957
+
1958
+ managed_gateway = resolve_managed_tool_gateway("openai-audio")
1959
+ if managed_gateway is None:
1960
+ message = "Neither VOICE_TOOLS_OPENAI_KEY nor OPENAI_API_KEY is set"
1961
+ if managed_nous_tools_enabled():
1962
+ message += ", and the managed OpenAI audio gateway is unavailable"
1963
+ raise ValueError(message)
1964
+
1965
+ return managed_gateway.nous_user_token, urljoin(
1966
+ f"{managed_gateway.gateway_origin.rstrip('/')}/", "v1"
1967
+ )
1968
+
1969
+
1970
+ def _has_openai_audio_backend() -> bool:
1971
+ """Return True when OpenAI audio can use direct credentials or the managed gateway."""
1972
+ return bool(resolve_openai_audio_api_key() or resolve_managed_tool_gateway("openai-audio"))
1973
+
1974
+
1975
+ # ===========================================================================
1976
+ # Streaming TTS: sentence-by-sentence pipeline for ElevenLabs
1977
+ # ===========================================================================
1978
+ # Sentence boundary pattern: punctuation followed by space or newline
1979
+ _SENTENCE_BOUNDARY_RE = re.compile(r'(?<=[.!?])(?:\s|\n)|(?:\n\n)')
1980
+
1981
+ # Markdown stripping patterns (same as cli.py _voice_speak_response)
1982
+ _MD_CODE_BLOCK = re.compile(r'```[\s\S]*?```')
1983
+ _MD_LINK = re.compile(r'\[([^\]]+)\]\([^)]+\)')
1984
+ _MD_URL = re.compile(r'https?://\S+')
1985
+ _MD_BOLD = re.compile(r'\*\*(.+?)\*\*')
1986
+ _MD_ITALIC = re.compile(r'\*(.+?)\*')
1987
+ _MD_INLINE_CODE = re.compile(r'`(.+?)`')
1988
+ _MD_HEADER = re.compile(r'^#+\s*', flags=re.MULTILINE)
1989
+ _MD_LIST_ITEM = re.compile(r'^\s*[-*]\s+', flags=re.MULTILINE)
1990
+ _MD_HR = re.compile(r'---+')
1991
+ _MD_EXCESS_NL = re.compile(r'\n{3,}')
1992
+
1993
+
1994
+ def _strip_markdown_for_tts(text: str) -> str:
1995
+ """Remove markdown formatting that shouldn't be spoken aloud."""
1996
+ text = _MD_CODE_BLOCK.sub(' ', text)
1997
+ text = _MD_LINK.sub(r'\1', text)
1998
+ text = _MD_URL.sub('', text)
1999
+ text = _MD_BOLD.sub(r'\1', text)
2000
+ text = _MD_ITALIC.sub(r'\1', text)
2001
+ text = _MD_INLINE_CODE.sub(r'\1', text)
2002
+ text = _MD_HEADER.sub('', text)
2003
+ text = _MD_LIST_ITEM.sub('', text)
2004
+ text = _MD_HR.sub('', text)
2005
+ text = _MD_EXCESS_NL.sub('\n\n', text)
2006
+ return text.strip()
2007
+
2008
+
2009
+ def stream_tts_to_speaker(
2010
+ text_queue: queue.Queue,
2011
+ stop_event: threading.Event,
2012
+ tts_done_event: threading.Event,
2013
+ display_callback: Optional[Callable[[str], None]] = None,
2014
+ ):
2015
+ """Consume text deltas from *text_queue*, buffer them into sentences,
2016
+ and stream each sentence through ElevenLabs TTS to the speaker in
2017
+ real-time.
2018
+
2019
+ Protocol:
2020
+ * The producer puts ``str`` deltas onto *text_queue*.
2021
+ * A ``None`` sentinel signals end-of-text (flush remaining buffer).
2022
+ * *stop_event* can be set to abort early (e.g. user interrupt).
2023
+ * *tts_done_event* is **set** in the ``finally`` block so callers
2024
+ waiting on it (continuous voice mode) know playback is finished.
2025
+ """
2026
+ tts_done_event.clear()
2027
+
2028
+ try:
2029
+ # --- TTS client setup (optional -- display_callback works without it) ---
2030
+ client = None
2031
+ output_stream = None
2032
+ voice_id = DEFAULT_ELEVENLABS_VOICE_ID
2033
+ model_id = DEFAULT_ELEVENLABS_STREAMING_MODEL_ID
2034
+
2035
+ tts_config = _load_tts_config()
2036
+ el_config = tts_config.get("elevenlabs", {})
2037
+ voice_id = el_config.get("voice_id", voice_id)
2038
+ model_id = el_config.get("streaming_model_id",
2039
+ el_config.get("model_id", model_id))
2040
+ # Per-sentence cap for the streaming path. Look up the cap against
2041
+ # the *streaming* model_id (defaults to eleven_flash_v2_5 = 40k chars),
2042
+ # not the sync model_id. A user override
2043
+ # (tts.elevenlabs.max_text_length) still wins.
2044
+ stream_max_len = _resolve_max_text_length(
2045
+ "elevenlabs",
2046
+ {**tts_config, "elevenlabs": {**el_config, "model_id": model_id}},
2047
+ )
2048
+
2049
+ api_key = (get_env_value("ELEVENLABS_API_KEY") or "")
2050
+ if not api_key:
2051
+ logger.warning("ELEVENLABS_API_KEY not set; streaming TTS audio disabled")
2052
+ else:
2053
+ try:
2054
+ ElevenLabs = _import_elevenlabs()
2055
+ client = ElevenLabs(api_key=api_key)
2056
+ except ImportError:
2057
+ logger.warning("elevenlabs package not installed; streaming TTS disabled")
2058
+
2059
+ # Open a single sounddevice output stream for the lifetime of
2060
+ # this function. ElevenLabs pcm_24000 produces signed 16-bit
2061
+ # little-endian mono PCM at 24 kHz.
2062
+ if client is not None:
2063
+ try:
2064
+ sd = _import_sounddevice()
2065
+ output_stream = sd.OutputStream(
2066
+ samplerate=24000, channels=1, dtype="int16",
2067
+ )
2068
+ output_stream.start()
2069
+ except (ImportError, OSError) as exc:
2070
+ logger.debug("sounddevice not available: %s", exc)
2071
+ output_stream = None
2072
+ except Exception as exc:
2073
+ logger.warning("sounddevice OutputStream failed: %s", exc)
2074
+ output_stream = None
2075
+
2076
+ sentence_buf = ""
2077
+ min_sentence_len = 20
2078
+ long_flush_len = 100
2079
+ queue_timeout = 0.5
2080
+ _spoken_sentences: list[str] = [] # track spoken sentences to skip duplicates
2081
+ # Regex to strip complete <think>...</think> blocks from buffer
2082
+ _think_block_re = re.compile(r'<think[\s>].*?</think>', flags=re.DOTALL)
2083
+
2084
+ def _speak_sentence(sentence: str):
2085
+ """Display sentence and optionally generate + play audio."""
2086
+ if stop_event.is_set():
2087
+ return
2088
+ cleaned = _strip_markdown_for_tts(sentence).strip()
2089
+ if not cleaned:
2090
+ return
2091
+ # Skip duplicate/near-duplicate sentences (LLM repetition)
2092
+ cleaned_lower = cleaned.lower().rstrip(".!,")
2093
+ for prev in _spoken_sentences:
2094
+ if prev.lower().rstrip(".!,") == cleaned_lower:
2095
+ return
2096
+ _spoken_sentences.append(cleaned)
2097
+ # Display raw sentence on screen before TTS processing
2098
+ if display_callback is not None:
2099
+ display_callback(sentence)
2100
+ # Skip audio generation if no TTS client available
2101
+ if client is None:
2102
+ return
2103
+ # Truncate very long sentences (ElevenLabs streaming path)
2104
+ if len(cleaned) > stream_max_len:
2105
+ cleaned = cleaned[:stream_max_len]
2106
+ try:
2107
+ audio_iter = client.text_to_speech.convert(
2108
+ text=cleaned,
2109
+ voice_id=voice_id,
2110
+ model_id=model_id,
2111
+ output_format="pcm_24000",
2112
+ )
2113
+ if output_stream is not None:
2114
+ for chunk in audio_iter:
2115
+ if stop_event.is_set():
2116
+ break
2117
+ import numpy as _np
2118
+ audio_array = _np.frombuffer(chunk, dtype=_np.int16)
2119
+ output_stream.write(audio_array.reshape(-1, 1))
2120
+ else:
2121
+ # Fallback: write chunks to temp file and play via system player
2122
+ _play_via_tempfile(audio_iter, stop_event)
2123
+ except Exception as exc:
2124
+ logger.warning("Streaming TTS sentence failed: %s", exc)
2125
+
2126
+ def _play_via_tempfile(audio_iter, stop_evt):
2127
+ """Write PCM chunks to a temp WAV file and play it."""
2128
+ tmp_path = None
2129
+ try:
2130
+ import wave
2131
+ tmp = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
2132
+ tmp_path = tmp.name
2133
+ with wave.open(tmp, "wb") as wf:
2134
+ wf.setnchannels(1)
2135
+ wf.setsampwidth(2) # 16-bit
2136
+ wf.setframerate(24000)
2137
+ for chunk in audio_iter:
2138
+ if stop_evt.is_set():
2139
+ break
2140
+ wf.writeframes(chunk)
2141
+ from tools.voice_mode import play_audio_file
2142
+ play_audio_file(tmp_path)
2143
+ except Exception as exc:
2144
+ logger.warning("Temp-file TTS fallback failed: %s", exc)
2145
+ finally:
2146
+ if tmp_path:
2147
+ try:
2148
+ os.unlink(tmp_path)
2149
+ except OSError:
2150
+ pass
2151
+
2152
+ while not stop_event.is_set():
2153
+ # Read next delta from queue
2154
+ try:
2155
+ delta = text_queue.get(timeout=queue_timeout)
2156
+ except queue.Empty:
2157
+ # Timeout: if we have accumulated a long buffer, flush it
2158
+ if len(sentence_buf) > long_flush_len:
2159
+ _speak_sentence(sentence_buf)
2160
+ sentence_buf = ""
2161
+ continue
2162
+
2163
+ if delta is None:
2164
+ # End-of-text sentinel: strip any remaining think blocks, flush
2165
+ sentence_buf = _think_block_re.sub('', sentence_buf)
2166
+ if sentence_buf.strip():
2167
+ _speak_sentence(sentence_buf)
2168
+ break
2169
+
2170
+ sentence_buf += delta
2171
+
2172
+ # --- Think block filtering ---
2173
+ # Strip complete <think>...</think> blocks from buffer.
2174
+ # Works correctly even when tags span multiple deltas.
2175
+ sentence_buf = _think_block_re.sub('', sentence_buf)
2176
+
2177
+ # If an incomplete <think tag is at the end, wait for more data
2178
+ # before extracting sentences (the closing tag may arrive next).
2179
+ if '<think' in sentence_buf and '</think>' not in sentence_buf:
2180
+ continue
2181
+
2182
+ # Check for sentence boundaries
2183
+ while True:
2184
+ m = _SENTENCE_BOUNDARY_RE.search(sentence_buf)
2185
+ if m is None:
2186
+ break
2187
+ end_pos = m.end()
2188
+ sentence = sentence_buf[:end_pos]
2189
+ sentence_buf = sentence_buf[end_pos:]
2190
+ # Merge short fragments into the next sentence
2191
+ if len(sentence.strip()) < min_sentence_len:
2192
+ sentence_buf = sentence + sentence_buf
2193
+ break
2194
+ _speak_sentence(sentence)
2195
+
2196
+ # Drain any remaining items from the queue
2197
+ while True:
2198
+ try:
2199
+ text_queue.get_nowait()
2200
+ except queue.Empty:
2201
+ break
2202
+
2203
+ # output_stream is closed in the finally block below
2204
+
2205
+ except Exception as exc:
2206
+ logger.warning("Streaming TTS pipeline error: %s", exc)
2207
+ finally:
2208
+ # Always close the audio output stream to avoid locking the device
2209
+ if output_stream is not None:
2210
+ try:
2211
+ output_stream.stop()
2212
+ output_stream.close()
2213
+ except Exception:
2214
+ pass
2215
+ tts_done_event.set()
2216
+
2217
+
2218
+ # ===========================================================================
2219
+ # Main -- quick diagnostics
2220
+ # ===========================================================================
2221
+ if __name__ == "__main__":
2222
+ print("🔊 Text-to-Speech Tool Module")
2223
+ print("=" * 50)
2224
+
2225
+ def _check(importer, label):
2226
+ try:
2227
+ importer()
2228
+ return True
2229
+ except ImportError:
2230
+ return False
2231
+
2232
+ print("\nProvider availability:")
2233
+ print(f" Edge TTS: {'installed' if _check(_import_edge_tts, 'edge') else 'not installed (pip install edge-tts)'}")
2234
+ print(f" ElevenLabs: {'installed' if _check(_import_elevenlabs, 'el') else 'not installed (pip install elevenlabs)'}")
2235
+ print(f" API Key: {'set' if get_env_value('ELEVENLABS_API_KEY') else 'not set'}")
2236
+ print(f" OpenAI: {'installed' if _check(_import_openai_client, 'oai') else 'not installed'}")
2237
+ print(
2238
+ " API Key: "
2239
+ f"{'set' if resolve_openai_audio_api_key() else 'not set (VOICE_TOOLS_OPENAI_KEY or OPENAI_API_KEY)'}"
2240
+ )
2241
+ print(f" MiniMax: {'API key set' if get_env_value('MINIMAX_API_KEY') else 'not set (MINIMAX_API_KEY)'}")
2242
+ print(f" Piper: {'installed' if _check_piper_available() else 'not installed (pip install piper-tts)'}")
2243
+ print(f" ffmpeg: {'✅ found' if _has_ffmpeg() else '❌ not found (needed for Telegram Opus)'}")
2244
+ print(f"\n Output dir: {DEFAULT_OUTPUT_DIR}")
2245
+
2246
+ config = _load_tts_config()
2247
+ provider = _get_provider(config)
2248
+ print(f" Configured provider: {provider}")
2249
+
2250
+
2251
+ # ---------------------------------------------------------------------------
2252
+ # Registry
2253
+ # ---------------------------------------------------------------------------
2254
+ from tools.registry import registry, tool_error
2255
+
2256
+ TTS_SCHEMA = {
2257
+ "name": "text_to_speech",
2258
+ "description": "Convert text to speech audio. Returns a MEDIA: path that the platform delivers as native audio. Compatible providers render as a voice bubble on Telegram; otherwise audio is sent as a regular attachment. In CLI mode, saves to ~/voice-memos/. Voice and provider are user-configured (built-in providers like edge/openai or custom command providers under tts.providers.<name>), not model-selected.",
2259
+ "parameters": {
2260
+ "type": "object",
2261
+ "properties": {
2262
+ "text": {
2263
+ "type": "string",
2264
+ "description": "The text to convert to speech. Provider-specific character caps apply and are enforced automatically (OpenAI 4096, xAI 15000, MiniMax 10000, ElevenLabs 5k-40k depending on model); over-long input is truncated."
2265
+ },
2266
+ "output_path": {
2267
+ "type": "string",
2268
+ "description": f"Optional custom file path to save the audio. Defaults to {display_hermes_home()}/audio_cache/<timestamp>.mp3"
2269
+ }
2270
+ },
2271
+ "required": ["text"]
2272
+ }
2273
+ }
2274
+
2275
+ registry.register(
2276
+ name="text_to_speech",
2277
+ toolset="tts",
2278
+ schema=TTS_SCHEMA,
2279
+ handler=lambda args, **kw: text_to_speech_tool(
2280
+ text=args.get("text", ""),
2281
+ output_path=args.get("output_path")),
2282
+ check_fn=check_tts_requirements,
2283
+ emoji="🔊",
2284
+ )
2285
+