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,1873 @@
1
+ """
2
+ Gateway configuration management.
3
+
4
+ Handles loading and validating configuration for:
5
+ - Connected platforms (Telegram, Discord, WhatsApp, Weixin, and more)
6
+ - Home channels for each platform
7
+ - Session reset policies
8
+ - Delivery preferences
9
+ """
10
+
11
+ import logging
12
+ import os
13
+ import json
14
+ from pathlib import Path
15
+ from dataclasses import dataclass, field
16
+ from typing import Dict, List, Optional, Any, Callable
17
+ from enum import Enum
18
+
19
+ from hermes_cli.config import get_hermes_home
20
+ from utils import is_truthy_value
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ def _coerce_bool(value: Any, default: bool = True) -> bool:
26
+ """Coerce bool-ish config values, preserving a caller-provided default."""
27
+ if value is None:
28
+ return default
29
+ if isinstance(value, str):
30
+ lowered = value.strip().lower()
31
+ if lowered in {"true", "1", "yes", "on"}:
32
+ return True
33
+ if lowered in {"false", "0", "no", "off"}:
34
+ return False
35
+ return default
36
+ return is_truthy_value(value, default=default)
37
+
38
+
39
+ def _coerce_float(value: Any, default: float) -> float:
40
+ """Coerce numeric config values, falling back on malformed input."""
41
+ if value is None:
42
+ return default
43
+ try:
44
+ return float(value)
45
+ except (TypeError, ValueError):
46
+ return default
47
+
48
+
49
+ def _coerce_int(value: Any, default: int) -> int:
50
+ """Coerce integer config values, falling back on malformed input."""
51
+ if value is None:
52
+ return default
53
+ try:
54
+ return int(value)
55
+ except (TypeError, ValueError):
56
+ return default
57
+
58
+
59
+ def _normalize_unauthorized_dm_behavior(value: Any, default: str = "pair") -> str:
60
+ """Normalize unauthorized DM behavior to a supported value."""
61
+ if isinstance(value, str):
62
+ normalized = value.strip().lower()
63
+ if normalized in {"pair", "ignore"}:
64
+ return normalized
65
+ return default
66
+
67
+
68
+ def _normalize_notice_delivery(value: Any, default: str = "public") -> str:
69
+ """Normalize notice delivery mode to a supported value."""
70
+ if isinstance(value, str):
71
+ normalized = value.strip().lower()
72
+ if normalized in {"public", "private"}:
73
+ return normalized
74
+ return default
75
+
76
+
77
+ def _ensure_platform_extra_dict(platforms_data: dict, name: str) -> tuple[dict, dict]:
78
+ """Get-or-create ``platforms_data[name]`` and its nested ``extra`` dict.
79
+
80
+ Both slots are coerced to ``{}`` if a non-dict value is encountered, so
81
+ callers can safely write keys without type-checking. Returns
82
+ ``(plat_data, extra)`` for in-place mutation.
83
+ """
84
+ plat_data = platforms_data.setdefault(name, {})
85
+ if not isinstance(plat_data, dict):
86
+ plat_data = {}
87
+ platforms_data[name] = plat_data
88
+ extra = plat_data.setdefault("extra", {})
89
+ if not isinstance(extra, dict):
90
+ extra = {}
91
+ plat_data["extra"] = extra
92
+ return plat_data, extra
93
+
94
+
95
+ # Module-level cache for bundled platform plugin names (lives outside the
96
+ # enum so it doesn't become an accidental enum member).
97
+ _Platform__bundled_plugin_names: Optional[set] = None
98
+
99
+
100
+ class Platform(Enum):
101
+ """Supported messaging platforms.
102
+
103
+ Built-in platforms have explicit members. Plugin platforms use dynamic
104
+ members created on-demand by ``_missing_()`` so that
105
+ ``Platform("irc")`` works without modifying this enum. Dynamic members
106
+ are cached in ``_value2member_map_`` for identity-stable comparisons.
107
+ """
108
+ LOCAL = "local"
109
+ TELEGRAM = "telegram"
110
+ DISCORD = "discord"
111
+ WHATSAPP = "whatsapp"
112
+ SLACK = "slack"
113
+ SIGNAL = "signal"
114
+ MATTERMOST = "mattermost"
115
+ MATRIX = "matrix"
116
+ HOMEASSISTANT = "homeassistant"
117
+ EMAIL = "email"
118
+ SMS = "sms"
119
+ DINGTALK = "dingtalk"
120
+ API_SERVER = "api_server"
121
+ WEBHOOK = "webhook"
122
+ MSGRAPH_WEBHOOK = "msgraph_webhook"
123
+ FEISHU = "feishu"
124
+ WECOM = "wecom"
125
+ WECOM_CALLBACK = "wecom_callback"
126
+ WEIXIN = "weixin"
127
+ BLUEBUBBLES = "bluebubbles"
128
+ QQBOT = "qqbot"
129
+ YUANBAO = "yuanbao"
130
+ @classmethod
131
+ def _missing_(cls, value):
132
+ """Accept unknown platform names only for known plugin adapters.
133
+
134
+ Creates a pseudo-member cached in ``_value2member_map_`` so that
135
+ ``Platform("irc") is Platform("irc")`` holds True (identity-stable).
136
+ Arbitrary strings are rejected to prevent enum pollution.
137
+ """
138
+ if not isinstance(value, str) or not value.strip():
139
+ return None
140
+ # Normalise to lowercase to avoid case mismatches in config
141
+ value = value.strip().lower()
142
+ # Check cache first (another call may have created it already)
143
+ if value in cls._value2member_map_:
144
+ return cls._value2member_map_[value]
145
+
146
+ # Only create pseudo-members for bundled plugin platforms (discovered
147
+ # via filesystem scan) or runtime-registered plugin platforms.
148
+ global _Platform__bundled_plugin_names
149
+ if _Platform__bundled_plugin_names is None:
150
+ _Platform__bundled_plugin_names = cls._scan_bundled_plugin_platforms()
151
+ if value in _Platform__bundled_plugin_names:
152
+ pseudo = object.__new__(cls)
153
+ pseudo._value_ = value
154
+ pseudo._name_ = value.upper().replace("-", "_").replace(" ", "_")
155
+ cls._value2member_map_[value] = pseudo
156
+ cls._member_map_[pseudo._name_] = pseudo
157
+ return pseudo
158
+
159
+ # Runtime-registered plugins (e.g. user-installed, discovered after
160
+ # the enum was defined).
161
+ try:
162
+ from gateway.platform_registry import platform_registry
163
+ if platform_registry.is_registered(value):
164
+ pseudo = object.__new__(cls)
165
+ pseudo._value_ = value
166
+ pseudo._name_ = value.upper().replace("-", "_").replace(" ", "_")
167
+ cls._value2member_map_[value] = pseudo
168
+ cls._member_map_[pseudo._name_] = pseudo
169
+ return pseudo
170
+ except Exception:
171
+ pass
172
+
173
+ return None
174
+
175
+ @classmethod
176
+ def _scan_bundled_plugin_platforms(cls) -> set:
177
+ """Return names of bundled platform plugins under ``plugins/platforms/``."""
178
+ names: set = set()
179
+ try:
180
+ platforms_dir = Path(__file__).parent.parent / "plugins" / "platforms"
181
+ if platforms_dir.is_dir():
182
+ for child in platforms_dir.iterdir():
183
+ if (
184
+ child.is_dir()
185
+ and (child / "__init__.py").exists()
186
+ and (
187
+ (child / "plugin.yaml").exists()
188
+ or (child / "plugin.yml").exists()
189
+ )
190
+ ):
191
+ names.add(child.name.lower())
192
+ except Exception:
193
+ pass
194
+ return names
195
+
196
+
197
+ # Snapshot of built-in platform values before any dynamic _missing_ lookups.
198
+ # Used to distinguish real platforms from arbitrary strings.
199
+ _BUILTIN_PLATFORM_VALUES = frozenset(m.value for m in Platform.__members__.values())
200
+
201
+
202
+ @dataclass
203
+ class HomeChannel:
204
+ """
205
+ Default destination for a platform.
206
+
207
+ When a cron job specifies deliver="telegram" without a specific chat ID,
208
+ messages are sent to this home channel. Thread-aware platforms may also
209
+ store a thread/topic ID so the bare platform target routes to the exact
210
+ conversation where /sethome was run.
211
+ """
212
+ platform: Platform
213
+ chat_id: str
214
+ name: str # Human-readable name for display
215
+ thread_id: Optional[str] = None
216
+
217
+ def to_dict(self) -> Dict[str, Any]:
218
+ result = {
219
+ "platform": self.platform.value,
220
+ "chat_id": self.chat_id,
221
+ "name": self.name,
222
+ }
223
+ if self.thread_id:
224
+ result["thread_id"] = self.thread_id
225
+ return result
226
+
227
+ @classmethod
228
+ def from_dict(cls, data: Dict[str, Any]) -> "HomeChannel":
229
+ return cls(
230
+ platform=Platform(data["platform"]),
231
+ chat_id=str(data["chat_id"]),
232
+ name=data.get("name", "Home"),
233
+ thread_id=str(data["thread_id"]) if data.get("thread_id") else None,
234
+ )
235
+
236
+
237
+ @dataclass
238
+ class SessionResetPolicy:
239
+ """
240
+ Controls when sessions reset (lose context).
241
+
242
+ Modes:
243
+ - "daily": Reset at a specific hour each day
244
+ - "idle": Reset after N minutes of inactivity
245
+ - "both": Whichever triggers first (daily boundary OR idle timeout)
246
+ - "none": Never auto-reset (context managed only by compression)
247
+ """
248
+ mode: str = "both" # "daily", "idle", "both", or "none"
249
+ at_hour: int = 4 # Hour for daily reset (0-23, local time)
250
+ idle_minutes: int = 1440 # Minutes of inactivity before reset (24 hours)
251
+ notify: bool = True # Send a notification to the user when auto-reset occurs
252
+ notify_exclude_platforms: tuple = ("api_server", "webhook") # Platforms that don't get reset notifications
253
+
254
+ def to_dict(self) -> Dict[str, Any]:
255
+ return {
256
+ "mode": self.mode,
257
+ "at_hour": self.at_hour,
258
+ "idle_minutes": self.idle_minutes,
259
+ "notify": self.notify,
260
+ "notify_exclude_platforms": list(self.notify_exclude_platforms),
261
+ }
262
+
263
+ @classmethod
264
+ def from_dict(cls, data: Dict[str, Any]) -> "SessionResetPolicy":
265
+ # Handle both missing keys and explicit null values (YAML null → None)
266
+ mode = data.get("mode")
267
+ at_hour = data.get("at_hour")
268
+ idle_minutes = data.get("idle_minutes")
269
+ notify = data.get("notify")
270
+ exclude = data.get("notify_exclude_platforms")
271
+ return cls(
272
+ mode=mode if mode is not None else "both",
273
+ at_hour=at_hour if at_hour is not None else 4,
274
+ idle_minutes=idle_minutes if idle_minutes is not None else 1440,
275
+ notify=_coerce_bool(notify, True),
276
+ notify_exclude_platforms=tuple(exclude) if exclude is not None else ("api_server", "webhook"),
277
+ )
278
+
279
+
280
+ @dataclass
281
+ class PlatformConfig:
282
+ """Configuration for a single messaging platform."""
283
+ enabled: bool = False
284
+ token: Optional[str] = None # Bot token (Telegram, Discord)
285
+ api_key: Optional[str] = None # API key if different from token
286
+ home_channel: Optional[HomeChannel] = None
287
+
288
+ # Reply threading mode (Telegram/Slack)
289
+ # - "off": Never thread replies to original message
290
+ # - "first": Only first chunk threads to user's message (default)
291
+ # - "all": All chunks in multi-part replies thread to user's message
292
+ reply_to_mode: str = "first"
293
+
294
+ # Whether the gateway is allowed to send "♻️ Gateway online" /
295
+ # "♻ Gateway restarted" lifecycle notifications on this platform.
296
+ # Default True preserves prior behavior. Set False on platforms used
297
+ # by end users (e.g. Slack) where operator-flavored restart pings are
298
+ # noise; keep True for back-channels where the operator wants them.
299
+ gateway_restart_notification: bool = True
300
+
301
+ # Platform-specific settings
302
+ extra: Dict[str, Any] = field(default_factory=dict)
303
+
304
+ def to_dict(self) -> Dict[str, Any]:
305
+ result = {
306
+ "enabled": self.enabled,
307
+ "extra": self.extra,
308
+ "reply_to_mode": self.reply_to_mode,
309
+ "gateway_restart_notification": self.gateway_restart_notification,
310
+ }
311
+ if self.token:
312
+ result["token"] = self.token
313
+ if self.api_key:
314
+ result["api_key"] = self.api_key
315
+ if self.home_channel:
316
+ result["home_channel"] = self.home_channel.to_dict()
317
+ return result
318
+
319
+ @classmethod
320
+ def from_dict(cls, data: Dict[str, Any]) -> "PlatformConfig":
321
+ home_channel = None
322
+ if "home_channel" in data:
323
+ home_channel = HomeChannel.from_dict(data["home_channel"])
324
+
325
+ return cls(
326
+ enabled=_coerce_bool(data.get("enabled"), False),
327
+ token=data.get("token"),
328
+ api_key=data.get("api_key"),
329
+ home_channel=home_channel,
330
+ reply_to_mode=data.get("reply_to_mode", "first"),
331
+ gateway_restart_notification=_coerce_bool(
332
+ data.get("gateway_restart_notification"), True
333
+ ),
334
+ extra=data.get("extra", {}),
335
+ )
336
+
337
+
338
+ # Streaming defaults — single source of truth so both StreamingConfig and
339
+ # StreamConsumerConfig agree on the out-of-the-box edit rhythm. Tuned for
340
+ # Telegram's ~1 edit/s flood envelope: a touch under 1s lets the cadence
341
+ # breathe without bumping into rate limits, and a smaller buffer threshold
342
+ # makes short replies feel near-instant in DMs.
343
+ DEFAULT_STREAMING_EDIT_INTERVAL: float = 0.8
344
+ DEFAULT_STREAMING_BUFFER_THRESHOLD: int = 24
345
+ DEFAULT_STREAMING_CURSOR: str = " ▉"
346
+
347
+
348
+ @dataclass
349
+ class StreamingConfig:
350
+ """Configuration for real-time token streaming to messaging platforms."""
351
+ enabled: bool = False
352
+ # Transport selection:
353
+ # "auto" — prefer native streaming-draft updates when the platform
354
+ # supports them (Telegram sendMessageDraft, Bot API 9.5+);
355
+ # fall back to edit-based when not. Recommended.
356
+ # "draft" — explicitly request native drafts; falls back to edit when
357
+ # the platform/chat doesn't support them.
358
+ # "edit" — progressive editMessageText only (legacy behaviour).
359
+ # "off" — disable streaming entirely.
360
+ transport: str = "auto"
361
+ edit_interval: float = DEFAULT_STREAMING_EDIT_INTERVAL
362
+ buffer_threshold: int = DEFAULT_STREAMING_BUFFER_THRESHOLD
363
+ cursor: str = DEFAULT_STREAMING_CURSOR
364
+ # Ported from openclaw/openclaw#72038. When >0, the final edit for
365
+ # a long-running streamed response is delivered as a fresh message
366
+ # if the original preview has been visible for at least this many
367
+ # seconds, so the platform's visible timestamp reflects completion
368
+ # time instead of the preview creation time. Currently applied to
369
+ # Telegram only (other platforms ignore the setting). Default 60s
370
+ # matches the OpenClaw rollout. Set to 0 to disable.
371
+ fresh_final_after_seconds: float = 60.0
372
+
373
+ def to_dict(self) -> Dict[str, Any]:
374
+ return {
375
+ "enabled": self.enabled,
376
+ "transport": self.transport,
377
+ "edit_interval": self.edit_interval,
378
+ "buffer_threshold": self.buffer_threshold,
379
+ "cursor": self.cursor,
380
+ "fresh_final_after_seconds": self.fresh_final_after_seconds,
381
+ }
382
+
383
+ @classmethod
384
+ def from_dict(cls, data: Dict[str, Any]) -> "StreamingConfig":
385
+ if not data:
386
+ return cls()
387
+ return cls(
388
+ enabled=_coerce_bool(data.get("enabled"), False),
389
+ transport=data.get("transport", "auto"),
390
+ edit_interval=_coerce_float(
391
+ data.get("edit_interval"), DEFAULT_STREAMING_EDIT_INTERVAL,
392
+ ),
393
+ buffer_threshold=_coerce_int(
394
+ data.get("buffer_threshold"), DEFAULT_STREAMING_BUFFER_THRESHOLD,
395
+ ),
396
+ cursor=data.get("cursor", DEFAULT_STREAMING_CURSOR),
397
+ fresh_final_after_seconds=_coerce_float(
398
+ data.get("fresh_final_after_seconds"), 60.0
399
+ ),
400
+ )
401
+
402
+
403
+ # -----------------------------------------------------------------------------
404
+ # Built-in platform connection checkers
405
+ # -----------------------------------------------------------------------------
406
+ # Each callable receives a ``PlatformConfig`` and returns ``True`` when the
407
+ # platform is sufficiently configured to be considered "connected". Platforms
408
+ # that rely on the generic ``token or api_key`` check (Telegram, Discord,
409
+ # Slack, Matrix, Mattermost, HomeAssistant) do not need an entry here.
410
+ _PLATFORM_CONNECTED_CHECKERS: dict[Platform, Callable[[PlatformConfig], bool]] = {
411
+ Platform.WEIXIN: lambda cfg: bool(
412
+ cfg.extra.get("account_id") and (cfg.token or cfg.extra.get("token"))
413
+ ),
414
+ Platform.WHATSAPP: lambda cfg: True, # bridge handles auth
415
+ Platform.SIGNAL: lambda cfg: bool(cfg.extra.get("http_url")),
416
+ Platform.EMAIL: lambda cfg: bool(cfg.extra.get("address")),
417
+ Platform.SMS: lambda cfg: bool(os.getenv("TWILIO_ACCOUNT_SID")),
418
+ Platform.API_SERVER: lambda cfg: True,
419
+ Platform.WEBHOOK: lambda cfg: True,
420
+ Platform.MSGRAPH_WEBHOOK: lambda cfg: True,
421
+ Platform.FEISHU: lambda cfg: bool(cfg.extra.get("app_id")),
422
+ Platform.WECOM: lambda cfg: bool(cfg.extra.get("bot_id")),
423
+ Platform.WECOM_CALLBACK: lambda cfg: bool(
424
+ cfg.extra.get("corp_id") or cfg.extra.get("apps")
425
+ ),
426
+ Platform.BLUEBUBBLES: lambda cfg: bool(
427
+ cfg.extra.get("server_url") and cfg.extra.get("password")
428
+ ),
429
+ Platform.QQBOT: lambda cfg: bool(
430
+ cfg.extra.get("app_id") and cfg.extra.get("client_secret")
431
+ ),
432
+ Platform.YUANBAO: lambda cfg: bool(
433
+ cfg.extra.get("app_id") and cfg.extra.get("app_secret")
434
+ ),
435
+ Platform.DINGTALK: lambda cfg: bool(
436
+ (cfg.extra.get("client_id") or os.getenv("DINGTALK_CLIENT_ID"))
437
+ and (cfg.extra.get("client_secret") or os.getenv("DINGTALK_CLIENT_SECRET"))
438
+ ),
439
+ }
440
+
441
+
442
+ @dataclass
443
+ class GatewayConfig:
444
+ """
445
+ Main gateway configuration.
446
+
447
+ Manages all platform connections, session policies, and delivery settings.
448
+ """
449
+ # Platform configurations
450
+ platforms: Dict[Platform, PlatformConfig] = field(default_factory=dict)
451
+
452
+ # Session reset policies by type
453
+ default_reset_policy: SessionResetPolicy = field(default_factory=SessionResetPolicy)
454
+ reset_by_type: Dict[str, SessionResetPolicy] = field(default_factory=dict)
455
+ reset_by_platform: Dict[Platform, SessionResetPolicy] = field(default_factory=dict)
456
+
457
+ # Reset trigger commands
458
+ reset_triggers: List[str] = field(default_factory=lambda: ["/new", "/reset"])
459
+
460
+ # User-defined quick commands (slash commands that bypass the agent loop)
461
+ quick_commands: Dict[str, Any] = field(default_factory=dict)
462
+
463
+ # Storage paths
464
+ sessions_dir: Path = field(default_factory=lambda: get_hermes_home() / "sessions")
465
+
466
+ # Delivery settings
467
+ always_log_local: bool = True # Always save cron outputs to local files
468
+
469
+ # STT settings
470
+ stt_enabled: bool = True # Whether to auto-transcribe inbound voice messages
471
+
472
+ # Session isolation in shared chats
473
+ group_sessions_per_user: bool = True # Isolate group/channel sessions per participant when user IDs are available
474
+ thread_sessions_per_user: bool = False # When False (default), threads are shared across all participants
475
+
476
+ # Unauthorized DM policy
477
+ unauthorized_dm_behavior: str = "pair" # "pair" or "ignore"
478
+
479
+ # Streaming configuration
480
+ streaming: StreamingConfig = field(default_factory=StreamingConfig)
481
+
482
+ # Session store pruning: drop SessionEntry records older than this many
483
+ # days from the in-memory dict and sessions.json. Keeps the store from
484
+ # growing unbounded in gateways serving many chats/threads/users over
485
+ # months. Pruning is invisible to users — if they resume, they get a
486
+ # fresh session exactly as if the reset policy had fired. 0 = disabled.
487
+ session_store_max_age_days: int = 90
488
+
489
+ def get_connected_platforms(self) -> List[Platform]:
490
+ """Return list of platforms that are enabled and configured."""
491
+ connected = []
492
+ for platform, config in self.platforms.items():
493
+ if not config.enabled:
494
+ continue
495
+ if self._is_platform_connected(platform, config):
496
+ connected.append(platform)
497
+ return connected
498
+
499
+ def _is_platform_connected(self, platform: Platform, config: PlatformConfig) -> bool:
500
+ """Check whether a single platform is sufficiently configured."""
501
+ # Weixin requires both a token and an account_id (checked first so
502
+ # the generic token branch doesn't let it through without account_id).
503
+ if platform == Platform.WEIXIN:
504
+ return bool(
505
+ config.extra.get("account_id")
506
+ and (config.token or config.extra.get("token"))
507
+ )
508
+
509
+ # Generic token/api_key auth covers Telegram, Discord, Slack, etc.
510
+ if config.token or config.api_key:
511
+ return True
512
+
513
+ # Platform-specific check
514
+ checker = _PLATFORM_CONNECTED_CHECKERS.get(platform)
515
+ if checker is not None:
516
+ return checker(config)
517
+
518
+ # Plugin-registered platforms
519
+ try:
520
+ from gateway.platform_registry import platform_registry
521
+ entry = platform_registry.get(platform.value)
522
+ if entry:
523
+ if entry.is_connected is not None:
524
+ return entry.is_connected(config)
525
+ if entry.validate_config is not None:
526
+ return entry.validate_config(config)
527
+ return True
528
+ except Exception:
529
+ pass # Registry not yet initialised during early import
530
+
531
+ return False
532
+
533
+ def get_home_channel(self, platform: Platform) -> Optional[HomeChannel]:
534
+ """Get the home channel for a platform."""
535
+ config = self.platforms.get(platform)
536
+ if config:
537
+ return config.home_channel
538
+ return None
539
+
540
+ def get_reset_policy(
541
+ self,
542
+ platform: Optional[Platform] = None,
543
+ session_type: Optional[str] = None
544
+ ) -> SessionResetPolicy:
545
+ """
546
+ Get the appropriate reset policy for a session.
547
+
548
+ Priority: platform override > type override > default
549
+ """
550
+ # Platform-specific override takes precedence
551
+ if platform and platform in self.reset_by_platform:
552
+ return self.reset_by_platform[platform]
553
+
554
+ # Type-specific override (dm, group, thread)
555
+ if session_type and session_type in self.reset_by_type:
556
+ return self.reset_by_type[session_type]
557
+
558
+ return self.default_reset_policy
559
+
560
+ def to_dict(self) -> Dict[str, Any]:
561
+ return {
562
+ "platforms": {
563
+ p.value: c.to_dict() for p, c in self.platforms.items()
564
+ },
565
+ "default_reset_policy": self.default_reset_policy.to_dict(),
566
+ "reset_by_type": {
567
+ k: v.to_dict() for k, v in self.reset_by_type.items()
568
+ },
569
+ "reset_by_platform": {
570
+ p.value: v.to_dict() for p, v in self.reset_by_platform.items()
571
+ },
572
+ "reset_triggers": self.reset_triggers,
573
+ "quick_commands": self.quick_commands,
574
+ "sessions_dir": str(self.sessions_dir),
575
+ "always_log_local": self.always_log_local,
576
+ "stt_enabled": self.stt_enabled,
577
+ "group_sessions_per_user": self.group_sessions_per_user,
578
+ "thread_sessions_per_user": self.thread_sessions_per_user,
579
+ "unauthorized_dm_behavior": self.unauthorized_dm_behavior,
580
+ "streaming": self.streaming.to_dict(),
581
+ "session_store_max_age_days": self.session_store_max_age_days,
582
+ }
583
+
584
+ @classmethod
585
+ def from_dict(cls, data: Dict[str, Any]) -> "GatewayConfig":
586
+ platforms = {}
587
+ for platform_name, platform_data in data.get("platforms", {}).items():
588
+ try:
589
+ platform = Platform(platform_name)
590
+ platforms[platform] = PlatformConfig.from_dict(platform_data)
591
+ except ValueError:
592
+ pass # Skip unknown platforms
593
+
594
+ reset_by_type = {}
595
+ for type_name, policy_data in data.get("reset_by_type", {}).items():
596
+ reset_by_type[type_name] = SessionResetPolicy.from_dict(policy_data)
597
+
598
+ reset_by_platform = {}
599
+ for platform_name, policy_data in data.get("reset_by_platform", {}).items():
600
+ try:
601
+ platform = Platform(platform_name)
602
+ reset_by_platform[platform] = SessionResetPolicy.from_dict(policy_data)
603
+ except ValueError:
604
+ pass
605
+
606
+ default_policy = SessionResetPolicy()
607
+ if "default_reset_policy" in data:
608
+ default_policy = SessionResetPolicy.from_dict(data["default_reset_policy"])
609
+
610
+ sessions_dir = get_hermes_home() / "sessions"
611
+ if "sessions_dir" in data:
612
+ sessions_dir = Path(data["sessions_dir"])
613
+
614
+ quick_commands = data.get("quick_commands", {})
615
+ if not isinstance(quick_commands, dict):
616
+ quick_commands = {}
617
+
618
+ stt_enabled = data.get("stt_enabled")
619
+ if stt_enabled is None:
620
+ stt_enabled = data.get("stt", {}).get("enabled") if isinstance(data.get("stt"), dict) else None
621
+
622
+ group_sessions_per_user = data.get("group_sessions_per_user")
623
+ thread_sessions_per_user = data.get("thread_sessions_per_user")
624
+ unauthorized_dm_behavior = _normalize_unauthorized_dm_behavior(
625
+ data.get("unauthorized_dm_behavior"),
626
+ "pair",
627
+ )
628
+
629
+ try:
630
+ session_store_max_age_days = int(data.get("session_store_max_age_days", 90))
631
+ session_store_max_age_days = max(session_store_max_age_days, 0)
632
+ except (TypeError, ValueError):
633
+ session_store_max_age_days = 90
634
+
635
+ return cls(
636
+ platforms=platforms,
637
+ default_reset_policy=default_policy,
638
+ reset_by_type=reset_by_type,
639
+ reset_by_platform=reset_by_platform,
640
+ reset_triggers=data.get("reset_triggers", ["/new", "/reset"]),
641
+ quick_commands=quick_commands,
642
+ sessions_dir=sessions_dir,
643
+ always_log_local=_coerce_bool(data.get("always_log_local"), True),
644
+ stt_enabled=_coerce_bool(stt_enabled, True),
645
+ group_sessions_per_user=_coerce_bool(group_sessions_per_user, True),
646
+ thread_sessions_per_user=_coerce_bool(thread_sessions_per_user, False),
647
+ unauthorized_dm_behavior=unauthorized_dm_behavior,
648
+ streaming=StreamingConfig.from_dict(data.get("streaming", {})),
649
+ session_store_max_age_days=session_store_max_age_days,
650
+ )
651
+
652
+ def get_unauthorized_dm_behavior(self, platform: Optional[Platform] = None) -> str:
653
+ """Return the effective unauthorized-DM behavior for a platform."""
654
+ if platform:
655
+ platform_cfg = self.platforms.get(platform)
656
+ if platform_cfg and "unauthorized_dm_behavior" in platform_cfg.extra:
657
+ return _normalize_unauthorized_dm_behavior(
658
+ platform_cfg.extra.get("unauthorized_dm_behavior"),
659
+ self.unauthorized_dm_behavior,
660
+ )
661
+ return self.unauthorized_dm_behavior
662
+
663
+ def get_notice_delivery(self, platform: Optional[Platform] = None) -> str:
664
+ """Return the effective notice-delivery mode for a platform."""
665
+ if platform:
666
+ platform_cfg = self.platforms.get(platform)
667
+ if platform_cfg and "notice_delivery" in platform_cfg.extra:
668
+ return _normalize_notice_delivery(
669
+ platform_cfg.extra.get("notice_delivery"),
670
+ "public",
671
+ )
672
+ return "public"
673
+
674
+
675
+ def load_gateway_config() -> GatewayConfig:
676
+ """
677
+ Load gateway configuration from multiple sources.
678
+
679
+ Priority (highest to lowest):
680
+ 1. Environment variables
681
+ 2. ~/.hermes/config.yaml (primary user-facing config)
682
+ 3. ~/.hermes/gateway.json (legacy — provides defaults under config.yaml)
683
+ 4. Built-in defaults
684
+ """
685
+ _home = get_hermes_home()
686
+ gw_data: dict = {}
687
+
688
+ # Legacy fallback: gateway.json provides the base layer.
689
+ # config.yaml keys always win when both specify the same setting.
690
+ gateway_json_path = _home / "gateway.json"
691
+ if gateway_json_path.exists():
692
+ try:
693
+ with open(gateway_json_path, "r", encoding="utf-8") as f:
694
+ gw_data = json.load(f) or {}
695
+ logger.info(
696
+ "Loaded legacy %s — consider moving settings to config.yaml",
697
+ gateway_json_path,
698
+ )
699
+ except Exception as e:
700
+ logger.warning("Failed to load %s: %s", gateway_json_path, e)
701
+
702
+ # Primary source: config.yaml
703
+ try:
704
+ import yaml
705
+ config_yaml_path = _home / "config.yaml"
706
+ if config_yaml_path.exists():
707
+ with open(config_yaml_path, encoding="utf-8") as f:
708
+ yaml_cfg = yaml.safe_load(f) or {}
709
+
710
+ # Map config.yaml keys → GatewayConfig.from_dict() schema.
711
+ # Each key overwrites whatever gateway.json may have set.
712
+ sr = yaml_cfg.get("session_reset")
713
+ if sr and isinstance(sr, dict):
714
+ gw_data["default_reset_policy"] = sr
715
+
716
+ qc = yaml_cfg.get("quick_commands")
717
+ if qc is not None:
718
+ if isinstance(qc, dict):
719
+ gw_data["quick_commands"] = qc
720
+ else:
721
+ logger.warning(
722
+ "Ignoring invalid quick_commands in config.yaml "
723
+ "(expected mapping, got %s)",
724
+ type(qc).__name__,
725
+ )
726
+
727
+ stt_cfg = yaml_cfg.get("stt")
728
+ if isinstance(stt_cfg, dict):
729
+ gw_data["stt"] = stt_cfg
730
+
731
+ if "group_sessions_per_user" in yaml_cfg:
732
+ gw_data["group_sessions_per_user"] = yaml_cfg["group_sessions_per_user"]
733
+
734
+ if "thread_sessions_per_user" in yaml_cfg:
735
+ gw_data["thread_sessions_per_user"] = yaml_cfg["thread_sessions_per_user"]
736
+
737
+ streaming_cfg = yaml_cfg.get("streaming")
738
+ if not isinstance(streaming_cfg, dict):
739
+ # Fall back to nested gateway.streaming written by
740
+ # ``hermes config set gateway.streaming.*``
741
+ streaming_cfg = yaml_cfg.get("gateway", {}).get("streaming")
742
+ if isinstance(streaming_cfg, dict):
743
+ gw_data["streaming"] = streaming_cfg
744
+
745
+ if "reset_triggers" in yaml_cfg:
746
+ gw_data["reset_triggers"] = yaml_cfg["reset_triggers"]
747
+
748
+ if "always_log_local" in yaml_cfg:
749
+ gw_data["always_log_local"] = yaml_cfg["always_log_local"]
750
+
751
+ if "unauthorized_dm_behavior" in yaml_cfg:
752
+ gw_data["unauthorized_dm_behavior"] = _normalize_unauthorized_dm_behavior(
753
+ yaml_cfg.get("unauthorized_dm_behavior"),
754
+ "pair",
755
+ )
756
+
757
+ # Merge platforms section from config.yaml into gw_data so that
758
+ # nested keys like platforms.webhook.extra.routes are loaded.
759
+ yaml_platforms = yaml_cfg.get("platforms")
760
+ platforms_data = gw_data.setdefault("platforms", {})
761
+ if not isinstance(platforms_data, dict):
762
+ platforms_data = {}
763
+ gw_data["platforms"] = platforms_data
764
+ if isinstance(yaml_platforms, dict):
765
+ for plat_name, plat_block in yaml_platforms.items():
766
+ if not isinstance(plat_block, dict):
767
+ continue
768
+ existing = platforms_data.get(plat_name, {})
769
+ if not isinstance(existing, dict):
770
+ existing = {}
771
+ # Deep-merge extra dicts so gateway.json defaults survive
772
+ merged_extra = {**existing.get("extra", {}), **plat_block.get("extra", {})}
773
+ if plat_name == Platform.SLACK.value and "enabled" in plat_block:
774
+ merged_extra["_enabled_explicit"] = True
775
+ merged = {**existing, **plat_block}
776
+ if merged_extra:
777
+ merged["extra"] = merged_extra
778
+ platforms_data[plat_name] = merged
779
+ gw_data["platforms"] = platforms_data
780
+ # Iterate built-in platforms plus any registered plugin platforms
781
+ # so plugin authors get the same shared-key bridging (#24836).
782
+ try:
783
+ from hermes_cli.plugins import discover_plugins
784
+ discover_plugins() # idempotent
785
+ from gateway.platform_registry import platform_registry as _pr
786
+ except Exception as e:
787
+ logger.debug("plugin discovery skipped: %s", e)
788
+ _pr = None
789
+
790
+ _shared_loop_targets: list = list(Platform)
791
+ if _pr is not None:
792
+ for _entry in _pr.plugin_entries():
793
+ try:
794
+ _plat = Platform(_entry.name)
795
+ except (ValueError, KeyError):
796
+ continue
797
+ if _plat not in _shared_loop_targets:
798
+ _shared_loop_targets.append(_plat)
799
+
800
+ for plat in _shared_loop_targets:
801
+ if plat == Platform.LOCAL:
802
+ continue
803
+ platform_cfg = yaml_cfg.get(plat.value)
804
+ if not isinstance(platform_cfg, dict):
805
+ continue
806
+ # Collect bridgeable keys from this platform section
807
+ bridged = {}
808
+ if "unauthorized_dm_behavior" in platform_cfg:
809
+ bridged["unauthorized_dm_behavior"] = _normalize_unauthorized_dm_behavior(
810
+ platform_cfg.get("unauthorized_dm_behavior"),
811
+ gw_data.get("unauthorized_dm_behavior", "pair"),
812
+ )
813
+ if "notice_delivery" in platform_cfg:
814
+ bridged["notice_delivery"] = _normalize_notice_delivery(
815
+ platform_cfg.get("notice_delivery"),
816
+ "public",
817
+ )
818
+ if "reply_prefix" in platform_cfg:
819
+ bridged["reply_prefix"] = platform_cfg["reply_prefix"]
820
+ if "reply_in_thread" in platform_cfg:
821
+ bridged["reply_in_thread"] = platform_cfg["reply_in_thread"]
822
+ if "require_mention" in platform_cfg:
823
+ bridged["require_mention"] = platform_cfg["require_mention"]
824
+ if "free_response_channels" in platform_cfg:
825
+ bridged["free_response_channels"] = platform_cfg["free_response_channels"]
826
+ if "mention_patterns" in platform_cfg:
827
+ bridged["mention_patterns"] = platform_cfg["mention_patterns"]
828
+ if "dm_policy" in platform_cfg:
829
+ bridged["dm_policy"] = platform_cfg["dm_policy"]
830
+ if "allow_from" in platform_cfg:
831
+ bridged["allow_from"] = platform_cfg["allow_from"]
832
+ if "allow_admin_from" in platform_cfg:
833
+ bridged["allow_admin_from"] = platform_cfg["allow_admin_from"]
834
+ if "user_allowed_commands" in platform_cfg:
835
+ bridged["user_allowed_commands"] = platform_cfg["user_allowed_commands"]
836
+ if "group_policy" in platform_cfg:
837
+ bridged["group_policy"] = platform_cfg["group_policy"]
838
+ if "group_allow_from" in platform_cfg:
839
+ bridged["group_allow_from"] = platform_cfg["group_allow_from"]
840
+ if "group_allow_admin_from" in platform_cfg:
841
+ bridged["group_allow_admin_from"] = platform_cfg["group_allow_admin_from"]
842
+ if "group_user_allowed_commands" in platform_cfg:
843
+ bridged["group_user_allowed_commands"] = platform_cfg["group_user_allowed_commands"]
844
+ if plat in {Platform.DISCORD, Platform.SLACK} and "channel_skill_bindings" in platform_cfg:
845
+ bridged["channel_skill_bindings"] = platform_cfg["channel_skill_bindings"]
846
+ if "channel_prompts" in platform_cfg:
847
+ channel_prompts = platform_cfg["channel_prompts"]
848
+ if isinstance(channel_prompts, dict):
849
+ bridged["channel_prompts"] = {str(k): v for k, v in channel_prompts.items()}
850
+ else:
851
+ bridged["channel_prompts"] = channel_prompts
852
+ enabled_was_explicit = "enabled" in platform_cfg
853
+ if not bridged and not enabled_was_explicit:
854
+ continue
855
+ plat_data, extra = _ensure_platform_extra_dict(platforms_data, plat.value)
856
+ if enabled_was_explicit:
857
+ plat_data["enabled"] = platform_cfg["enabled"]
858
+ if plat == Platform.SLACK and enabled_was_explicit:
859
+ extra["_enabled_explicit"] = True
860
+ extra.update(bridged)
861
+
862
+ # Plugin-owned YAML→env config bridges (#24836). See
863
+ # ``PlatformEntry.apply_yaml_config_fn`` for the hook contract.
864
+ # Order: shared-key loop (above) → this dispatch → legacy hardcoded
865
+ # blocks (below; no-op when a hook already set their env var) →
866
+ # ``_apply_env_overrides()`` after ``GatewayConfig.from_dict``.
867
+ if _pr is not None:
868
+ for entry in _pr.all_entries():
869
+ if entry.apply_yaml_config_fn is None:
870
+ continue
871
+ platform_cfg = yaml_cfg.get(entry.name)
872
+ if not isinstance(platform_cfg, dict):
873
+ continue
874
+ try:
875
+ seeded = entry.apply_yaml_config_fn(yaml_cfg, platform_cfg)
876
+ except Exception as e:
877
+ logger.debug(
878
+ "apply_yaml_config_fn for %s raised: %s",
879
+ entry.name, e,
880
+ )
881
+ continue
882
+ if not isinstance(seeded, dict) or not seeded:
883
+ continue
884
+ _, extra = _ensure_platform_extra_dict(platforms_data, entry.name)
885
+ extra.update(seeded)
886
+
887
+ # Slack settings → env vars (env vars take precedence)
888
+ slack_cfg = yaml_cfg.get("slack", {})
889
+ if isinstance(slack_cfg, dict):
890
+ if "require_mention" in slack_cfg and not os.getenv("SLACK_REQUIRE_MENTION"):
891
+ os.environ["SLACK_REQUIRE_MENTION"] = str(slack_cfg["require_mention"]).lower()
892
+ if "strict_mention" in slack_cfg and not os.getenv("SLACK_STRICT_MENTION"):
893
+ os.environ["SLACK_STRICT_MENTION"] = str(slack_cfg["strict_mention"]).lower()
894
+ if "allow_bots" in slack_cfg and not os.getenv("SLACK_ALLOW_BOTS"):
895
+ os.environ["SLACK_ALLOW_BOTS"] = str(slack_cfg["allow_bots"]).lower()
896
+ frc = slack_cfg.get("free_response_channels")
897
+ if frc is not None and not os.getenv("SLACK_FREE_RESPONSE_CHANNELS"):
898
+ if isinstance(frc, list):
899
+ frc = ",".join(str(v) for v in frc)
900
+ os.environ["SLACK_FREE_RESPONSE_CHANNELS"] = str(frc)
901
+ if "reactions" in slack_cfg and not os.getenv("SLACK_REACTIONS"):
902
+ os.environ["SLACK_REACTIONS"] = str(slack_cfg["reactions"]).lower()
903
+ # allowed_channels: if set, bot ONLY responds in these channels (whitelist)
904
+ ac = slack_cfg.get("allowed_channels")
905
+ if ac is not None and not os.getenv("SLACK_ALLOWED_CHANNELS"):
906
+ if isinstance(ac, list):
907
+ ac = ",".join(str(v) for v in ac)
908
+ os.environ["SLACK_ALLOWED_CHANNELS"] = str(ac)
909
+
910
+ # Discord settings → env vars (env vars take precedence)
911
+ discord_cfg = yaml_cfg.get("discord", {})
912
+ if isinstance(discord_cfg, dict):
913
+ if "require_mention" in discord_cfg and not os.getenv("DISCORD_REQUIRE_MENTION"):
914
+ os.environ["DISCORD_REQUIRE_MENTION"] = str(discord_cfg["require_mention"]).lower()
915
+ if "thread_require_mention" in discord_cfg and not os.getenv("DISCORD_THREAD_REQUIRE_MENTION"):
916
+ os.environ["DISCORD_THREAD_REQUIRE_MENTION"] = str(discord_cfg["thread_require_mention"]).lower()
917
+ frc = discord_cfg.get("free_response_channels")
918
+ if frc is not None and not os.getenv("DISCORD_FREE_RESPONSE_CHANNELS"):
919
+ if isinstance(frc, list):
920
+ frc = ",".join(str(v) for v in frc)
921
+ os.environ["DISCORD_FREE_RESPONSE_CHANNELS"] = str(frc)
922
+ if "auto_thread" in discord_cfg and not os.getenv("DISCORD_AUTO_THREAD"):
923
+ os.environ["DISCORD_AUTO_THREAD"] = str(discord_cfg["auto_thread"]).lower()
924
+ if "reactions" in discord_cfg and not os.getenv("DISCORD_REACTIONS"):
925
+ os.environ["DISCORD_REACTIONS"] = str(discord_cfg["reactions"]).lower()
926
+ # ignored_channels: channels where bot never responds (even when mentioned)
927
+ ic = discord_cfg.get("ignored_channels")
928
+ if ic is not None and not os.getenv("DISCORD_IGNORED_CHANNELS"):
929
+ if isinstance(ic, list):
930
+ ic = ",".join(str(v) for v in ic)
931
+ os.environ["DISCORD_IGNORED_CHANNELS"] = str(ic)
932
+ # allowed_channels: if set, bot ONLY responds in these channels (whitelist)
933
+ ac = discord_cfg.get("allowed_channels")
934
+ if ac is not None and not os.getenv("DISCORD_ALLOWED_CHANNELS"):
935
+ if isinstance(ac, list):
936
+ ac = ",".join(str(v) for v in ac)
937
+ os.environ["DISCORD_ALLOWED_CHANNELS"] = str(ac)
938
+ # no_thread_channels: channels where bot responds directly without creating thread
939
+ ntc = discord_cfg.get("no_thread_channels")
940
+ if ntc is not None and not os.getenv("DISCORD_NO_THREAD_CHANNELS"):
941
+ if isinstance(ntc, list):
942
+ ntc = ",".join(str(v) for v in ntc)
943
+ os.environ["DISCORD_NO_THREAD_CHANNELS"] = str(ntc)
944
+ # history_backfill: recover missed channel messages for shared sessions
945
+ # when require_mention is active. Fetches messages between bot turns
946
+ # and prepends them to the user message for context.
947
+ if "history_backfill" in discord_cfg and not os.getenv("DISCORD_HISTORY_BACKFILL"):
948
+ os.environ["DISCORD_HISTORY_BACKFILL"] = str(discord_cfg["history_backfill"]).lower()
949
+ hbl = discord_cfg.get("history_backfill_limit")
950
+ if hbl is not None and not os.getenv("DISCORD_HISTORY_BACKFILL_LIMIT"):
951
+ os.environ["DISCORD_HISTORY_BACKFILL_LIMIT"] = str(hbl)
952
+ # allow_mentions: granular control over what the bot can ping.
953
+ # Safe defaults (no @everyone/roles) are applied in the adapter;
954
+ # these YAML keys only override when set and let users opt back
955
+ # into unsafe modes (e.g. roles=true) if they actually want it.
956
+ allow_mentions_cfg = discord_cfg.get("allow_mentions")
957
+ if isinstance(allow_mentions_cfg, dict):
958
+ for yaml_key, env_key in (
959
+ ("everyone", "DISCORD_ALLOW_MENTION_EVERYONE"),
960
+ ("roles", "DISCORD_ALLOW_MENTION_ROLES"),
961
+ ("users", "DISCORD_ALLOW_MENTION_USERS"),
962
+ ("replied_user", "DISCORD_ALLOW_MENTION_REPLIED_USER"),
963
+ ):
964
+ if yaml_key in allow_mentions_cfg and not os.getenv(env_key):
965
+ os.environ[env_key] = str(allow_mentions_cfg[yaml_key]).lower()
966
+ # reply_to_mode: top-level preferred, falls back to extra.reply_to_mode
967
+ # YAML 1.1 parses bare 'off' as boolean False — coerce to string "off".
968
+ _discord_extra = discord_cfg.get("extra") if isinstance(discord_cfg.get("extra"), dict) else {}
969
+ _discord_rtm = (
970
+ discord_cfg["reply_to_mode"] if "reply_to_mode" in discord_cfg
971
+ else _discord_extra.get("reply_to_mode")
972
+ )
973
+ if _discord_rtm is not None and not os.getenv("DISCORD_REPLY_TO_MODE"):
974
+ _rtm_str = "off" if _discord_rtm is False else str(_discord_rtm).lower()
975
+ os.environ["DISCORD_REPLY_TO_MODE"] = _rtm_str
976
+
977
+ # Bridge top-level require_mention to Telegram when the telegram: section
978
+ # does not already provide one. Users often write "require_mention: true"
979
+ # at the top level alongside group_sessions_per_user, expecting it to work
980
+ # the same way (#3979).
981
+ _tl_require_mention = yaml_cfg.get("require_mention")
982
+ if _tl_require_mention is not None:
983
+ _tg_section = yaml_cfg.get("telegram") or {}
984
+ if "require_mention" not in _tg_section:
985
+ _tg_plat = platforms_data.setdefault(Platform.TELEGRAM.value, {})
986
+ _tg_extra = _tg_plat.setdefault("extra", {})
987
+ _tg_extra.setdefault("require_mention", _tl_require_mention)
988
+
989
+ # Telegram settings → env vars (env vars take precedence)
990
+ telegram_cfg = yaml_cfg.get("telegram", {})
991
+ if isinstance(telegram_cfg, dict):
992
+ # Prefer telegram.require_mention; fall back to the top-level shorthand.
993
+ _effective_rm = telegram_cfg.get("require_mention", yaml_cfg.get("require_mention"))
994
+ if _effective_rm is not None and not os.getenv("TELEGRAM_REQUIRE_MENTION"):
995
+ os.environ["TELEGRAM_REQUIRE_MENTION"] = str(_effective_rm).lower()
996
+ if "mention_patterns" in telegram_cfg and not os.getenv("TELEGRAM_MENTION_PATTERNS"):
997
+ os.environ["TELEGRAM_MENTION_PATTERNS"] = json.dumps(telegram_cfg["mention_patterns"])
998
+ if "guest_mode" in telegram_cfg and not os.getenv("TELEGRAM_GUEST_MODE"):
999
+ os.environ["TELEGRAM_GUEST_MODE"] = str(telegram_cfg["guest_mode"]).lower()
1000
+ frc = telegram_cfg.get("free_response_chats")
1001
+ if frc is not None and not os.getenv("TELEGRAM_FREE_RESPONSE_CHATS"):
1002
+ if isinstance(frc, list):
1003
+ frc = ",".join(str(v) for v in frc)
1004
+ os.environ["TELEGRAM_FREE_RESPONSE_CHATS"] = str(frc)
1005
+ # allowed_chats: if set, bot ONLY responds in these group chats (whitelist)
1006
+ ac = telegram_cfg.get("allowed_chats")
1007
+ if ac is not None and not os.getenv("TELEGRAM_ALLOWED_CHATS"):
1008
+ if isinstance(ac, list):
1009
+ ac = ",".join(str(v) for v in ac)
1010
+ os.environ["TELEGRAM_ALLOWED_CHATS"] = str(ac)
1011
+ ignored_threads = telegram_cfg.get("ignored_threads")
1012
+ if ignored_threads is not None and not os.getenv("TELEGRAM_IGNORED_THREADS"):
1013
+ if isinstance(ignored_threads, list):
1014
+ ignored_threads = ",".join(str(v) for v in ignored_threads)
1015
+ os.environ["TELEGRAM_IGNORED_THREADS"] = str(ignored_threads)
1016
+ if "reactions" in telegram_cfg and not os.getenv("TELEGRAM_REACTIONS"):
1017
+ os.environ["TELEGRAM_REACTIONS"] = str(telegram_cfg["reactions"]).lower()
1018
+ if "proxy_url" in telegram_cfg and not os.getenv("TELEGRAM_PROXY"):
1019
+ os.environ["TELEGRAM_PROXY"] = str(telegram_cfg["proxy_url"]).strip()
1020
+ # reply_to_mode: top-level preferred, falls back to extra.reply_to_mode
1021
+ # YAML 1.1 parses bare 'off' as boolean False — coerce to string "off".
1022
+ _telegram_extra = telegram_cfg.get("extra") if isinstance(telegram_cfg.get("extra"), dict) else {}
1023
+ _telegram_rtm = (
1024
+ telegram_cfg["reply_to_mode"] if "reply_to_mode" in telegram_cfg
1025
+ else _telegram_extra.get("reply_to_mode")
1026
+ )
1027
+ if _telegram_rtm is not None and not os.getenv("TELEGRAM_REPLY_TO_MODE"):
1028
+ _rtm_str = "off" if _telegram_rtm is False else str(_telegram_rtm).lower()
1029
+ os.environ["TELEGRAM_REPLY_TO_MODE"] = _rtm_str
1030
+ allowed_users = telegram_cfg.get("allow_from")
1031
+ if allowed_users is not None and not os.getenv("TELEGRAM_ALLOWED_USERS"):
1032
+ if isinstance(allowed_users, list):
1033
+ allowed_users = ",".join(str(v) for v in allowed_users)
1034
+ os.environ["TELEGRAM_ALLOWED_USERS"] = str(allowed_users)
1035
+ group_allowed_users = telegram_cfg.get("group_allow_from")
1036
+ if group_allowed_users is not None and not os.getenv("TELEGRAM_GROUP_ALLOWED_USERS"):
1037
+ if isinstance(group_allowed_users, list):
1038
+ group_allowed_users = ",".join(str(v) for v in group_allowed_users)
1039
+ os.environ["TELEGRAM_GROUP_ALLOWED_USERS"] = str(group_allowed_users)
1040
+ group_allowed_chats = telegram_cfg.get("group_allowed_chats")
1041
+ if group_allowed_chats is not None and not os.getenv("TELEGRAM_GROUP_ALLOWED_CHATS"):
1042
+ if isinstance(group_allowed_chats, list):
1043
+ group_allowed_chats = ",".join(str(v) for v in group_allowed_chats)
1044
+ os.environ["TELEGRAM_GROUP_ALLOWED_CHATS"] = str(group_allowed_chats)
1045
+ for _telegram_extra_key in ("guest_mode", "disable_link_previews"):
1046
+ if _telegram_extra_key in telegram_cfg:
1047
+ plat_data = platforms_data.setdefault(Platform.TELEGRAM.value, {})
1048
+ if not isinstance(plat_data, dict):
1049
+ plat_data = {}
1050
+ platforms_data[Platform.TELEGRAM.value] = plat_data
1051
+ extra = plat_data.setdefault("extra", {})
1052
+ if not isinstance(extra, dict):
1053
+ extra = {}
1054
+ plat_data["extra"] = extra
1055
+ extra[_telegram_extra_key] = telegram_cfg[_telegram_extra_key]
1056
+
1057
+ whatsapp_cfg = yaml_cfg.get("whatsapp", {})
1058
+ if isinstance(whatsapp_cfg, dict):
1059
+ if "require_mention" in whatsapp_cfg and not os.getenv("WHATSAPP_REQUIRE_MENTION"):
1060
+ os.environ["WHATSAPP_REQUIRE_MENTION"] = str(whatsapp_cfg["require_mention"]).lower()
1061
+ if "mention_patterns" in whatsapp_cfg and not os.getenv("WHATSAPP_MENTION_PATTERNS"):
1062
+ os.environ["WHATSAPP_MENTION_PATTERNS"] = json.dumps(whatsapp_cfg["mention_patterns"])
1063
+ frc = whatsapp_cfg.get("free_response_chats")
1064
+ if frc is not None and not os.getenv("WHATSAPP_FREE_RESPONSE_CHATS"):
1065
+ if isinstance(frc, list):
1066
+ frc = ",".join(str(v) for v in frc)
1067
+ os.environ["WHATSAPP_FREE_RESPONSE_CHATS"] = str(frc)
1068
+ if "dm_policy" in whatsapp_cfg and not os.getenv("WHATSAPP_DM_POLICY"):
1069
+ os.environ["WHATSAPP_DM_POLICY"] = str(whatsapp_cfg["dm_policy"]).lower()
1070
+ af = whatsapp_cfg.get("allow_from")
1071
+ if af is not None and not os.getenv("WHATSAPP_ALLOWED_USERS"):
1072
+ if isinstance(af, list):
1073
+ af = ",".join(str(v) for v in af)
1074
+ os.environ["WHATSAPP_ALLOWED_USERS"] = str(af)
1075
+ if "group_policy" in whatsapp_cfg and not os.getenv("WHATSAPP_GROUP_POLICY"):
1076
+ os.environ["WHATSAPP_GROUP_POLICY"] = str(whatsapp_cfg["group_policy"]).lower()
1077
+ gaf = whatsapp_cfg.get("group_allow_from")
1078
+ if gaf is not None and not os.getenv("WHATSAPP_GROUP_ALLOWED_USERS"):
1079
+ if isinstance(gaf, list):
1080
+ gaf = ",".join(str(v) for v in gaf)
1081
+ os.environ["WHATSAPP_GROUP_ALLOWED_USERS"] = str(gaf)
1082
+
1083
+ # DingTalk settings → env vars (env vars take precedence)
1084
+ dingtalk_cfg = yaml_cfg.get("dingtalk", {})
1085
+ if isinstance(dingtalk_cfg, dict):
1086
+ if "require_mention" in dingtalk_cfg and not os.getenv("DINGTALK_REQUIRE_MENTION"):
1087
+ os.environ["DINGTALK_REQUIRE_MENTION"] = str(dingtalk_cfg["require_mention"]).lower()
1088
+ if "mention_patterns" in dingtalk_cfg and not os.getenv("DINGTALK_MENTION_PATTERNS"):
1089
+ os.environ["DINGTALK_MENTION_PATTERNS"] = json.dumps(dingtalk_cfg["mention_patterns"])
1090
+ frc = dingtalk_cfg.get("free_response_chats")
1091
+ if frc is not None and not os.getenv("DINGTALK_FREE_RESPONSE_CHATS"):
1092
+ if isinstance(frc, list):
1093
+ frc = ",".join(str(v) for v in frc)
1094
+ os.environ["DINGTALK_FREE_RESPONSE_CHATS"] = str(frc)
1095
+ # allowed_chats: if set, bot ONLY responds in these group chats (whitelist)
1096
+ ac = dingtalk_cfg.get("allowed_chats")
1097
+ if ac is not None and not os.getenv("DINGTALK_ALLOWED_CHATS"):
1098
+ if isinstance(ac, list):
1099
+ ac = ",".join(str(v) for v in ac)
1100
+ os.environ["DINGTALK_ALLOWED_CHATS"] = str(ac)
1101
+ allowed = dingtalk_cfg.get("allowed_users")
1102
+ if allowed is not None and not os.getenv("DINGTALK_ALLOWED_USERS"):
1103
+ if isinstance(allowed, list):
1104
+ allowed = ",".join(str(v) for v in allowed)
1105
+ os.environ["DINGTALK_ALLOWED_USERS"] = str(allowed)
1106
+
1107
+ # Mattermost settings → env vars (env vars take precedence)
1108
+ mattermost_cfg = yaml_cfg.get("mattermost", {})
1109
+ if isinstance(mattermost_cfg, dict):
1110
+ if "require_mention" in mattermost_cfg and not os.getenv("MATTERMOST_REQUIRE_MENTION"):
1111
+ os.environ["MATTERMOST_REQUIRE_MENTION"] = str(mattermost_cfg["require_mention"]).lower()
1112
+ frc = mattermost_cfg.get("free_response_channels")
1113
+ if frc is not None and not os.getenv("MATTERMOST_FREE_RESPONSE_CHANNELS"):
1114
+ if isinstance(frc, list):
1115
+ frc = ",".join(str(v) for v in frc)
1116
+ os.environ["MATTERMOST_FREE_RESPONSE_CHANNELS"] = str(frc)
1117
+ # allowed_channels: if set, bot ONLY responds in these channels (whitelist)
1118
+ ac = mattermost_cfg.get("allowed_channels")
1119
+ if ac is not None and not os.getenv("MATTERMOST_ALLOWED_CHANNELS"):
1120
+ if isinstance(ac, list):
1121
+ ac = ",".join(str(v) for v in ac)
1122
+ os.environ["MATTERMOST_ALLOWED_CHANNELS"] = str(ac)
1123
+
1124
+ # Matrix settings → env vars (env vars take precedence)
1125
+ matrix_cfg = yaml_cfg.get("matrix", {})
1126
+ if isinstance(matrix_cfg, dict):
1127
+ if "require_mention" in matrix_cfg and not os.getenv("MATRIX_REQUIRE_MENTION"):
1128
+ os.environ["MATRIX_REQUIRE_MENTION"] = str(matrix_cfg["require_mention"]).lower()
1129
+ frc = matrix_cfg.get("free_response_rooms")
1130
+ if frc is not None and not os.getenv("MATRIX_FREE_RESPONSE_ROOMS"):
1131
+ if isinstance(frc, list):
1132
+ frc = ",".join(str(v) for v in frc)
1133
+ os.environ["MATRIX_FREE_RESPONSE_ROOMS"] = str(frc)
1134
+ # allowed_rooms: if set, bot ONLY responds in these rooms (whitelist)
1135
+ ar = matrix_cfg.get("allowed_rooms")
1136
+ if ar is not None and not os.getenv("MATRIX_ALLOWED_ROOMS"):
1137
+ if isinstance(ar, list):
1138
+ ar = ",".join(str(v) for v in ar)
1139
+ os.environ["MATRIX_ALLOWED_ROOMS"] = str(ar)
1140
+ if "auto_thread" in matrix_cfg and not os.getenv("MATRIX_AUTO_THREAD"):
1141
+ os.environ["MATRIX_AUTO_THREAD"] = str(matrix_cfg["auto_thread"]).lower()
1142
+ if "dm_mention_threads" in matrix_cfg and not os.getenv("MATRIX_DM_MENTION_THREADS"):
1143
+ os.environ["MATRIX_DM_MENTION_THREADS"] = str(matrix_cfg["dm_mention_threads"]).lower()
1144
+
1145
+ # Feishu settings → env vars (env vars take precedence)
1146
+ feishu_cfg = yaml_cfg.get("feishu", {})
1147
+ if isinstance(feishu_cfg, dict):
1148
+ if "allow_bots" in feishu_cfg and not os.getenv("FEISHU_ALLOW_BOTS"):
1149
+ os.environ["FEISHU_ALLOW_BOTS"] = str(feishu_cfg["allow_bots"]).lower()
1150
+
1151
+ except Exception as e:
1152
+ logger.warning(
1153
+ "Failed to process config.yaml — falling back to .env / gateway.json values. "
1154
+ "Check %s for syntax errors. Error: %s",
1155
+ _home / "config.yaml",
1156
+ e,
1157
+ )
1158
+
1159
+ config = GatewayConfig.from_dict(gw_data)
1160
+
1161
+ # Override with environment variables
1162
+ _apply_env_overrides(config)
1163
+
1164
+ # --- Validate loaded values ---
1165
+ _validate_gateway_config(config)
1166
+
1167
+ return config
1168
+
1169
+
1170
+ def _validate_gateway_config(config: "GatewayConfig") -> None:
1171
+ """Validate and sanitize a loaded GatewayConfig in place.
1172
+
1173
+ Called by ``load_gateway_config()`` after all config sources are merged.
1174
+ Extracted as a separate function for testability.
1175
+ """
1176
+ policy = config.default_reset_policy
1177
+
1178
+ if not (0 <= policy.at_hour <= 23):
1179
+ logger.warning(
1180
+ "Invalid at_hour=%s (must be 0-23). Using default 4.", policy.at_hour
1181
+ )
1182
+ policy.at_hour = 4
1183
+
1184
+ if policy.idle_minutes is None or policy.idle_minutes <= 0:
1185
+ logger.warning(
1186
+ "Invalid idle_minutes=%s (must be positive). Using default 1440.",
1187
+ policy.idle_minutes,
1188
+ )
1189
+ policy.idle_minutes = 1440
1190
+
1191
+ # Warn about empty bot tokens — platforms that loaded an empty string
1192
+ # won't connect and the cause can be confusing without a log line.
1193
+ _token_env_names = {
1194
+ Platform.TELEGRAM: "TELEGRAM_BOT_TOKEN",
1195
+ Platform.DISCORD: "DISCORD_BOT_TOKEN",
1196
+ Platform.SLACK: "SLACK_BOT_TOKEN",
1197
+ Platform.MATTERMOST: "MATTERMOST_TOKEN",
1198
+ Platform.MATRIX: "MATRIX_ACCESS_TOKEN",
1199
+ Platform.WEIXIN: "WEIXIN_TOKEN",
1200
+ }
1201
+ for platform, pconfig in config.platforms.items():
1202
+ if not pconfig.enabled:
1203
+ continue
1204
+ env_name = _token_env_names.get(platform)
1205
+ if env_name and pconfig.token is not None and not pconfig.token.strip():
1206
+ logger.warning(
1207
+ "%s is enabled but %s is empty. "
1208
+ "The adapter will likely fail to connect.",
1209
+ platform.value, env_name,
1210
+ )
1211
+
1212
+ # Reject known-weak placeholder tokens.
1213
+ # Ported from openclaw/openclaw#64586: users who copy .env.example
1214
+ # without changing placeholder values get a clear startup error instead
1215
+ # of a confusing "auth failed" from the platform API.
1216
+ try:
1217
+ from hermes_cli.auth import has_usable_secret
1218
+ except ImportError:
1219
+ has_usable_secret = None # type: ignore[assignment]
1220
+
1221
+ if has_usable_secret is not None:
1222
+ for platform, pconfig in config.platforms.items():
1223
+ if not pconfig.enabled:
1224
+ continue
1225
+ env_name = _token_env_names.get(platform)
1226
+ if not env_name:
1227
+ continue
1228
+ token = pconfig.token
1229
+ if token and token.strip() and not has_usable_secret(token, min_length=4):
1230
+ logger.error(
1231
+ "%s is enabled but %s is set to a placeholder value ('%s'). "
1232
+ "Set a real bot token before starting the gateway. "
1233
+ "The adapter will NOT be started.",
1234
+ platform.value, env_name, token.strip()[:6] + "...",
1235
+ )
1236
+ pconfig.enabled = False
1237
+
1238
+
1239
+ def _apply_env_overrides(config: GatewayConfig) -> None:
1240
+ """Apply environment variable overrides to config."""
1241
+
1242
+ # Telegram
1243
+ telegram_token = os.getenv("TELEGRAM_BOT_TOKEN")
1244
+ if telegram_token:
1245
+ if Platform.TELEGRAM not in config.platforms:
1246
+ config.platforms[Platform.TELEGRAM] = PlatformConfig()
1247
+ config.platforms[Platform.TELEGRAM].enabled = True
1248
+ config.platforms[Platform.TELEGRAM].token = telegram_token
1249
+
1250
+ # Reply threading mode for Telegram (off/first/all)
1251
+ telegram_reply_mode = os.getenv("TELEGRAM_REPLY_TO_MODE", "").lower()
1252
+ if telegram_reply_mode in {"off", "first", "all"}:
1253
+ if Platform.TELEGRAM not in config.platforms:
1254
+ config.platforms[Platform.TELEGRAM] = PlatformConfig()
1255
+ config.platforms[Platform.TELEGRAM].reply_to_mode = telegram_reply_mode
1256
+
1257
+ telegram_fallback_ips = os.getenv("TELEGRAM_FALLBACK_IPS", "")
1258
+ if telegram_fallback_ips:
1259
+ if Platform.TELEGRAM not in config.platforms:
1260
+ config.platforms[Platform.TELEGRAM] = PlatformConfig()
1261
+ config.platforms[Platform.TELEGRAM].extra["fallback_ips"] = [
1262
+ ip.strip() for ip in telegram_fallback_ips.split(",") if ip.strip()
1263
+ ]
1264
+
1265
+ telegram_home = os.getenv("TELEGRAM_HOME_CHANNEL")
1266
+ if telegram_home and Platform.TELEGRAM in config.platforms:
1267
+ config.platforms[Platform.TELEGRAM].home_channel = HomeChannel(
1268
+ platform=Platform.TELEGRAM,
1269
+ chat_id=telegram_home,
1270
+ name=os.getenv("TELEGRAM_HOME_CHANNEL_NAME", "Home"),
1271
+ thread_id=os.getenv("TELEGRAM_HOME_CHANNEL_THREAD_ID") or None,
1272
+ )
1273
+
1274
+ # Discord
1275
+ discord_token = os.getenv("DISCORD_BOT_TOKEN")
1276
+ if discord_token:
1277
+ if Platform.DISCORD not in config.platforms:
1278
+ config.platforms[Platform.DISCORD] = PlatformConfig()
1279
+ config.platforms[Platform.DISCORD].enabled = True
1280
+ config.platforms[Platform.DISCORD].token = discord_token
1281
+
1282
+ discord_home = os.getenv("DISCORD_HOME_CHANNEL")
1283
+ if discord_home and Platform.DISCORD in config.platforms:
1284
+ config.platforms[Platform.DISCORD].home_channel = HomeChannel(
1285
+ platform=Platform.DISCORD,
1286
+ chat_id=discord_home,
1287
+ name=os.getenv("DISCORD_HOME_CHANNEL_NAME", "Home"),
1288
+ thread_id=os.getenv("DISCORD_HOME_CHANNEL_THREAD_ID") or None,
1289
+ )
1290
+
1291
+ # Reply threading mode for Discord (off/first/all)
1292
+ discord_reply_mode = os.getenv("DISCORD_REPLY_TO_MODE", "").lower()
1293
+ if discord_reply_mode in {"off", "first", "all"}:
1294
+ if Platform.DISCORD not in config.platforms:
1295
+ config.platforms[Platform.DISCORD] = PlatformConfig()
1296
+ config.platforms[Platform.DISCORD].reply_to_mode = discord_reply_mode
1297
+
1298
+ # WhatsApp (typically uses different auth mechanism)
1299
+ whatsapp_enabled = os.getenv("WHATSAPP_ENABLED", "").lower() in {"true", "1", "yes"}
1300
+ whatsapp_disabled_explicitly = os.getenv("WHATSAPP_ENABLED", "").lower() in {"false", "0", "no"}
1301
+ if Platform.WHATSAPP in config.platforms:
1302
+ # YAML config exists — respect explicit disable
1303
+ wa_cfg = config.platforms[Platform.WHATSAPP]
1304
+ if whatsapp_disabled_explicitly:
1305
+ wa_cfg.enabled = False
1306
+ elif whatsapp_enabled:
1307
+ wa_cfg.enabled = True
1308
+ # else: keep whatever the YAML set
1309
+ elif whatsapp_enabled:
1310
+ config.platforms[Platform.WHATSAPP] = PlatformConfig(enabled=True)
1311
+ whatsapp_home = os.getenv("WHATSAPP_HOME_CHANNEL")
1312
+ if whatsapp_home and Platform.WHATSAPP in config.platforms:
1313
+ config.platforms[Platform.WHATSAPP].home_channel = HomeChannel(
1314
+ platform=Platform.WHATSAPP,
1315
+ chat_id=whatsapp_home,
1316
+ name=os.getenv("WHATSAPP_HOME_CHANNEL_NAME", "Home"),
1317
+ thread_id=os.getenv("WHATSAPP_HOME_CHANNEL_THREAD_ID") or None,
1318
+ )
1319
+
1320
+ # Slack
1321
+ slack_token = os.getenv("SLACK_BOT_TOKEN")
1322
+ if slack_token:
1323
+ if Platform.SLACK not in config.platforms:
1324
+ # No yaml config for Slack — env-only setup, enable it
1325
+ config.platforms[Platform.SLACK] = PlatformConfig()
1326
+ config.platforms[Platform.SLACK].enabled = True
1327
+ else:
1328
+ slack_config = config.platforms[Platform.SLACK]
1329
+ enabled_was_explicit = bool(slack_config.extra.pop("_enabled_explicit", False))
1330
+ if not slack_config.enabled and not enabled_was_explicit:
1331
+ # Top-level Slack settings such as channel prompts should not
1332
+ # turn an env-token setup into a disabled platform. Only an
1333
+ # explicit slack.enabled/platforms.slack.enabled false should.
1334
+ slack_config.enabled = True
1335
+ # If yaml config exists, respect its enabled flag (don't override
1336
+ # explicit enabled: false). Token is still stored so skills that
1337
+ # send Slack messages can use it without activating the gateway adapter.
1338
+ config.platforms[Platform.SLACK].token = slack_token
1339
+ slack_home = os.getenv("SLACK_HOME_CHANNEL")
1340
+ if slack_home and Platform.SLACK in config.platforms:
1341
+ config.platforms[Platform.SLACK].home_channel = HomeChannel(
1342
+ platform=Platform.SLACK,
1343
+ chat_id=slack_home,
1344
+ name=os.getenv("SLACK_HOME_CHANNEL_NAME", ""),
1345
+ thread_id=os.getenv("SLACK_HOME_CHANNEL_THREAD_ID") or None,
1346
+ )
1347
+
1348
+ # Signal
1349
+ signal_url = os.getenv("SIGNAL_HTTP_URL")
1350
+ signal_account = os.getenv("SIGNAL_ACCOUNT")
1351
+ if signal_url and signal_account:
1352
+ if Platform.SIGNAL not in config.platforms:
1353
+ config.platforms[Platform.SIGNAL] = PlatformConfig()
1354
+ config.platforms[Platform.SIGNAL].enabled = True
1355
+ config.platforms[Platform.SIGNAL].extra.update({
1356
+ "http_url": signal_url,
1357
+ "account": signal_account,
1358
+ "ignore_stories": os.getenv("SIGNAL_IGNORE_STORIES", "true").lower() in {"true", "1", "yes"},
1359
+ })
1360
+ signal_home = os.getenv("SIGNAL_HOME_CHANNEL")
1361
+ if signal_home and Platform.SIGNAL in config.platforms:
1362
+ config.platforms[Platform.SIGNAL].home_channel = HomeChannel(
1363
+ platform=Platform.SIGNAL,
1364
+ chat_id=signal_home,
1365
+ name=os.getenv("SIGNAL_HOME_CHANNEL_NAME", "Home"),
1366
+ thread_id=os.getenv("SIGNAL_HOME_CHANNEL_THREAD_ID") or None,
1367
+ )
1368
+
1369
+ # Mattermost
1370
+ mattermost_token = os.getenv("MATTERMOST_TOKEN")
1371
+ if mattermost_token:
1372
+ mattermost_url = os.getenv("MATTERMOST_URL", "")
1373
+ if not mattermost_url:
1374
+ logger.warning("MATTERMOST_TOKEN set but MATTERMOST_URL is missing")
1375
+ if Platform.MATTERMOST not in config.platforms:
1376
+ config.platforms[Platform.MATTERMOST] = PlatformConfig()
1377
+ config.platforms[Platform.MATTERMOST].enabled = True
1378
+ config.platforms[Platform.MATTERMOST].token = mattermost_token
1379
+ config.platforms[Platform.MATTERMOST].extra["url"] = mattermost_url
1380
+ mattermost_home = os.getenv("MATTERMOST_HOME_CHANNEL")
1381
+ if mattermost_home and Platform.MATTERMOST in config.platforms:
1382
+ config.platforms[Platform.MATTERMOST].home_channel = HomeChannel(
1383
+ platform=Platform.MATTERMOST,
1384
+ chat_id=mattermost_home,
1385
+ name=os.getenv("MATTERMOST_HOME_CHANNEL_NAME", "Home"),
1386
+ thread_id=os.getenv("MATTERMOST_HOME_CHANNEL_THREAD_ID") or None,
1387
+ )
1388
+
1389
+ # Matrix
1390
+ matrix_token = os.getenv("MATRIX_ACCESS_TOKEN")
1391
+ matrix_homeserver = os.getenv("MATRIX_HOMESERVER", "")
1392
+ if matrix_token or os.getenv("MATRIX_PASSWORD"):
1393
+ if not matrix_homeserver:
1394
+ logger.warning("MATRIX_ACCESS_TOKEN/MATRIX_PASSWORD set but MATRIX_HOMESERVER is missing")
1395
+ if Platform.MATRIX not in config.platforms:
1396
+ config.platforms[Platform.MATRIX] = PlatformConfig()
1397
+ config.platforms[Platform.MATRIX].enabled = True
1398
+ if matrix_token:
1399
+ config.platforms[Platform.MATRIX].token = matrix_token
1400
+ config.platforms[Platform.MATRIX].extra["homeserver"] = matrix_homeserver
1401
+ matrix_user = os.getenv("MATRIX_USER_ID", "")
1402
+ if matrix_user:
1403
+ config.platforms[Platform.MATRIX].extra["user_id"] = matrix_user
1404
+ matrix_password = os.getenv("MATRIX_PASSWORD", "")
1405
+ if matrix_password:
1406
+ config.platforms[Platform.MATRIX].extra["password"] = matrix_password
1407
+ matrix_e2ee = os.getenv("MATRIX_ENCRYPTION", "").lower() in {"true", "1", "yes"}
1408
+ config.platforms[Platform.MATRIX].extra["encryption"] = matrix_e2ee
1409
+ matrix_device_id = os.getenv("MATRIX_DEVICE_ID", "")
1410
+ if matrix_device_id:
1411
+ config.platforms[Platform.MATRIX].extra["device_id"] = matrix_device_id
1412
+ matrix_home = os.getenv("MATRIX_HOME_ROOM")
1413
+ if matrix_home and Platform.MATRIX in config.platforms:
1414
+ config.platforms[Platform.MATRIX].home_channel = HomeChannel(
1415
+ platform=Platform.MATRIX,
1416
+ chat_id=matrix_home,
1417
+ name=os.getenv("MATRIX_HOME_ROOM_NAME", "Home"),
1418
+ thread_id=os.getenv("MATRIX_HOME_ROOM_THREAD_ID") or None,
1419
+ )
1420
+
1421
+ # Home Assistant
1422
+ hass_token = os.getenv("HASS_TOKEN")
1423
+ if hass_token:
1424
+ if Platform.HOMEASSISTANT not in config.platforms:
1425
+ config.platforms[Platform.HOMEASSISTANT] = PlatformConfig()
1426
+ config.platforms[Platform.HOMEASSISTANT].enabled = True
1427
+ config.platforms[Platform.HOMEASSISTANT].token = hass_token
1428
+ hass_url = os.getenv("HASS_URL")
1429
+ if hass_url:
1430
+ config.platforms[Platform.HOMEASSISTANT].extra["url"] = hass_url
1431
+
1432
+ # Email
1433
+ email_addr = os.getenv("EMAIL_ADDRESS")
1434
+ email_pwd = os.getenv("EMAIL_PASSWORD")
1435
+ email_imap = os.getenv("EMAIL_IMAP_HOST")
1436
+ email_smtp = os.getenv("EMAIL_SMTP_HOST")
1437
+ if all([email_addr, email_pwd, email_imap, email_smtp]):
1438
+ if Platform.EMAIL not in config.platforms:
1439
+ config.platforms[Platform.EMAIL] = PlatformConfig()
1440
+ config.platforms[Platform.EMAIL].enabled = True
1441
+ config.platforms[Platform.EMAIL].extra.update({
1442
+ "address": email_addr,
1443
+ "imap_host": email_imap,
1444
+ "smtp_host": email_smtp,
1445
+ })
1446
+ email_home = os.getenv("EMAIL_HOME_ADDRESS")
1447
+ if email_home and Platform.EMAIL in config.platforms:
1448
+ config.platforms[Platform.EMAIL].home_channel = HomeChannel(
1449
+ platform=Platform.EMAIL,
1450
+ chat_id=email_home,
1451
+ name=os.getenv("EMAIL_HOME_ADDRESS_NAME", "Home"),
1452
+ thread_id=os.getenv("EMAIL_HOME_ADDRESS_THREAD_ID") or None,
1453
+ )
1454
+
1455
+ # SMS (Twilio)
1456
+ twilio_sid = os.getenv("TWILIO_ACCOUNT_SID")
1457
+ if twilio_sid:
1458
+ if Platform.SMS not in config.platforms:
1459
+ config.platforms[Platform.SMS] = PlatformConfig()
1460
+ config.platforms[Platform.SMS].enabled = True
1461
+ config.platforms[Platform.SMS].api_key = os.getenv("TWILIO_AUTH_TOKEN", "")
1462
+ sms_home = os.getenv("SMS_HOME_CHANNEL")
1463
+ if sms_home and Platform.SMS in config.platforms:
1464
+ config.platforms[Platform.SMS].home_channel = HomeChannel(
1465
+ platform=Platform.SMS,
1466
+ chat_id=sms_home,
1467
+ name=os.getenv("SMS_HOME_CHANNEL_NAME", "Home"),
1468
+ thread_id=os.getenv("SMS_HOME_CHANNEL_THREAD_ID") or None,
1469
+ )
1470
+
1471
+ # API Server
1472
+ api_server_enabled = os.getenv("API_SERVER_ENABLED", "").lower() in {"true", "1", "yes"}
1473
+ api_server_key = os.getenv("API_SERVER_KEY", "")
1474
+ api_server_cors_origins = os.getenv("API_SERVER_CORS_ORIGINS", "")
1475
+ api_server_port = os.getenv("API_SERVER_PORT")
1476
+ api_server_host = os.getenv("API_SERVER_HOST")
1477
+ if api_server_enabled or api_server_key:
1478
+ if Platform.API_SERVER not in config.platforms:
1479
+ config.platforms[Platform.API_SERVER] = PlatformConfig()
1480
+ config.platforms[Platform.API_SERVER].enabled = True
1481
+ if api_server_key:
1482
+ config.platforms[Platform.API_SERVER].extra["key"] = api_server_key
1483
+ if api_server_cors_origins:
1484
+ origins = [origin.strip() for origin in api_server_cors_origins.split(",") if origin.strip()]
1485
+ if origins:
1486
+ config.platforms[Platform.API_SERVER].extra["cors_origins"] = origins
1487
+ if api_server_port:
1488
+ try:
1489
+ config.platforms[Platform.API_SERVER].extra["port"] = int(api_server_port)
1490
+ except ValueError:
1491
+ pass
1492
+ if api_server_host:
1493
+ config.platforms[Platform.API_SERVER].extra["host"] = api_server_host
1494
+ api_server_model_name = os.getenv("API_SERVER_MODEL_NAME", "")
1495
+ if api_server_model_name:
1496
+ config.platforms[Platform.API_SERVER].extra["model_name"] = api_server_model_name
1497
+
1498
+ # Webhook platform
1499
+ webhook_enabled = os.getenv("WEBHOOK_ENABLED", "").lower() in {"true", "1", "yes"}
1500
+ webhook_port = os.getenv("WEBHOOK_PORT")
1501
+ webhook_secret = os.getenv("WEBHOOK_SECRET", "")
1502
+ if webhook_enabled:
1503
+ if Platform.WEBHOOK not in config.platforms:
1504
+ config.platforms[Platform.WEBHOOK] = PlatformConfig()
1505
+ config.platforms[Platform.WEBHOOK].enabled = True
1506
+ if webhook_port:
1507
+ try:
1508
+ config.platforms[Platform.WEBHOOK].extra["port"] = int(webhook_port)
1509
+ except ValueError:
1510
+ pass
1511
+ if webhook_secret:
1512
+ config.platforms[Platform.WEBHOOK].extra["secret"] = webhook_secret
1513
+
1514
+ # Microsoft Graph webhook platform
1515
+ msgraph_webhook_enabled = os.getenv("MSGRAPH_WEBHOOK_ENABLED", "").lower() in {
1516
+ "true",
1517
+ "1",
1518
+ "yes",
1519
+ }
1520
+ msgraph_webhook_port = os.getenv("MSGRAPH_WEBHOOK_PORT")
1521
+ msgraph_webhook_client_state = os.getenv("MSGRAPH_WEBHOOK_CLIENT_STATE", "")
1522
+ msgraph_webhook_resources = os.getenv("MSGRAPH_WEBHOOK_ACCEPTED_RESOURCES", "")
1523
+ msgraph_webhook_allowed_cidrs = os.getenv(
1524
+ "MSGRAPH_WEBHOOK_ALLOWED_SOURCE_CIDRS", ""
1525
+ )
1526
+ if (
1527
+ msgraph_webhook_enabled
1528
+ or Platform.MSGRAPH_WEBHOOK in config.platforms
1529
+ or msgraph_webhook_port
1530
+ or msgraph_webhook_client_state
1531
+ or msgraph_webhook_resources
1532
+ or msgraph_webhook_allowed_cidrs
1533
+ ):
1534
+ if Platform.MSGRAPH_WEBHOOK not in config.platforms:
1535
+ config.platforms[Platform.MSGRAPH_WEBHOOK] = PlatformConfig()
1536
+ if msgraph_webhook_enabled:
1537
+ config.platforms[Platform.MSGRAPH_WEBHOOK].enabled = True
1538
+ if msgraph_webhook_port:
1539
+ try:
1540
+ config.platforms[Platform.MSGRAPH_WEBHOOK].extra["port"] = int(
1541
+ msgraph_webhook_port
1542
+ )
1543
+ except ValueError:
1544
+ pass
1545
+ if msgraph_webhook_client_state:
1546
+ config.platforms[Platform.MSGRAPH_WEBHOOK].extra["client_state"] = (
1547
+ msgraph_webhook_client_state
1548
+ )
1549
+ if msgraph_webhook_resources:
1550
+ resources = [
1551
+ resource.strip()
1552
+ for resource in msgraph_webhook_resources.split(",")
1553
+ if resource.strip()
1554
+ ]
1555
+ if resources:
1556
+ config.platforms[Platform.MSGRAPH_WEBHOOK].extra[
1557
+ "accepted_resources"
1558
+ ] = resources
1559
+ if msgraph_webhook_allowed_cidrs:
1560
+ cidrs = [
1561
+ cidr.strip()
1562
+ for cidr in msgraph_webhook_allowed_cidrs.split(",")
1563
+ if cidr.strip()
1564
+ ]
1565
+ if cidrs:
1566
+ config.platforms[Platform.MSGRAPH_WEBHOOK].extra[
1567
+ "allowed_source_cidrs"
1568
+ ] = cidrs
1569
+
1570
+ # DingTalk
1571
+ dingtalk_client_id = os.getenv("DINGTALK_CLIENT_ID")
1572
+ dingtalk_client_secret = os.getenv("DINGTALK_CLIENT_SECRET")
1573
+ if dingtalk_client_id and dingtalk_client_secret:
1574
+ if Platform.DINGTALK not in config.platforms:
1575
+ config.platforms[Platform.DINGTALK] = PlatformConfig()
1576
+ config.platforms[Platform.DINGTALK].enabled = True
1577
+ config.platforms[Platform.DINGTALK].extra.update({
1578
+ "client_id": dingtalk_client_id,
1579
+ "client_secret": dingtalk_client_secret,
1580
+ })
1581
+ dingtalk_home = os.getenv("DINGTALK_HOME_CHANNEL")
1582
+ if dingtalk_home:
1583
+ config.platforms[Platform.DINGTALK].home_channel = HomeChannel(
1584
+ platform=Platform.DINGTALK,
1585
+ chat_id=dingtalk_home,
1586
+ name=os.getenv("DINGTALK_HOME_CHANNEL_NAME", "Home"),
1587
+ thread_id=os.getenv("DINGTALK_HOME_CHANNEL_THREAD_ID") or None,
1588
+ )
1589
+
1590
+ # Feishu / Lark
1591
+ feishu_app_id = os.getenv("FEISHU_APP_ID")
1592
+ feishu_app_secret = os.getenv("FEISHU_APP_SECRET")
1593
+ if feishu_app_id and feishu_app_secret:
1594
+ if Platform.FEISHU not in config.platforms:
1595
+ config.platforms[Platform.FEISHU] = PlatformConfig()
1596
+ config.platforms[Platform.FEISHU].enabled = True
1597
+ config.platforms[Platform.FEISHU].extra.update({
1598
+ "app_id": feishu_app_id,
1599
+ "app_secret": feishu_app_secret,
1600
+ "domain": os.getenv("FEISHU_DOMAIN", "feishu"),
1601
+ "connection_mode": os.getenv("FEISHU_CONNECTION_MODE", "websocket"),
1602
+ })
1603
+ feishu_encrypt_key = os.getenv("FEISHU_ENCRYPT_KEY", "")
1604
+ if feishu_encrypt_key:
1605
+ config.platforms[Platform.FEISHU].extra["encrypt_key"] = feishu_encrypt_key
1606
+ feishu_verification_token = os.getenv("FEISHU_VERIFICATION_TOKEN", "")
1607
+ if feishu_verification_token:
1608
+ config.platforms[Platform.FEISHU].extra["verification_token"] = feishu_verification_token
1609
+ feishu_home = os.getenv("FEISHU_HOME_CHANNEL")
1610
+ if feishu_home:
1611
+ config.platforms[Platform.FEISHU].home_channel = HomeChannel(
1612
+ platform=Platform.FEISHU,
1613
+ chat_id=feishu_home,
1614
+ name=os.getenv("FEISHU_HOME_CHANNEL_NAME", "Home"),
1615
+ thread_id=os.getenv("FEISHU_HOME_CHANNEL_THREAD_ID") or None,
1616
+ )
1617
+
1618
+ # WeCom (Enterprise WeChat)
1619
+ wecom_bot_id = os.getenv("WECOM_BOT_ID")
1620
+ wecom_secret = os.getenv("WECOM_SECRET")
1621
+ if wecom_bot_id and wecom_secret:
1622
+ if Platform.WECOM not in config.platforms:
1623
+ config.platforms[Platform.WECOM] = PlatformConfig()
1624
+ config.platforms[Platform.WECOM].enabled = True
1625
+ config.platforms[Platform.WECOM].extra.update({
1626
+ "bot_id": wecom_bot_id,
1627
+ "secret": wecom_secret,
1628
+ })
1629
+ wecom_ws_url = os.getenv("WECOM_WEBSOCKET_URL", "")
1630
+ if wecom_ws_url:
1631
+ config.platforms[Platform.WECOM].extra["websocket_url"] = wecom_ws_url
1632
+ wecom_home = os.getenv("WECOM_HOME_CHANNEL")
1633
+ if wecom_home:
1634
+ config.platforms[Platform.WECOM].home_channel = HomeChannel(
1635
+ platform=Platform.WECOM,
1636
+ chat_id=wecom_home,
1637
+ name=os.getenv("WECOM_HOME_CHANNEL_NAME", "Home"),
1638
+ thread_id=os.getenv("WECOM_HOME_CHANNEL_THREAD_ID") or None,
1639
+ )
1640
+
1641
+ # WeCom callback mode (self-built apps)
1642
+ wecom_callback_corp_id = os.getenv("WECOM_CALLBACK_CORP_ID")
1643
+ wecom_callback_corp_secret = os.getenv("WECOM_CALLBACK_CORP_SECRET")
1644
+ if wecom_callback_corp_id and wecom_callback_corp_secret:
1645
+ if Platform.WECOM_CALLBACK not in config.platforms:
1646
+ config.platforms[Platform.WECOM_CALLBACK] = PlatformConfig()
1647
+ config.platforms[Platform.WECOM_CALLBACK].enabled = True
1648
+ config.platforms[Platform.WECOM_CALLBACK].extra.update({
1649
+ "corp_id": wecom_callback_corp_id,
1650
+ "corp_secret": wecom_callback_corp_secret,
1651
+ "agent_id": os.getenv("WECOM_CALLBACK_AGENT_ID", ""),
1652
+ "token": os.getenv("WECOM_CALLBACK_TOKEN", ""),
1653
+ "encoding_aes_key": os.getenv("WECOM_CALLBACK_ENCODING_AES_KEY", ""),
1654
+ "host": os.getenv("WECOM_CALLBACK_HOST", "0.0.0.0"),
1655
+ "port": int(os.getenv("WECOM_CALLBACK_PORT", "8645")),
1656
+ })
1657
+
1658
+ # Weixin (personal WeChat via iLink Bot API)
1659
+ weixin_token = os.getenv("WEIXIN_TOKEN")
1660
+ weixin_account_id = os.getenv("WEIXIN_ACCOUNT_ID")
1661
+ if weixin_token or weixin_account_id:
1662
+ if Platform.WEIXIN not in config.platforms:
1663
+ config.platforms[Platform.WEIXIN] = PlatformConfig()
1664
+ config.platforms[Platform.WEIXIN].enabled = True
1665
+ if weixin_token:
1666
+ config.platforms[Platform.WEIXIN].token = weixin_token
1667
+ extra = config.platforms[Platform.WEIXIN].extra
1668
+ if weixin_account_id:
1669
+ extra["account_id"] = weixin_account_id
1670
+ weixin_base_url = os.getenv("WEIXIN_BASE_URL", "").strip()
1671
+ if weixin_base_url:
1672
+ extra["base_url"] = weixin_base_url.rstrip("/")
1673
+ weixin_cdn_base_url = os.getenv("WEIXIN_CDN_BASE_URL", "").strip()
1674
+ if weixin_cdn_base_url:
1675
+ extra["cdn_base_url"] = weixin_cdn_base_url.rstrip("/")
1676
+ weixin_dm_policy = os.getenv("WEIXIN_DM_POLICY", "").strip().lower()
1677
+ if weixin_dm_policy:
1678
+ extra["dm_policy"] = weixin_dm_policy
1679
+ weixin_group_policy = os.getenv("WEIXIN_GROUP_POLICY", "").strip().lower()
1680
+ if weixin_group_policy:
1681
+ extra["group_policy"] = weixin_group_policy
1682
+ weixin_allowed_users = os.getenv("WEIXIN_ALLOWED_USERS", "").strip()
1683
+ if weixin_allowed_users:
1684
+ extra["allow_from"] = weixin_allowed_users
1685
+ weixin_group_allowed_users = os.getenv("WEIXIN_GROUP_ALLOWED_USERS", "").strip()
1686
+ if weixin_group_allowed_users:
1687
+ extra["group_allow_from"] = weixin_group_allowed_users
1688
+ weixin_split_multiline = os.getenv("WEIXIN_SPLIT_MULTILINE_MESSAGES", "").strip()
1689
+ if weixin_split_multiline:
1690
+ extra["split_multiline_messages"] = weixin_split_multiline
1691
+ weixin_home = os.getenv("WEIXIN_HOME_CHANNEL", "").strip()
1692
+ if weixin_home:
1693
+ config.platforms[Platform.WEIXIN].home_channel = HomeChannel(
1694
+ platform=Platform.WEIXIN,
1695
+ chat_id=weixin_home,
1696
+ name=os.getenv("WEIXIN_HOME_CHANNEL_NAME", "Home"),
1697
+ thread_id=os.getenv("WEIXIN_HOME_CHANNEL_THREAD_ID") or None,
1698
+ )
1699
+
1700
+ # BlueBubbles (iMessage)
1701
+ bluebubbles_server_url = os.getenv("BLUEBUBBLES_SERVER_URL")
1702
+ bluebubbles_password = os.getenv("BLUEBUBBLES_PASSWORD")
1703
+ if bluebubbles_server_url and bluebubbles_password:
1704
+ if Platform.BLUEBUBBLES not in config.platforms:
1705
+ config.platforms[Platform.BLUEBUBBLES] = PlatformConfig()
1706
+ config.platforms[Platform.BLUEBUBBLES].enabled = True
1707
+ config.platforms[Platform.BLUEBUBBLES].extra.update({
1708
+ "server_url": bluebubbles_server_url.rstrip("/"),
1709
+ "password": bluebubbles_password,
1710
+ "webhook_host": os.getenv("BLUEBUBBLES_WEBHOOK_HOST", "127.0.0.1"),
1711
+ "webhook_port": int(os.getenv("BLUEBUBBLES_WEBHOOK_PORT", "8645")),
1712
+ "webhook_path": os.getenv("BLUEBUBBLES_WEBHOOK_PATH", "/bluebubbles-webhook"),
1713
+ "send_read_receipts": os.getenv("BLUEBUBBLES_SEND_READ_RECEIPTS", "true").lower() in {"true", "1", "yes"},
1714
+ })
1715
+ bluebubbles_home = os.getenv("BLUEBUBBLES_HOME_CHANNEL")
1716
+ if bluebubbles_home and Platform.BLUEBUBBLES in config.platforms:
1717
+ config.platforms[Platform.BLUEBUBBLES].home_channel = HomeChannel(
1718
+ platform=Platform.BLUEBUBBLES,
1719
+ chat_id=bluebubbles_home,
1720
+ name=os.getenv("BLUEBUBBLES_HOME_CHANNEL_NAME", "Home"),
1721
+ thread_id=os.getenv("BLUEBUBBLES_HOME_CHANNEL_THREAD_ID") or None,
1722
+ )
1723
+
1724
+ # QQ (Official Bot API v2)
1725
+ qq_app_id = os.getenv("QQ_APP_ID")
1726
+ qq_client_secret = os.getenv("QQ_CLIENT_SECRET")
1727
+ if qq_app_id or qq_client_secret:
1728
+ if Platform.QQBOT not in config.platforms:
1729
+ config.platforms[Platform.QQBOT] = PlatformConfig()
1730
+ config.platforms[Platform.QQBOT].enabled = True
1731
+ extra = config.platforms[Platform.QQBOT].extra
1732
+ if qq_app_id:
1733
+ extra["app_id"] = qq_app_id
1734
+ if qq_client_secret:
1735
+ extra["client_secret"] = qq_client_secret
1736
+ qq_allowed_users = os.getenv("QQ_ALLOWED_USERS", "").strip()
1737
+ if qq_allowed_users:
1738
+ extra["allow_from"] = qq_allowed_users
1739
+ qq_group_allowed = os.getenv("QQ_GROUP_ALLOWED_USERS", "").strip()
1740
+ if qq_group_allowed:
1741
+ extra["group_allow_from"] = qq_group_allowed
1742
+ qq_home = os.getenv("QQBOT_HOME_CHANNEL", "").strip()
1743
+ qq_home_name_env = "QQBOT_HOME_CHANNEL_NAME"
1744
+ if not qq_home:
1745
+ # Back-compat: accept the pre-rename name and log a one-time warning.
1746
+ legacy_home = os.getenv("QQ_HOME_CHANNEL", "").strip()
1747
+ if legacy_home:
1748
+ qq_home = legacy_home
1749
+ qq_home_name_env = "QQ_HOME_CHANNEL_NAME"
1750
+ logging.getLogger(__name__).warning(
1751
+ "QQ_HOME_CHANNEL is deprecated; rename to QQBOT_HOME_CHANNEL "
1752
+ "in your .env for consistency with the platform key."
1753
+ )
1754
+ if qq_home:
1755
+ config.platforms[Platform.QQBOT].home_channel = HomeChannel(
1756
+ platform=Platform.QQBOT,
1757
+ chat_id=qq_home,
1758
+ name=os.getenv("QQBOT_HOME_CHANNEL_NAME") or os.getenv(qq_home_name_env, "Home"),
1759
+ thread_id=(
1760
+ os.getenv("QQBOT_HOME_CHANNEL_THREAD_ID")
1761
+ or os.getenv("QQ_HOME_CHANNEL_THREAD_ID")
1762
+ or None
1763
+ ),
1764
+ )
1765
+
1766
+ # Yuanbao — YUANBAO_APP_ID preferred
1767
+ yuanbao_app_id = os.getenv("YUANBAO_APP_ID") or os.getenv("YUANBAO_APP_KEY")
1768
+ yuanbao_app_secret = os.getenv("YUANBAO_APP_SECRET")
1769
+ if yuanbao_app_id and yuanbao_app_secret:
1770
+ if Platform.YUANBAO not in config.platforms:
1771
+ config.platforms[Platform.YUANBAO] = PlatformConfig()
1772
+ config.platforms[Platform.YUANBAO].enabled = True
1773
+ extra = config.platforms[Platform.YUANBAO].extra
1774
+ extra["app_id"] = yuanbao_app_id
1775
+ extra["app_secret"] = yuanbao_app_secret
1776
+ yuanbao_bot_id = os.getenv("YUANBAO_BOT_ID")
1777
+ if yuanbao_bot_id:
1778
+ extra["bot_id"] = yuanbao_bot_id
1779
+ yuanbao_ws_url = os.getenv("YUANBAO_WS_URL")
1780
+ if yuanbao_ws_url:
1781
+ extra["ws_url"] = yuanbao_ws_url
1782
+ yuanbao_api_domain = os.getenv("YUANBAO_API_DOMAIN")
1783
+ if yuanbao_api_domain:
1784
+ extra["api_domain"] = yuanbao_api_domain
1785
+ yuanbao_route_env = os.getenv("YUANBAO_ROUTE_ENV")
1786
+ if yuanbao_route_env:
1787
+ extra["route_env"] = yuanbao_route_env
1788
+ yuanbao_home = os.getenv("YUANBAO_HOME_CHANNEL")
1789
+ if yuanbao_home:
1790
+ config.platforms[Platform.YUANBAO].home_channel = HomeChannel(
1791
+ platform=Platform.YUANBAO,
1792
+ chat_id=yuanbao_home,
1793
+ name=os.getenv("YUANBAO_HOME_CHANNEL_NAME", "Home"),
1794
+ thread_id=os.getenv("YUANBAO_HOME_CHANNEL_THREAD_ID") or None,
1795
+ )
1796
+ yuanbao_dm_policy = os.getenv("YUANBAO_DM_POLICY")
1797
+ if yuanbao_dm_policy:
1798
+ extra["dm_policy"] = yuanbao_dm_policy.strip().lower()
1799
+ yuanbao_dm_allow_from = os.getenv("YUANBAO_DM_ALLOW_FROM")
1800
+ if yuanbao_dm_allow_from:
1801
+ extra["dm_allow_from"] = yuanbao_dm_allow_from
1802
+ yuanbao_group_policy = os.getenv("YUANBAO_GROUP_POLICY")
1803
+ if yuanbao_group_policy:
1804
+ extra["group_policy"] = yuanbao_group_policy.strip().lower()
1805
+ yuanbao_group_allow_from = os.getenv("YUANBAO_GROUP_ALLOW_FROM")
1806
+ if yuanbao_group_allow_from:
1807
+ extra["group_allow_from"] = yuanbao_group_allow_from
1808
+
1809
+ # Session settings
1810
+ idle_minutes = os.getenv("SESSION_IDLE_MINUTES")
1811
+ if idle_minutes:
1812
+ try:
1813
+ config.default_reset_policy.idle_minutes = int(idle_minutes)
1814
+ except ValueError:
1815
+ pass
1816
+
1817
+ reset_hour = os.getenv("SESSION_RESET_HOUR")
1818
+ if reset_hour:
1819
+ try:
1820
+ config.default_reset_policy.at_hour = int(reset_hour)
1821
+ except ValueError:
1822
+ pass
1823
+
1824
+ # Registry-driven enable for plugin platforms. Built-ins have explicit
1825
+ # blocks above; plugins expose check_fn() which is the single source of
1826
+ # truth for "are my env vars set?". When it returns True, ensure the
1827
+ # platform is enabled so start() will create its adapter. Plugins that
1828
+ # need to seed ``PlatformConfig.extra`` from env vars (e.g. Google Chat's
1829
+ # project_id / subscription_name) can supply ``env_enablement_fn`` on
1830
+ # their PlatformEntry — called here BEFORE adapter construction.
1831
+ try:
1832
+ from hermes_cli.plugins import discover_plugins
1833
+ discover_plugins() # idempotent
1834
+ from gateway.platform_registry import platform_registry
1835
+ for entry in platform_registry.plugin_entries():
1836
+ try:
1837
+ if not entry.check_fn():
1838
+ continue
1839
+ except Exception as e:
1840
+ logger.debug("check_fn for %s raised: %s", entry.name, e)
1841
+ continue
1842
+ platform = Platform(entry.name)
1843
+ if platform not in config.platforms:
1844
+ config.platforms[platform] = PlatformConfig()
1845
+ config.platforms[platform].enabled = True
1846
+ # Seed extras from env if the plugin opted in.
1847
+ if entry.env_enablement_fn is not None:
1848
+ try:
1849
+ seed = entry.env_enablement_fn()
1850
+ except Exception as e:
1851
+ logger.debug(
1852
+ "env_enablement_fn for %s raised: %s", entry.name, e
1853
+ )
1854
+ seed = None
1855
+ if isinstance(seed, dict) and seed:
1856
+ # Extract the home_channel dict (if provided) so we wire it
1857
+ # up as a proper HomeChannel dataclass. Everything else is
1858
+ # merged into ``extra``.
1859
+ home = seed.pop("home_channel", None)
1860
+ config.platforms[platform].extra.update(seed)
1861
+ if isinstance(home, dict) and home.get("chat_id"):
1862
+ config.platforms[platform].home_channel = HomeChannel(
1863
+ platform=platform,
1864
+ chat_id=str(home["chat_id"]),
1865
+ name=str(home.get("name") or "Home"),
1866
+ thread_id=(
1867
+ str(home["thread_id"])
1868
+ if home.get("thread_id")
1869
+ else None
1870
+ ),
1871
+ )
1872
+ except Exception as e:
1873
+ logger.debug("Plugin platform enable pass failed: %s", e)