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,2376 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Terminal Tool Module
4
+
5
+ A terminal tool that executes commands in local, Docker, Modal, SSH,
6
+ Singularity, Daytona, and Vercel Sandbox environments. Supports local
7
+ execution, containerized backends, and cloud sandboxes, including managed
8
+ Modal mode.
9
+
10
+ Environment Selection (via TERMINAL_ENV environment variable):
11
+ - "local": Execute directly on the host machine (default, fastest)
12
+ - "docker": Execute in Docker containers (isolated, requires Docker)
13
+ - "modal": Execute in Modal cloud sandboxes (direct Modal or managed gateway)
14
+ - "vercel_sandbox": Execute in Vercel Sandbox cloud sandboxes
15
+
16
+ Features:
17
+ - Multiple execution backends (local, docker, modal, vercel_sandbox)
18
+ - Background task support
19
+ - VM/container lifecycle management
20
+ - Automatic cleanup after inactivity
21
+
22
+ Cloud sandbox note:
23
+ - Persistent filesystems preserve working state across sandbox recreation
24
+ - Persistent filesystems do NOT guarantee the same live sandbox or long-running processes survive cleanup, idle reaping, or Hermes exit
25
+
26
+ Usage:
27
+ from terminal_tool import terminal_tool
28
+
29
+ # Execute a simple command
30
+ result = terminal_tool("ls -la")
31
+
32
+ # Execute in background
33
+ result = terminal_tool("python server.py", background=True)
34
+ """
35
+
36
+ import importlib.util
37
+ import json
38
+ import logging
39
+ import os
40
+ import platform
41
+ import re
42
+ import time
43
+ import threading
44
+ import atexit
45
+ import shutil
46
+ import subprocess
47
+ from pathlib import Path
48
+ from typing import Optional, Dict, Any, List
49
+
50
+ from utils import env_var_enabled
51
+
52
+ logger = logging.getLogger(__name__)
53
+
54
+
55
+ # ---------------------------------------------------------------------------
56
+ # Global interrupt event: set by the agent when a user interrupt arrives.
57
+ # The terminal tool polls this during command execution so it can kill
58
+ # long-running subprocesses immediately instead of blocking until timeout.
59
+ # ---------------------------------------------------------------------------
60
+ from tools.interrupt import is_interrupted, _interrupt_event # noqa: F401 — re-exported
61
+ # display_hermes_home imported lazily at call site (stale-module safety during hermes update)
62
+
63
+
64
+
65
+
66
+ # =============================================================================
67
+ # Custom Singularity Environment with more space
68
+ # =============================================================================
69
+
70
+ # Singularity helpers (scratch dir, SIF cache) now live in tools/environments/singularity.py
71
+ from tools.environments.singularity import _get_scratch_dir
72
+ from tools.tool_backend_helpers import (
73
+ coerce_modal_mode,
74
+ has_direct_modal_credentials,
75
+ managed_nous_tools_enabled,
76
+ resolve_modal_backend_state,
77
+ )
78
+
79
+
80
+ def _safe_parse_import_env(
81
+ name: str,
82
+ default: Any,
83
+ converter,
84
+ type_label: str,
85
+ ):
86
+ """Parse module-level numeric env vars without breaking import.
87
+
88
+ Terminal tool is imported by CLI, ACP, tests, and tool discovery. A single
89
+ malformed env var must not make the whole module unloadable at import time.
90
+ """
91
+ raw = os.getenv(name)
92
+ if raw is None or raw == "":
93
+ return default
94
+ try:
95
+ return converter(raw)
96
+ except (TypeError, ValueError):
97
+ logger.warning(
98
+ "Invalid value for %s: %r (expected %s). Falling back to %r.",
99
+ name,
100
+ raw,
101
+ type_label,
102
+ default,
103
+ )
104
+ return default
105
+
106
+
107
+ # Hard cap on foreground timeout; override via TERMINAL_MAX_FOREGROUND_TIMEOUT env var.
108
+ FOREGROUND_MAX_TIMEOUT = _safe_parse_import_env(
109
+ "TERMINAL_MAX_FOREGROUND_TIMEOUT",
110
+ 600,
111
+ int,
112
+ "integer",
113
+ )
114
+
115
+ # Disk usage warning threshold (in GB)
116
+ DISK_USAGE_WARNING_THRESHOLD_GB = _safe_parse_import_env(
117
+ "TERMINAL_DISK_WARNING_GB",
118
+ 500.0,
119
+ float,
120
+ "number",
121
+ )
122
+ _VERCEL_SANDBOX_DEFAULT_CWD = "/vercel/sandbox"
123
+ _SUPPORTED_VERCEL_RUNTIMES = ("node24", "node22", "python3.13")
124
+
125
+
126
+ def _is_supported_vercel_runtime(runtime: str) -> bool:
127
+ return not runtime or runtime in _SUPPORTED_VERCEL_RUNTIMES
128
+
129
+
130
+ def _check_vercel_sandbox_requirements(config: dict[str, Any]) -> bool:
131
+ """Validate Vercel Sandbox terminal backend requirements."""
132
+ runtime = (config.get("vercel_runtime") or "").strip()
133
+ if not _is_supported_vercel_runtime(runtime):
134
+ supported = ", ".join(_SUPPORTED_VERCEL_RUNTIMES)
135
+ logger.error(
136
+ "Vercel Sandbox runtime %r is not supported. "
137
+ "Set TERMINAL_VERCEL_RUNTIME to one of: %s.",
138
+ runtime,
139
+ supported,
140
+ )
141
+ return False
142
+
143
+ disk = config.get("container_disk", 51200)
144
+ if disk not in {0, 51200}:
145
+ logger.error(
146
+ "Vercel Sandbox does not support custom TERMINAL_CONTAINER_DISK=%s. "
147
+ "Use the default shared setting (51200 MB).",
148
+ disk,
149
+ )
150
+ return False
151
+
152
+ if importlib.util.find_spec("vercel") is None:
153
+ logger.error(
154
+ "vercel is required for the Vercel Sandbox terminal backend: pip install vercel"
155
+ )
156
+ return False
157
+
158
+ has_oidc = bool(os.getenv("VERCEL_OIDC_TOKEN"))
159
+ has_token = bool(os.getenv("VERCEL_TOKEN"))
160
+ has_project = bool(os.getenv("VERCEL_PROJECT_ID"))
161
+ has_team = bool(os.getenv("VERCEL_TEAM_ID"))
162
+
163
+ if has_oidc:
164
+ return True
165
+
166
+ if has_token or has_project or has_team:
167
+ if has_token and has_project and has_team:
168
+ return True
169
+ logger.error(
170
+ "Vercel Sandbox backend selected with token auth, but "
171
+ "VERCEL_TOKEN, VERCEL_PROJECT_ID, and VERCEL_TEAM_ID must all "
172
+ "be set together. VERCEL_OIDC_TOKEN is supported for one-off "
173
+ "local development only."
174
+ )
175
+ return False
176
+
177
+ logger.error(
178
+ "Vercel Sandbox backend selected but no supported auth configuration "
179
+ "was found. Set VERCEL_TOKEN, VERCEL_PROJECT_ID, and VERCEL_TEAM_ID "
180
+ "for normal use. VERCEL_OIDC_TOKEN is supported for one-off local "
181
+ "development only."
182
+ )
183
+ return False
184
+
185
+
186
+ def _check_disk_usage_warning():
187
+ """Check if total disk usage exceeds warning threshold."""
188
+ try:
189
+ scratch_dir = _get_scratch_dir()
190
+
191
+ # Get total size of hermes directories
192
+ total_bytes = 0
193
+ import glob
194
+ for path in glob.glob(str(scratch_dir / "hermes-*")):
195
+ for f in Path(path).rglob('*'):
196
+ if f.is_file():
197
+ try:
198
+ total_bytes += f.stat().st_size
199
+ except OSError as e:
200
+ logger.debug("Could not stat file %s: %s", f, e)
201
+
202
+ total_gb = total_bytes / (1024 ** 3)
203
+
204
+ if total_gb > DISK_USAGE_WARNING_THRESHOLD_GB:
205
+ logger.warning("Disk usage (%.1fGB) exceeds threshold (%.0fGB). Consider running cleanup_all_environments().",
206
+ total_gb, DISK_USAGE_WARNING_THRESHOLD_GB)
207
+ return True
208
+
209
+ return False
210
+ except Exception as e:
211
+ logger.debug("Disk usage warning check failed: %s", e, exc_info=True)
212
+ return False
213
+
214
+
215
+ # Interactive sudo password cache.
216
+ #
217
+ # Scope the cache to the active session when a session key is available, then
218
+ # fall back to callback identity (ACP / CLI interactive callbacks), then the
219
+ # current thread. This prevents one interactive session from reusing another
220
+ # session's cached sudo password inside the same long-lived process.
221
+ _sudo_password_cache: dict[str, str] = {}
222
+ _sudo_password_cache_lock = threading.Lock()
223
+
224
+ # Optional UI callbacks for interactive prompts. When set, these are called
225
+ # instead of the default /dev/tty or input() readers. The CLI registers these
226
+ # so prompts route through prompt_toolkit's event loop.
227
+ # Callback slots used by the approval prompt and sudo password prompt
228
+ # routines. Stored in thread-local state so overlapping ACP sessions —
229
+ # each running in its own ThreadPoolExecutor thread — don't stomp on
230
+ # each other's callbacks. See GHSA-qg5c-hvr5-hjgr.
231
+ #
232
+ # CLI mode is single-threaded, so each thread (the only one) holds its
233
+ # own callback exactly like before. Gateway mode resolves approvals via
234
+ # the per-session queue in tools.approval, not through these callbacks,
235
+ # so it's unaffected.
236
+ import threading
237
+ _callback_tls = threading.local()
238
+
239
+
240
+ def _get_sudo_password_callback():
241
+ return getattr(_callback_tls, "sudo_password", None)
242
+
243
+
244
+ def _get_approval_callback():
245
+ return getattr(_callback_tls, "approval", None)
246
+
247
+
248
+ def set_sudo_password_callback(cb):
249
+ """Register a callback for sudo password prompts (used by CLI).
250
+
251
+ Per-thread scope — ACP sessions that run concurrently in a
252
+ ThreadPoolExecutor each have their own callback slot.
253
+ """
254
+ _callback_tls.sudo_password = cb
255
+
256
+
257
+ def set_approval_callback(cb):
258
+ """Register a callback for dangerous command approval prompts.
259
+
260
+ Per-thread scope — ACP sessions that run concurrently in a
261
+ ThreadPoolExecutor each have their own callback slot. See
262
+ GHSA-qg5c-hvr5-hjgr.
263
+ """
264
+ _callback_tls.approval = cb
265
+
266
+
267
+ def _get_sudo_password_cache_scope() -> str:
268
+ """Return the cache scope for interactive sudo passwords."""
269
+ try:
270
+ from gateway.session_context import get_session_env
271
+
272
+ session_key = get_session_env("HERMES_SESSION_KEY", "")
273
+ except Exception:
274
+ session_key = os.getenv("HERMES_SESSION_KEY", "")
275
+ if session_key:
276
+ return f"session:{session_key}"
277
+
278
+ callback = _get_sudo_password_callback()
279
+ if callback is not None:
280
+ owner = getattr(callback, "__self__", None)
281
+ func = getattr(callback, "__func__", None)
282
+ if owner is not None and func is not None:
283
+ return f"callback-owner:{id(owner)}:{id(func)}"
284
+ return f"callback:{id(callback)}"
285
+
286
+ return f"thread:{threading.get_ident()}"
287
+
288
+
289
+ def _get_cached_sudo_password() -> str:
290
+ """Return the cached sudo password for the current scope."""
291
+ scope = _get_sudo_password_cache_scope()
292
+ with _sudo_password_cache_lock:
293
+ return _sudo_password_cache.get(scope, "")
294
+
295
+
296
+ def _set_cached_sudo_password(password: str) -> None:
297
+ """Persist a sudo password for the current scope."""
298
+ scope = _get_sudo_password_cache_scope()
299
+ with _sudo_password_cache_lock:
300
+ if password:
301
+ _sudo_password_cache[scope] = password
302
+ else:
303
+ _sudo_password_cache.pop(scope, None)
304
+
305
+
306
+ def _reset_cached_sudo_passwords() -> None:
307
+ """Clear all cached sudo passwords.
308
+
309
+ Internal helper for tests and process teardown paths.
310
+ """
311
+ with _sudo_password_cache_lock:
312
+ _sudo_password_cache.clear()
313
+
314
+ # =============================================================================
315
+ # Dangerous Command Approval System
316
+ # =============================================================================
317
+
318
+ # Dangerous command detection + approval now consolidated in tools/approval.py
319
+ from tools.approval import (
320
+ check_all_command_guards as _check_all_guards_impl,
321
+ )
322
+
323
+
324
+ def _check_all_guards(command: str, env_type: str) -> dict:
325
+ """Delegate to consolidated guard (tirith + dangerous cmd) with CLI callback."""
326
+ return _check_all_guards_impl(command, env_type,
327
+ approval_callback=_get_approval_callback())
328
+
329
+
330
+ # Allowlist: characters that can legitimately appear in directory paths.
331
+ # Covers alphanumeric, path separators, Windows drive/UNC separators, tilde,
332
+ # dot, hyphen, underscore, space, plus, at, equals, and comma. Everything
333
+ # else is rejected.
334
+ _WORKDIR_SAFE_RE = re.compile(r'^[A-Za-z0-9/\\:_\-.~ +@=,]+$')
335
+
336
+
337
+ def _validate_workdir(workdir: str) -> str | None:
338
+ """Reject workdir values that don't look like a filesystem path.
339
+
340
+ Uses an allowlist of safe characters rather than a deny-list, so novel
341
+ shell metacharacters can't slip through.
342
+
343
+ Returns None if safe, or an error message string if dangerous.
344
+ """
345
+ if not workdir:
346
+ return None
347
+ if not _WORKDIR_SAFE_RE.match(workdir):
348
+ # Find the first offending character for a helpful message.
349
+ for ch in workdir:
350
+ if not _WORKDIR_SAFE_RE.match(ch):
351
+ return (
352
+ f"Blocked: workdir contains disallowed character {repr(ch)}. "
353
+ "Use a simple filesystem path without shell metacharacters."
354
+ )
355
+ return "Blocked: workdir contains disallowed characters."
356
+ return None
357
+
358
+
359
+ def _handle_sudo_failure(output: str, env_type: str) -> str:
360
+ """
361
+ Check for sudo failure and add helpful message for messaging contexts.
362
+
363
+ Returns enhanced output if sudo failed in messaging context, else original.
364
+ """
365
+ is_gateway = env_var_enabled("HERMES_GATEWAY_SESSION")
366
+
367
+ if not is_gateway:
368
+ return output
369
+
370
+ # Check for sudo failure indicators
371
+ sudo_failures = [
372
+ "sudo: a password is required",
373
+ "sudo: no tty present",
374
+ "sudo: a terminal is required",
375
+ ]
376
+
377
+ for failure in sudo_failures:
378
+ if failure in output:
379
+ from calvyn_constants import display_hermes_home as _dhh
380
+ return output + f"\n\n💡 Tip: To enable sudo over messaging, add SUDO_PASSWORD to {_dhh()}/.env on the agent machine."
381
+
382
+ return output
383
+
384
+
385
+ def _prompt_for_sudo_password(timeout_seconds: int = 45) -> str:
386
+ """
387
+ Prompt user for sudo password with timeout.
388
+
389
+ Returns the password if entered, or empty string if:
390
+ - User presses Enter without input (skip)
391
+ - Timeout expires (45s default)
392
+ - Any error occurs
393
+
394
+ Only works in interactive mode (HERMES_INTERACTIVE=1).
395
+ If a _sudo_password_callback is registered (by the CLI), delegates to it
396
+ so the prompt integrates with prompt_toolkit's UI. Otherwise reads
397
+ directly from /dev/tty with echo disabled.
398
+ """
399
+ import sys
400
+
401
+ # Use the registered callback when available (prompt_toolkit-compatible)
402
+ _sudo_cb = _get_sudo_password_callback()
403
+ if _sudo_cb is not None:
404
+ try:
405
+ return _sudo_cb() or ""
406
+ except Exception:
407
+ return ""
408
+
409
+ result = {"password": None, "done": False}
410
+
411
+ def read_password_thread():
412
+ """Read password with echo disabled. Uses msvcrt on Windows, /dev/tty on Unix."""
413
+ tty_fd = None
414
+ old_attrs = None
415
+ try:
416
+ if platform.system() == "Windows":
417
+ import msvcrt
418
+ chars = []
419
+ while True:
420
+ c = msvcrt.getwch()
421
+ if c in {"\r", "\n"}:
422
+ break
423
+ if c == "\x03":
424
+ raise KeyboardInterrupt
425
+ chars.append(c)
426
+ result["password"] = "".join(chars)
427
+ else:
428
+ import termios
429
+ tty_fd = os.open("/dev/tty", os.O_RDONLY)
430
+ old_attrs = termios.tcgetattr(tty_fd)
431
+ new_attrs = termios.tcgetattr(tty_fd)
432
+ new_attrs[3] = new_attrs[3] & ~termios.ECHO
433
+ termios.tcsetattr(tty_fd, termios.TCSAFLUSH, new_attrs)
434
+ chars = []
435
+ while True:
436
+ b = os.read(tty_fd, 1)
437
+ if not b or b in {b"\n", b"\r"}:
438
+ break
439
+ chars.append(b)
440
+ result["password"] = b"".join(chars).decode("utf-8", errors="replace")
441
+ except (EOFError, KeyboardInterrupt, OSError):
442
+ result["password"] = ""
443
+ except Exception:
444
+ result["password"] = ""
445
+ finally:
446
+ if tty_fd is not None and old_attrs is not None:
447
+ try:
448
+ import termios as _termios
449
+ _termios.tcsetattr(tty_fd, _termios.TCSAFLUSH, old_attrs)
450
+ except Exception as e:
451
+ logger.debug("Failed to restore terminal attributes: %s", e)
452
+ if tty_fd is not None:
453
+ try:
454
+ os.close(tty_fd)
455
+ except Exception as e:
456
+ logger.debug("Failed to close tty fd: %s", e)
457
+ result["done"] = True
458
+
459
+ try:
460
+ os.environ["HERMES_SPINNER_PAUSE"] = "1"
461
+ time.sleep(0.2)
462
+
463
+ print()
464
+ print("┌" + "─" * 58 + "┐")
465
+ print("│ 🔐 SUDO PASSWORD REQUIRED" + " " * 30 + "│")
466
+ print("├" + "─" * 58 + "┤")
467
+ print("│ Enter password below (input is hidden), or: │")
468
+ print("│ • Press Enter to skip (command fails gracefully) │")
469
+ print(f"│ • Wait {timeout_seconds}s to auto-skip" + " " * 27 + "│")
470
+ print("└" + "─" * 58 + "┘")
471
+ print()
472
+ print(" Password (hidden): ", end="", flush=True)
473
+
474
+ password_thread = threading.Thread(target=read_password_thread, daemon=True)
475
+ password_thread.start()
476
+ password_thread.join(timeout=timeout_seconds)
477
+
478
+ if result["done"]:
479
+ password = result["password"] or ""
480
+ print() # newline after hidden input
481
+ if password:
482
+ print(" ✓ Password received (cached for this session)")
483
+ else:
484
+ print(" ⏭ Skipped - continuing without sudo")
485
+ print()
486
+ sys.stdout.flush()
487
+ return password
488
+ else:
489
+ print("\n ⏱ Timeout - continuing without sudo")
490
+ print(" (Press Enter to dismiss)")
491
+ print()
492
+ sys.stdout.flush()
493
+ return ""
494
+
495
+ except (EOFError, KeyboardInterrupt):
496
+ print()
497
+ print(" ⏭ Cancelled - continuing without sudo")
498
+ print()
499
+ sys.stdout.flush()
500
+ return ""
501
+ except Exception as e:
502
+ print(f"\n [sudo prompt error: {e}] - continuing without sudo\n")
503
+ sys.stdout.flush()
504
+ return ""
505
+ finally:
506
+ if "HERMES_SPINNER_PAUSE" in os.environ:
507
+ del os.environ["HERMES_SPINNER_PAUSE"]
508
+
509
+ def _safe_command_preview(command: Any, limit: int = 200) -> str:
510
+ """Return a log-safe preview for possibly-invalid command values."""
511
+ if command is None:
512
+ return "<None>"
513
+ if isinstance(command, str):
514
+ return command[:limit]
515
+ try:
516
+ return repr(command)[:limit]
517
+ except Exception:
518
+ return f"<{type(command).__name__}>"
519
+
520
+ def _looks_like_env_assignment(token: str) -> bool:
521
+ """Return True when *token* is a leading shell environment assignment."""
522
+ if "=" not in token or token.startswith("="):
523
+ return False
524
+ name, _value = token.split("=", 1)
525
+ return bool(re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", name))
526
+
527
+
528
+ def _read_shell_token(command: str, start: int) -> tuple[str, int]:
529
+ """Read one shell token, preserving quotes/escapes, starting at *start*."""
530
+ i = start
531
+ n = len(command)
532
+
533
+ while i < n:
534
+ ch = command[i]
535
+ if ch.isspace() or ch in ";|&()":
536
+ break
537
+ if ch == "'":
538
+ i += 1
539
+ while i < n and command[i] != "'":
540
+ i += 1
541
+ if i < n:
542
+ i += 1
543
+ continue
544
+ if ch == '"':
545
+ i += 1
546
+ while i < n:
547
+ inner = command[i]
548
+ if inner == "\\" and i + 1 < n:
549
+ i += 2
550
+ continue
551
+ if inner == '"':
552
+ i += 1
553
+ break
554
+ i += 1
555
+ continue
556
+ if ch == "\\" and i + 1 < n:
557
+ i += 2
558
+ continue
559
+ i += 1
560
+
561
+ return command[start:i], i
562
+
563
+
564
+ def _rewrite_real_sudo_invocations(command: str) -> tuple[str, bool]:
565
+ """Rewrite only real unquoted sudo command words, not plain text mentions."""
566
+ out: list[str] = []
567
+ i = 0
568
+ n = len(command)
569
+ command_start = True
570
+ found = False
571
+
572
+ while i < n:
573
+ ch = command[i]
574
+
575
+ if ch.isspace():
576
+ out.append(ch)
577
+ if ch == "\n":
578
+ command_start = True
579
+ i += 1
580
+ continue
581
+
582
+ if ch == "#" and command_start:
583
+ comment_end = command.find("\n", i)
584
+ if comment_end == -1:
585
+ out.append(command[i:])
586
+ break
587
+ out.append(command[i:comment_end])
588
+ i = comment_end
589
+ continue
590
+
591
+ if command.startswith("&&", i) or command.startswith("||", i) or command.startswith(";;", i):
592
+ out.append(command[i:i + 2])
593
+ i += 2
594
+ command_start = True
595
+ continue
596
+
597
+ if ch in ";|&(":
598
+ out.append(ch)
599
+ i += 1
600
+ command_start = True
601
+ continue
602
+
603
+ if ch == ")":
604
+ out.append(ch)
605
+ i += 1
606
+ command_start = False
607
+ continue
608
+
609
+ token, next_i = _read_shell_token(command, i)
610
+ if command_start and token == "sudo":
611
+ out.append("sudo -S -p ''")
612
+ found = True
613
+ else:
614
+ out.append(token)
615
+
616
+ if command_start and _looks_like_env_assignment(token):
617
+ command_start = True
618
+ else:
619
+ command_start = False
620
+ i = next_i
621
+
622
+ return "".join(out), found
623
+
624
+
625
+ def _sudo_nopasswd_works() -> bool:
626
+ """Return True when local sudo currently works without prompting.
627
+
628
+ Only probes for the `local` terminal backend; Docker/SSH/Modal/etc. must
629
+ not inherit the host's sudo state. Re-probes every call (no process-level
630
+ cache) so an expired sudo timestamp cannot make a later command silently
631
+ block waiting for a password.
632
+ """
633
+ terminal_env = os.getenv("TERMINAL_ENV", "local").strip().lower() or "local"
634
+ if terminal_env != "local":
635
+ return False
636
+
637
+ try:
638
+ probe = subprocess.run(
639
+ ["sudo", "-n", "true"],
640
+ stdin=subprocess.DEVNULL,
641
+ stdout=subprocess.DEVNULL,
642
+ stderr=subprocess.DEVNULL,
643
+ timeout=3,
644
+ check=False,
645
+ )
646
+ return probe.returncode == 0
647
+ except Exception:
648
+ return False
649
+
650
+
651
+ def _rewrite_compound_background(command: str) -> str:
652
+ """Wrap `A && B &` (or `A || B &`) to `A && { B & }` at depth 0.
653
+
654
+ Bash parses ``A && B &`` with `&&` tighter than `&`, so it forks a
655
+ subshell for the whole `A && B` compound and backgrounds it. Inside
656
+ the subshell, `B` runs foreground, so the subshell waits for `B` to
657
+ finish. When `B` is a long-running process (`python3 -m http.server`,
658
+ `yes > /dev/null`, anything that doesn't naturally exit), the subshell
659
+ never exits. It leaks as a process stuck in ``wait4`` forever — and
660
+ on the way, its open stdout pipe can prevent the terminal tool from
661
+ returning promptly.
662
+
663
+ Rewriting the tail to `A && { B & }` preserves `&&`'s error semantics
664
+ (skip B if A fails) while replacing the subshell with a brace group.
665
+ The brace group runs in the current shell (no fork), backgrounds B as
666
+ a simple command (bash doesn't wait for it in non-interactive mode),
667
+ and exits immediately. B runs as a normal backgrounded child, orphaned
668
+ when the parent shell exits.
669
+
670
+ Handles redirects (``&>``, ``2>&1``) and skips content inside quoted
671
+ strings and parenthesised subshells. Leaves simple ``cmd &`` alone —
672
+ that construct doesn't have the subshell-wait bug.
673
+ """
674
+ n = len(command)
675
+ i = 0
676
+ paren_depth = 0
677
+ brace_depth = 0
678
+ # Position in *command* just after the most recent `&&` / `||` at depth 0
679
+ # in the current statement; -1 when no chain operator is active.
680
+ last_chain_op_end = -1
681
+ rewrites: list[tuple[int, int]] = [] # (chain_op_end, amp_pos)
682
+
683
+ while i < n:
684
+ ch = command[i]
685
+
686
+ # Newline terminates a statement at depth 0 — reset chain state.
687
+ # Checked before the whitespace skip so we don't miss it.
688
+ if ch == "\n" and paren_depth == 0 and brace_depth == 0:
689
+ last_chain_op_end = -1
690
+ i += 1
691
+ continue
692
+
693
+ if ch.isspace():
694
+ i += 1
695
+ continue
696
+
697
+ # Comments (only at statement start — conservative: any `#` not inside
698
+ # a token ends the line). `_read_shell_token` handles quoted strings
699
+ # below so `#` inside quotes is safe.
700
+ if ch == "#":
701
+ nl = command.find("\n", i)
702
+ if nl == -1:
703
+ break
704
+ i = nl
705
+ continue
706
+
707
+ if ch == "\\" and i + 1 < n:
708
+ i += 2
709
+ continue
710
+
711
+ # Quoted tokens — consume whole string via the shared tokenizer.
712
+ if ch in {"'", '"'}:
713
+ _, next_i = _read_shell_token(command, i)
714
+ i = max(next_i, i + 1)
715
+ continue
716
+
717
+ if ch == "(":
718
+ paren_depth += 1
719
+ i += 1
720
+ continue
721
+
722
+ if ch == ")":
723
+ paren_depth = max(0, paren_depth - 1)
724
+ i += 1
725
+ continue
726
+
727
+ # Brace groups: `{ ... }` is a group (no subshell fork), and bash
728
+ # requires whitespace after `{`. We track depth so already-rewritten
729
+ # output (`A && { B & }`) is idempotent — the inner `&` is part of
730
+ # the group, not a new compound to rewrite. Also skip content inside
731
+ # the group since `A && B &` there is separately well-formed.
732
+ if ch == "{" and i + 1 < n and (command[i + 1].isspace() or command[i + 1] == "\n"):
733
+ brace_depth += 1
734
+ i += 1
735
+ continue
736
+ if ch == "}" and brace_depth > 0:
737
+ brace_depth -= 1
738
+ # Closing a group completes a compound statement; reset chain.
739
+ last_chain_op_end = -1
740
+ i += 1
741
+ continue
742
+
743
+ # Inside parens or brace groups, skip operators — they parse in their
744
+ # own scope. `(...)` subshells have the same bug class but are not the
745
+ # common agent pattern; leave for a follow-up.
746
+ if paren_depth > 0 or brace_depth > 0:
747
+ i += 1
748
+ continue
749
+
750
+ # Chain operators at depth 0
751
+ if command.startswith("&&", i) or command.startswith("||", i):
752
+ last_chain_op_end = i + 2
753
+ i += 2
754
+ continue
755
+
756
+ # Statement terminators reset the chain state
757
+ if ch == ";":
758
+ last_chain_op_end = -1
759
+ i += 1
760
+ continue
761
+
762
+ # Single `|` (pipe) starts a new pipeline stage; don't rewrite
763
+ # across it. `||` handled above.
764
+ if ch == "|":
765
+ last_chain_op_end = -1
766
+ i += 1
767
+ continue
768
+
769
+ # `&` handling: distinguish `&&`, `&>`, fd redirect (`>&`, `<&`),
770
+ # and a true backgrounding `&`.
771
+ if ch == "&":
772
+ # `&&` handled above; won't reach here
773
+ if i + 1 < n and command[i + 1] == ">":
774
+ # `&>` redirect — consume
775
+ i += 2
776
+ continue
777
+ # `>&` / `<&` fd target — look back past whitespace
778
+ j = i - 1
779
+ while j >= 0 and command[j].isspace():
780
+ j -= 1
781
+ if j >= 0 and command[j] in "<>":
782
+ i += 1
783
+ continue
784
+ # Real background operator
785
+ if last_chain_op_end >= 0:
786
+ rewrites.append((last_chain_op_end, i))
787
+ last_chain_op_end = -1
788
+ i += 1
789
+ continue
790
+
791
+ # Regular unquoted token — advance past it via the shared tokenizer
792
+ _, next_i = _read_shell_token(command, i)
793
+ i = max(next_i, i + 1)
794
+
795
+ if not rewrites:
796
+ return command
797
+
798
+ # Apply rewrites back-to-front so earlier indices remain valid.
799
+ result = command
800
+ for chain_end, amp_pos in reversed(rewrites):
801
+ # Skip whitespace right after the `&&`/`||` so the brace group
802
+ # opens flush against the inner command.
803
+ insert_pos = chain_end
804
+ while insert_pos < amp_pos and result[insert_pos].isspace():
805
+ insert_pos += 1
806
+ prefix = result[:insert_pos]
807
+ middle = result[insert_pos:amp_pos] # inner command + trailing space
808
+ suffix = result[amp_pos + 1 :]
809
+ # `{` needs a trailing space in bash; the closing `}` needs to be
810
+ # preceded by `;` or `&` — we're providing `&` from the backgrounding.
811
+ result = prefix + "{ " + middle + "& }" + suffix
812
+
813
+ return result
814
+
815
+
816
+ def _transform_sudo_command(command: str | None) -> tuple[str | None, str | None]:
817
+ """
818
+ Transform sudo commands to use -S flag if SUDO_PASSWORD is available.
819
+
820
+ This is a shared helper used by all execution environments to provide
821
+ consistent sudo handling across local, SSH, and container environments.
822
+
823
+ Returns:
824
+ (transformed_command, sudo_stdin) where:
825
+ - transformed_command has every bare ``sudo`` replaced with
826
+ ``sudo -S -p ''`` so sudo reads its password from stdin.
827
+ - sudo_stdin is the password string with a trailing newline that the
828
+ caller must prepend to the process's stdin stream. sudo -S reads
829
+ exactly one line (the password) and passes the rest of stdin to the
830
+ child command, so prepending is safe even when the caller also has
831
+ its own stdin_data to pipe.
832
+ - If no password is available, sudo_stdin is None and the command is
833
+ returned unchanged so it fails gracefully with
834
+ "sudo: a password is required".
835
+
836
+ Callers that drive a subprocess directly (local, ssh, docker, singularity)
837
+ should prepend sudo_stdin to their stdin_data and pass the merged bytes to
838
+ Popen's stdin pipe.
839
+
840
+ Callers that cannot pipe subprocess stdin (modal, daytona,
841
+ vercel_sandbox) must embed the password in the command string
842
+ themselves; see their execute() methods for how they handle the
843
+ non-None sudo_stdin case.
844
+
845
+ If SUDO_PASSWORD is not set and in interactive mode (HERMES_INTERACTIVE=1):
846
+ Prompts user for password with 45s timeout, caches for session.
847
+
848
+ If SUDO_PASSWORD is not set and NOT interactive:
849
+ Command runs as-is (fails gracefully with "sudo: a password is required").
850
+ """
851
+ if command is None:
852
+ return None, None
853
+ transformed, has_real_sudo = _rewrite_real_sudo_invocations(command)
854
+ if not has_real_sudo:
855
+ return command, None
856
+
857
+ has_configured_password = "SUDO_PASSWORD" in os.environ
858
+ sudo_password = (
859
+ os.environ.get("SUDO_PASSWORD", "")
860
+ if has_configured_password
861
+ else _get_cached_sudo_password()
862
+ )
863
+
864
+ # Local hosts with sudoers NOPASSWD should not be forced through the
865
+ # interactive Hermes password prompt or the sudo -S password-pipe path.
866
+ # Scoped to the local terminal backend so Docker/SSH/Modal/etc. can't
867
+ # inherit host sudo state. Re-probes every call (no process-lifetime
868
+ # cache) so an expired sudo timestamp doesn't make a later command block
869
+ # silently without Hermes prompting.
870
+ if not has_configured_password and not sudo_password and _sudo_nopasswd_works():
871
+ return command, None
872
+
873
+ if not has_configured_password and not sudo_password and env_var_enabled("HERMES_INTERACTIVE"):
874
+ sudo_password = _prompt_for_sudo_password(timeout_seconds=45)
875
+ if sudo_password:
876
+ _set_cached_sudo_password(sudo_password)
877
+
878
+ if has_configured_password or sudo_password:
879
+ # Trailing newline is required: sudo -S reads one line for the password.
880
+ return transformed, sudo_password + "\n"
881
+
882
+ return command, None
883
+
884
+
885
+ # Environment classes now live in tools/environments/
886
+ from tools.environments.local import LocalEnvironment as _LocalEnvironment
887
+ from tools.environments.singularity import SingularityEnvironment as _SingularityEnvironment
888
+ from tools.environments.ssh import SSHEnvironment as _SSHEnvironment
889
+ from tools.environments.docker import DockerEnvironment as _DockerEnvironment
890
+ from tools.environments.modal import ModalEnvironment as _ModalEnvironment
891
+ from tools.environments.managed_modal import ManagedModalEnvironment as _ManagedModalEnvironment
892
+ from tools.managed_tool_gateway import is_managed_tool_gateway_ready
893
+ import sys
894
+
895
+
896
+ # Tool description for LLM
897
+ TERMINAL_TOOL_DESCRIPTION = """Execute shell commands on a Linux environment. Filesystem usually persists between calls.
898
+
899
+ Do NOT use cat/head/tail to read files — use read_file instead.
900
+ Do NOT use grep/rg/find to search — use search_files instead.
901
+ Do NOT use ls to list directories — use search_files(target='files') instead.
902
+ Do NOT use sed/awk to edit files — use patch instead.
903
+ Do NOT use echo/cat heredoc to create files — use write_file instead.
904
+ Reserve terminal for: builds, installs, git, processes, scripts, network, package managers, and anything that needs a shell.
905
+
906
+ Foreground (default): Commands return INSTANTLY when done, even if the timeout is high. Set timeout=300 for long builds/scripts — you'll still get the result in seconds if it's fast. Prefer foreground for short commands.
907
+ Background: Set background=true to get a session_id. Two patterns:
908
+ (1) Long-lived processes that never exit (servers, watchers).
909
+ (2) Long-running tasks with notify_on_complete=true — you can keep working on other things and the system auto-notifies you when the task finishes. Great for test suites, builds, deployments, or anything that takes more than a minute.
910
+ For servers/watchers, do NOT use shell-level background wrappers (nohup/disown/setsid/trailing '&') in foreground mode. Use background=true so Hermes can track lifecycle and output.
911
+ After starting a server, verify readiness with a health check or log signal, then run tests in a separate terminal() call. Avoid blind sleep loops.
912
+ Use process(action="poll") for progress checks, process(action="wait") to block until done.
913
+ Working directory: Use 'workdir' for per-command cwd.
914
+ PTY mode: Set pty=true for interactive CLI tools (Codex, Claude Code, Python REPL).
915
+
916
+ Do NOT use vim/nano/interactive tools without pty=true — they hang without a pseudo-terminal. Pipe git output to cat if it might page.
917
+ """
918
+
919
+ # Global state for environment lifecycle management
920
+ _active_environments: Dict[str, Any] = {}
921
+ _last_activity: Dict[str, float] = {}
922
+ _env_lock = threading.Lock()
923
+ _creation_locks: Dict[str, threading.Lock] = {} # Per-task locks for sandbox creation
924
+ _creation_locks_lock = threading.Lock() # Protects _creation_locks dict itself
925
+ _cleanup_thread = None
926
+ _cleanup_running = False
927
+
928
+ # Per-task environment overrides registry.
929
+ # Allows environments (e.g., TerminalBench2Env) to specify a custom Docker/Modal
930
+ # image for a specific task_id BEFORE the agent loop starts. When the terminal or
931
+ # file tools create a new sandbox for that task_id, they check this registry first
932
+ # and fall back to the TERMINAL_MODAL_IMAGE (etc.) env var if no override is set.
933
+ #
934
+ # This is never exposed to the model -- only infrastructure code calls it.
935
+ # Thread-safe because each task_id is unique per rollout.
936
+ _task_env_overrides: Dict[str, Dict[str, Any]] = {}
937
+
938
+
939
+ def register_task_env_overrides(task_id: str, overrides: Dict[str, Any]):
940
+ """
941
+ Register environment overrides for a specific task/rollout.
942
+
943
+ Called by Atropos environments before the agent loop to configure
944
+ per-task sandbox settings (e.g., a custom Dockerfile for the Modal image).
945
+
946
+ Supported override keys:
947
+ - modal_image: str -- Path to Dockerfile or Docker Hub image name
948
+ - docker_image: str -- Docker image name
949
+ - cwd: str -- Working directory inside the sandbox
950
+
951
+ Args:
952
+ task_id: The rollout's unique task identifier
953
+ overrides: Dict of config keys to override
954
+ """
955
+ _task_env_overrides[task_id] = overrides
956
+
957
+
958
+ def clear_task_env_overrides(task_id: str):
959
+ """
960
+ Clear environment overrides for a task after rollout completes.
961
+
962
+ Called during cleanup to avoid stale entries accumulating.
963
+ """
964
+ _task_env_overrides.pop(task_id, None)
965
+
966
+
967
+ def _resolve_container_task_id(task_id: Optional[str]) -> str:
968
+ """
969
+ Map a tool-call ``task_id`` to the container/sandbox key used by
970
+ ``_active_environments``.
971
+
972
+ The top-level agent passes ``task_id=None`` and lands on ``"default"``.
973
+ ``delegate_task`` children pass their own subagent ID so that
974
+ file-state tracking, the active-subagents registry, and TUI events stay
975
+ distinct per child -- but we deliberately collapse that ID back to
976
+ ``"default"`` here so subagents share the parent's long-lived container
977
+ (one bash, one /workspace, one set of installed packages).
978
+
979
+ Exception: RL / benchmark environments (TerminalBench2, HermesSweEnv, ...)
980
+ call ``register_task_env_overrides(task_id, {...})`` to request a
981
+ per-task Docker/Modal image. When an override is registered for a
982
+ task_id, we honour it by returning the task_id unchanged -- those
983
+ rollouts need their own isolated sandbox, which is the whole point of
984
+ the override.
985
+ """
986
+ if task_id and task_id in _task_env_overrides:
987
+ return task_id
988
+ return "default"
989
+
990
+
991
+ # Configuration from environment variables
992
+
993
+ def _parse_env_var(name: str, default: str, converter=int, type_label: str = "integer"):
994
+ """Parse an environment variable with *converter*, raising a clear error on bad values.
995
+
996
+ Without this wrapper, a single malformed env var (e.g. TERMINAL_TIMEOUT=5m)
997
+ causes an unhandled ValueError that kills every terminal command.
998
+ """
999
+ raw = os.getenv(name, default)
1000
+ try:
1001
+ return converter(raw)
1002
+ except (ValueError, json.JSONDecodeError):
1003
+ raise ValueError(
1004
+ f"Invalid value for {name}: {raw!r} (expected {type_label}). "
1005
+ f"Check ~/.hermes/.env or environment variables."
1006
+ )
1007
+
1008
+
1009
+ def _get_env_config() -> Dict[str, Any]:
1010
+ """Get terminal environment configuration from environment variables."""
1011
+ # Default image with Python and Node.js for maximum compatibility
1012
+ default_image = "nikolaik/python-nodejs:python3.11-nodejs20"
1013
+ env_type = os.getenv("TERMINAL_ENV", "local")
1014
+
1015
+ mount_docker_cwd = os.getenv("TERMINAL_DOCKER_MOUNT_CWD_TO_WORKSPACE", "false").lower() in {"true", "1", "yes"}
1016
+
1017
+ # Default cwd: local uses the host's current directory, ssh uses the
1018
+ # remote home, Vercel uses its documented workspace root, and everything
1019
+ # else starts in the backend's default root-like cwd.
1020
+ if env_type == "local":
1021
+ default_cwd = os.getcwd()
1022
+ elif env_type == "ssh":
1023
+ default_cwd = "~"
1024
+ elif env_type == "vercel_sandbox":
1025
+ default_cwd = _VERCEL_SANDBOX_DEFAULT_CWD
1026
+ else:
1027
+ default_cwd = "/root"
1028
+
1029
+ # Read TERMINAL_CWD but sanity-check it for container backends.
1030
+ # If Docker cwd passthrough is explicitly enabled, remap the host path to
1031
+ # /workspace and track the original host path separately. Otherwise keep the
1032
+ # normal sandbox behavior and discard host paths.
1033
+ cwd = os.getenv("TERMINAL_CWD", default_cwd)
1034
+ if cwd:
1035
+ cwd = os.path.expanduser(cwd)
1036
+ host_cwd = None
1037
+ host_prefixes = ("/Users/", "/home/", "C:\\", "C:/")
1038
+ if env_type == "docker" and mount_docker_cwd:
1039
+ docker_cwd_source = os.getenv("TERMINAL_CWD") or os.getcwd()
1040
+ candidate = os.path.abspath(os.path.expanduser(docker_cwd_source))
1041
+ if (
1042
+ any(candidate.startswith(p) for p in host_prefixes)
1043
+ or (os.path.isabs(candidate) and os.path.isdir(candidate) and not candidate.startswith(("/workspace", "/root")))
1044
+ ):
1045
+ host_cwd = candidate
1046
+ cwd = "/workspace"
1047
+ elif env_type in {"modal", "docker", "singularity", "daytona", "vercel_sandbox"} and cwd:
1048
+ # Host paths and relative paths that won't work inside containers
1049
+ is_host_path = any(cwd.startswith(p) for p in host_prefixes)
1050
+ is_relative = not os.path.isabs(cwd) # e.g. "." or "src/"
1051
+ if (is_host_path or is_relative) and cwd != default_cwd:
1052
+ logger.info("Ignoring TERMINAL_CWD=%r for %s backend "
1053
+ "(host/relative path won't work in sandbox). Using %r instead.",
1054
+ cwd, env_type, default_cwd)
1055
+ cwd = default_cwd
1056
+
1057
+ return {
1058
+ "env_type": env_type,
1059
+ "modal_mode": coerce_modal_mode(os.getenv("TERMINAL_MODAL_MODE", "auto")),
1060
+ "docker_image": os.getenv("TERMINAL_DOCKER_IMAGE", default_image),
1061
+ "docker_forward_env": _parse_env_var("TERMINAL_DOCKER_FORWARD_ENV", "[]", json.loads, "valid JSON"),
1062
+ "singularity_image": os.getenv("TERMINAL_SINGULARITY_IMAGE", f"docker://{default_image}"),
1063
+ "modal_image": os.getenv("TERMINAL_MODAL_IMAGE", default_image),
1064
+ "daytona_image": os.getenv("TERMINAL_DAYTONA_IMAGE", default_image),
1065
+ "vercel_runtime": os.getenv("TERMINAL_VERCEL_RUNTIME", "").strip(),
1066
+ "cwd": cwd,
1067
+ "host_cwd": host_cwd,
1068
+ "docker_mount_cwd_to_workspace": mount_docker_cwd,
1069
+ "timeout": _parse_env_var("TERMINAL_TIMEOUT", "180"),
1070
+ "lifetime_seconds": _parse_env_var("TERMINAL_LIFETIME_SECONDS", "300"),
1071
+ # SSH-specific config
1072
+ "ssh_host": os.getenv("TERMINAL_SSH_HOST", ""),
1073
+ "ssh_user": os.getenv("TERMINAL_SSH_USER", ""),
1074
+ "ssh_port": _parse_env_var("TERMINAL_SSH_PORT", "22"),
1075
+ "ssh_key": os.getenv("TERMINAL_SSH_KEY", ""),
1076
+ # Persistent shell: SSH defaults to the config-level persistent_shell
1077
+ # setting (true by default for non-local backends); local is always opt-in.
1078
+ # Per-backend env vars override if explicitly set.
1079
+ "ssh_persistent": os.getenv(
1080
+ "TERMINAL_SSH_PERSISTENT",
1081
+ os.getenv("TERMINAL_PERSISTENT_SHELL", "true"),
1082
+ ).lower() in {"true", "1", "yes"},
1083
+ "local_persistent": os.getenv("TERMINAL_LOCAL_PERSISTENT", "false").lower() in {"true", "1", "yes"},
1084
+ # Container resource config (applies to docker, singularity, modal,
1085
+ # daytona, and vercel_sandbox -- ignored for local/ssh)
1086
+ "container_cpu": _parse_env_var("TERMINAL_CONTAINER_CPU", "1", float, "number"),
1087
+ "container_memory": _parse_env_var("TERMINAL_CONTAINER_MEMORY", "5120"), # MB (default 5GB)
1088
+ "container_disk": _parse_env_var("TERMINAL_CONTAINER_DISK", "51200"), # MB (default 50GB)
1089
+ "container_persistent": os.getenv("TERMINAL_CONTAINER_PERSISTENT", "true").lower() in {"true", "1", "yes"},
1090
+ "docker_volumes": _parse_env_var("TERMINAL_DOCKER_VOLUMES", "[]", json.loads, "valid JSON"),
1091
+ "docker_env": _parse_env_var("TERMINAL_DOCKER_ENV", "{}", json.loads, "valid JSON"),
1092
+ "docker_run_as_host_user": os.getenv("TERMINAL_DOCKER_RUN_AS_HOST_USER", "false").lower() in {"true", "1", "yes"},
1093
+ "docker_extra_args": _parse_env_var("TERMINAL_DOCKER_EXTRA_ARGS", "[]", json.loads, "valid JSON"),
1094
+ }
1095
+
1096
+
1097
+ def _get_modal_backend_state(modal_mode: object | None) -> Dict[str, Any]:
1098
+ """Resolve direct vs managed Modal backend selection."""
1099
+ return resolve_modal_backend_state(
1100
+ modal_mode,
1101
+ has_direct=has_direct_modal_credentials(),
1102
+ managed_ready=is_managed_tool_gateway_ready("modal"),
1103
+ )
1104
+
1105
+
1106
+ def _create_environment(env_type: str, image: str, cwd: str, timeout: int,
1107
+ ssh_config: dict = None, container_config: dict = None,
1108
+ local_config: dict = None,
1109
+ task_id: str = "default",
1110
+ host_cwd: str = None):
1111
+ """
1112
+ Create an execution environment for sandboxed command execution.
1113
+
1114
+ Args:
1115
+ env_type: One of "local", "docker", "singularity", "modal",
1116
+ "daytona", "vercel_sandbox", "ssh"
1117
+ image: Docker/Singularity/Modal image name (ignored for local/ssh/vercel)
1118
+ cwd: Working directory
1119
+ timeout: Default command timeout
1120
+ ssh_config: SSH connection config (for env_type="ssh")
1121
+ container_config: Resource config for container backends (cpu, memory, disk, persistent)
1122
+ task_id: Task identifier for environment reuse and snapshot keying
1123
+ host_cwd: Optional host working directory to bind into Docker when explicitly enabled
1124
+
1125
+ Returns:
1126
+ Environment instance with execute() method
1127
+ """
1128
+ cc = container_config or {}
1129
+ cpu = cc.get("container_cpu", 1)
1130
+ memory = cc.get("container_memory", 5120)
1131
+ disk = cc.get("container_disk", 51200)
1132
+ persistent = cc.get("container_persistent", True)
1133
+ volumes = cc.get("docker_volumes", [])
1134
+ docker_forward_env = cc.get("docker_forward_env", [])
1135
+ docker_env = cc.get("docker_env", {})
1136
+ docker_extra_args = cc.get("docker_extra_args", [])
1137
+
1138
+ if env_type == "local":
1139
+ return _LocalEnvironment(cwd=cwd, timeout=timeout)
1140
+
1141
+ elif env_type == "docker":
1142
+ return _DockerEnvironment(
1143
+ image=image, cwd=cwd, timeout=timeout,
1144
+ cpu=cpu, memory=memory, disk=disk,
1145
+ persistent_filesystem=persistent, task_id=task_id,
1146
+ volumes=volumes,
1147
+ host_cwd=host_cwd,
1148
+ auto_mount_cwd=cc.get("docker_mount_cwd_to_workspace", False),
1149
+ forward_env=docker_forward_env,
1150
+ env=docker_env,
1151
+ run_as_host_user=cc.get("docker_run_as_host_user", False),
1152
+ extra_args=docker_extra_args,
1153
+ )
1154
+
1155
+ elif env_type == "singularity":
1156
+ return _SingularityEnvironment(
1157
+ image=image, cwd=cwd, timeout=timeout,
1158
+ cpu=cpu, memory=memory, disk=disk,
1159
+ persistent_filesystem=persistent, task_id=task_id,
1160
+ )
1161
+
1162
+ elif env_type == "modal":
1163
+ sandbox_kwargs = {}
1164
+ if cpu > 0:
1165
+ sandbox_kwargs["cpu"] = cpu
1166
+ if memory > 0:
1167
+ sandbox_kwargs["memory"] = memory
1168
+ if disk > 0:
1169
+ try:
1170
+ import inspect, modal
1171
+ if "ephemeral_disk" in inspect.signature(modal.Sandbox.create).parameters:
1172
+ sandbox_kwargs["ephemeral_disk"] = disk
1173
+ except Exception:
1174
+ pass
1175
+
1176
+ modal_state = _get_modal_backend_state(cc.get("modal_mode"))
1177
+
1178
+ if modal_state["selected_backend"] == "managed":
1179
+ return _ManagedModalEnvironment(
1180
+ image=image, cwd=cwd, timeout=timeout,
1181
+ modal_sandbox_kwargs=sandbox_kwargs,
1182
+ persistent_filesystem=persistent, task_id=task_id,
1183
+ )
1184
+
1185
+ if modal_state["selected_backend"] != "direct":
1186
+ if modal_state["managed_mode_blocked"]:
1187
+ raise ValueError(
1188
+ "Modal backend is configured for managed mode, but "
1189
+ "a paid Nous subscription is required for the Tool Gateway and no direct "
1190
+ "Modal credentials/config were found. Log in with `hermes model` or "
1191
+ "choose TERMINAL_MODAL_MODE=direct/auto."
1192
+ )
1193
+ if modal_state["mode"] == "managed":
1194
+ raise ValueError(
1195
+ "Modal backend is configured for managed mode, but the managed tool gateway is unavailable."
1196
+ )
1197
+ if modal_state["mode"] == "direct":
1198
+ raise ValueError(
1199
+ "Modal backend is configured for direct mode, but no direct Modal credentials/config were found."
1200
+ )
1201
+ message = "Modal backend selected but no direct Modal credentials/config was found."
1202
+ if managed_nous_tools_enabled():
1203
+ message = (
1204
+ "Modal backend selected but no direct Modal credentials/config or managed tool gateway was found."
1205
+ )
1206
+ raise ValueError(message)
1207
+
1208
+ return _ModalEnvironment(
1209
+ image=image, cwd=cwd, timeout=timeout,
1210
+ modal_sandbox_kwargs=sandbox_kwargs,
1211
+ persistent_filesystem=persistent, task_id=task_id,
1212
+ )
1213
+
1214
+ elif env_type == "daytona":
1215
+ # Lazy import so daytona SDK is only required when backend is selected.
1216
+ from tools.environments.daytona import DaytonaEnvironment as _DaytonaEnvironment
1217
+ return _DaytonaEnvironment(
1218
+ image=image, cwd=cwd, timeout=timeout,
1219
+ cpu=int(cpu), memory=memory, disk=disk,
1220
+ persistent_filesystem=persistent, task_id=task_id,
1221
+ )
1222
+
1223
+ elif env_type == "vercel_sandbox":
1224
+ from tools.environments.vercel_sandbox import (
1225
+ VercelSandboxEnvironment as _VercelSandboxEnvironment,
1226
+ )
1227
+ return _VercelSandboxEnvironment(
1228
+ runtime=cc.get("vercel_runtime") or None,
1229
+ cwd=cwd,
1230
+ timeout=timeout,
1231
+ cpu=cpu,
1232
+ memory=memory,
1233
+ disk=disk,
1234
+ persistent_filesystem=persistent,
1235
+ task_id=task_id,
1236
+ )
1237
+
1238
+ elif env_type == "ssh":
1239
+ if not ssh_config or not ssh_config.get("host") or not ssh_config.get("user"):
1240
+ raise ValueError("SSH environment requires ssh_host and ssh_user to be configured")
1241
+ return _SSHEnvironment(
1242
+ host=ssh_config["host"],
1243
+ user=ssh_config["user"],
1244
+ port=ssh_config.get("port", 22),
1245
+ key_path=ssh_config.get("key", ""),
1246
+ cwd=cwd,
1247
+ timeout=timeout,
1248
+ )
1249
+
1250
+ else:
1251
+ raise ValueError(
1252
+ f"Unknown environment type: {env_type}. Use 'local', 'docker', "
1253
+ f"'singularity', 'modal', 'daytona', 'vercel_sandbox', or 'ssh'"
1254
+ )
1255
+
1256
+
1257
+ def _cleanup_inactive_envs(lifetime_seconds: int = 300):
1258
+ """Clean up environments that have been inactive for longer than lifetime_seconds."""
1259
+ current_time = time.time()
1260
+
1261
+ # Check the process registry -- skip cleanup for sandboxes with active
1262
+ # background processes (their _last_activity gets refreshed to keep them alive).
1263
+ try:
1264
+ from tools.process_registry import process_registry
1265
+ for task_id in list(_last_activity.keys()):
1266
+ if process_registry.has_active_processes(task_id):
1267
+ _last_activity[task_id] = current_time # Keep sandbox alive
1268
+ except ImportError:
1269
+ pass
1270
+
1271
+ # Phase 1: collect stale entries and remove them from tracking dicts while
1272
+ # holding the lock. Do NOT call env.cleanup() inside the lock -- Modal and
1273
+ # Docker teardown can block for 10-15s, which would stall every concurrent
1274
+ # terminal/file tool call waiting on _env_lock.
1275
+ envs_to_stop = [] # list of (task_id, env) pairs
1276
+
1277
+ with _env_lock:
1278
+ for task_id, last_time in list(_last_activity.items()):
1279
+ if current_time - last_time > lifetime_seconds:
1280
+ env = _active_environments.pop(task_id, None)
1281
+ _last_activity.pop(task_id, None)
1282
+ if env is not None:
1283
+ envs_to_stop.append((task_id, env))
1284
+
1285
+ # Also purge per-task creation locks for cleaned-up tasks
1286
+ with _creation_locks_lock:
1287
+ for task_id, _ in envs_to_stop:
1288
+ _creation_locks.pop(task_id, None)
1289
+
1290
+ # Phase 2: stop the actual sandboxes OUTSIDE the lock so other tool calls
1291
+ # are not blocked while Modal/Docker sandboxes shut down.
1292
+ for task_id, env in envs_to_stop:
1293
+ # Invalidate stale file_ops cache entry (Bug fix: prevents
1294
+ # ShellFileOperations from referencing a dead sandbox)
1295
+ try:
1296
+ from tools.file_tools import clear_file_ops_cache
1297
+ clear_file_ops_cache(task_id)
1298
+ except ImportError:
1299
+ pass
1300
+
1301
+ try:
1302
+ if hasattr(env, 'cleanup'):
1303
+ env.cleanup()
1304
+ elif hasattr(env, 'stop'):
1305
+ env.stop()
1306
+ elif hasattr(env, 'terminate'):
1307
+ env.terminate()
1308
+
1309
+ logger.info("Cleaned up inactive environment for task: %s", task_id)
1310
+
1311
+ except Exception as e:
1312
+ error_str = str(e)
1313
+ if "404" in error_str or "not found" in error_str.lower():
1314
+ logger.info("Environment for task %s already cleaned up", task_id)
1315
+ else:
1316
+ logger.warning("Error cleaning up environment for task %s: %s", task_id, e)
1317
+
1318
+
1319
+ def _cleanup_thread_worker():
1320
+ """Background thread worker that periodically cleans up inactive environments."""
1321
+ while _cleanup_running:
1322
+ try:
1323
+ config = _get_env_config()
1324
+ _cleanup_inactive_envs(config["lifetime_seconds"])
1325
+ except Exception as e:
1326
+ logger.warning("Error in cleanup thread: %s", e, exc_info=True)
1327
+
1328
+ for _ in range(60):
1329
+ if not _cleanup_running:
1330
+ break
1331
+ time.sleep(1)
1332
+
1333
+
1334
+ def _start_cleanup_thread():
1335
+ """Start the background cleanup thread if not already running."""
1336
+ global _cleanup_thread, _cleanup_running
1337
+
1338
+ with _env_lock:
1339
+ if _cleanup_thread is None or not _cleanup_thread.is_alive():
1340
+ _cleanup_running = True
1341
+ _cleanup_thread = threading.Thread(target=_cleanup_thread_worker, daemon=True)
1342
+ _cleanup_thread.start()
1343
+
1344
+
1345
+ def _stop_cleanup_thread():
1346
+ """Stop the background cleanup thread."""
1347
+ global _cleanup_running
1348
+ _cleanup_running = False
1349
+ if _cleanup_thread is not None:
1350
+ try:
1351
+ _cleanup_thread.join(timeout=5)
1352
+ except (SystemExit, KeyboardInterrupt):
1353
+ pass
1354
+
1355
+
1356
+ def get_active_env(task_id: str):
1357
+ """Return the active BaseEnvironment for *task_id*, or None."""
1358
+ lookup = _resolve_container_task_id(task_id)
1359
+ with _env_lock:
1360
+ return _active_environments.get(lookup) or _active_environments.get(task_id)
1361
+
1362
+
1363
+ def is_persistent_env(task_id: str) -> bool:
1364
+ """Return True if the active environment for task_id is configured for
1365
+ cross-turn persistence (``persistent_filesystem=True``).
1366
+
1367
+ Used by the agent loop to skip per-turn teardown for backends whose whole
1368
+ point is to survive between turns (docker with ``container_persistent``,
1369
+ daytona, modal, etc.). Non-persistent backends (e.g. Morph) still get torn
1370
+ down at end-of-turn to prevent leakage. The idle reaper
1371
+ (``_cleanup_inactive_envs``) handles persistent envs once they exceed
1372
+ ``terminal.lifetime_seconds``.
1373
+ """
1374
+ env = get_active_env(task_id)
1375
+ if env is None:
1376
+ return False
1377
+ return bool(getattr(env, "_persistent", False))
1378
+
1379
+
1380
+
1381
+
1382
+ def cleanup_all_environments():
1383
+ """Clean up ALL active environments. Use with caution."""
1384
+ task_ids = list(_active_environments.keys())
1385
+ cleaned = 0
1386
+
1387
+ for task_id in task_ids:
1388
+ try:
1389
+ cleanup_vm(task_id)
1390
+ cleaned += 1
1391
+ except Exception as e:
1392
+ logger.error("Error cleaning %s: %s", task_id, e, exc_info=True)
1393
+
1394
+ # Also clean any orphaned directories
1395
+ scratch_dir = _get_scratch_dir()
1396
+ import glob
1397
+ for path in glob.glob(str(scratch_dir / "hermes-*")):
1398
+ try:
1399
+ shutil.rmtree(path, ignore_errors=True)
1400
+ logger.info("Removed orphaned: %s", path)
1401
+ except OSError as e:
1402
+ logger.debug("Failed to remove orphaned path %s: %s", path, e)
1403
+
1404
+ if cleaned > 0:
1405
+ logger.info("Cleaned %d environments", cleaned)
1406
+ return cleaned
1407
+
1408
+
1409
+ def cleanup_vm(task_id: str):
1410
+ """Manually clean up a specific environment by task_id."""
1411
+ # Remove from tracking dicts while holding the lock, but defer the
1412
+ # actual (potentially slow) env.cleanup() call to outside the lock
1413
+ # so other tool calls aren't blocked.
1414
+ env = None
1415
+ with _env_lock:
1416
+ env = _active_environments.pop(task_id, None)
1417
+ _last_activity.pop(task_id, None)
1418
+
1419
+ # Clean up per-task creation lock
1420
+ with _creation_locks_lock:
1421
+ _creation_locks.pop(task_id, None)
1422
+
1423
+ # Invalidate stale file_ops cache entry
1424
+ try:
1425
+ from tools.file_tools import clear_file_ops_cache
1426
+ clear_file_ops_cache(task_id)
1427
+ except ImportError:
1428
+ pass
1429
+
1430
+ if env is None:
1431
+ return
1432
+
1433
+ try:
1434
+ if hasattr(env, 'cleanup'):
1435
+ env.cleanup()
1436
+ elif hasattr(env, 'stop'):
1437
+ env.stop()
1438
+ elif hasattr(env, 'terminate'):
1439
+ env.terminate()
1440
+
1441
+ logger.info("Manually cleaned up environment for task: %s", task_id)
1442
+
1443
+ except Exception as e:
1444
+ error_str = str(e)
1445
+ if "404" in error_str or "not found" in error_str.lower():
1446
+ logger.info("Environment for task %s already cleaned up", task_id)
1447
+ else:
1448
+ logger.warning("Error cleaning up environment for task %s: %s", task_id, e)
1449
+
1450
+
1451
+ def _atexit_cleanup():
1452
+ """Stop cleanup thread and shut down all remaining sandboxes on exit."""
1453
+ _stop_cleanup_thread()
1454
+ if _active_environments:
1455
+ count = len(_active_environments)
1456
+ logger.info("Shutting down %d remaining sandbox(es)...", count)
1457
+ cleanup_all_environments()
1458
+
1459
+ atexit.register(_atexit_cleanup)
1460
+
1461
+
1462
+ # =============================================================================
1463
+ # Exit Code Context for Common CLI Tools
1464
+ # =============================================================================
1465
+ # Many Unix commands use non-zero exit codes for informational purposes, not
1466
+ # to indicate failure. The model sees a raw exit_code=1 from `grep` and
1467
+ # wastes a turn investigating something that just means "no matches".
1468
+ # This lookup adds a human-readable note so the agent can move on.
1469
+
1470
+ def _interpret_exit_code(command: str, exit_code: int) -> str | None:
1471
+ """Return a human-readable note when a non-zero exit code is non-erroneous.
1472
+
1473
+ Returns None when the exit code is 0 or genuinely signals an error.
1474
+ The note is appended to the tool result so the model doesn't waste
1475
+ turns investigating expected exit codes.
1476
+ """
1477
+ if exit_code == 0:
1478
+ return None
1479
+
1480
+ # Extract the last command in a pipeline/chain — that determines the
1481
+ # exit code. Handles `cmd1 && cmd2`, `cmd1 | cmd2`, `cmd1; cmd2`.
1482
+ # Deliberately simple: split on shell operators and take the last piece.
1483
+ segments = re.split(r'\s*(?:\|\||&&|[|;])\s*', command)
1484
+ last_segment = (segments[-1] if segments else command).strip()
1485
+
1486
+ # Get base command name (first word), stripping env var assignments
1487
+ # like VAR=val cmd ...
1488
+ words = last_segment.split()
1489
+ base_cmd = ""
1490
+ for w in words:
1491
+ if "=" in w and not w.startswith("-"):
1492
+ continue # skip VAR=val
1493
+ base_cmd = w.split("/")[-1] # handle /usr/bin/grep -> grep
1494
+ break
1495
+
1496
+ if not base_cmd:
1497
+ return None
1498
+
1499
+ # Command-specific semantics
1500
+ semantics: dict[str, dict[int, str]] = {
1501
+ # grep/rg/ag/ack: 1=no matches found (normal), 2+=real error
1502
+ "grep": {1: "No matches found (not an error)"},
1503
+ "egrep": {1: "No matches found (not an error)"},
1504
+ "fgrep": {1: "No matches found (not an error)"},
1505
+ "rg": {1: "No matches found (not an error)"},
1506
+ "ag": {1: "No matches found (not an error)"},
1507
+ "ack": {1: "No matches found (not an error)"},
1508
+ # diff: 1=files differ (expected), 2+=real error
1509
+ "diff": {1: "Files differ (expected, not an error)"},
1510
+ "colordiff": {1: "Files differ (expected, not an error)"},
1511
+ # find: 1=some dirs inaccessible but results may still be valid
1512
+ "find": {1: "Some directories were inaccessible (partial results may still be valid)"},
1513
+ # test/[: 1=condition is false (expected)
1514
+ "test": {1: "Condition evaluated to false (expected, not an error)"},
1515
+ "[": {1: "Condition evaluated to false (expected, not an error)"},
1516
+ # curl: common non-error codes
1517
+ "curl": {
1518
+ 6: "Could not resolve host",
1519
+ 7: "Failed to connect to host",
1520
+ 22: "HTTP response code indicated error (e.g. 404, 500)",
1521
+ 28: "Operation timed out",
1522
+ },
1523
+ # git: 1 is context-dependent but often normal (e.g. git diff with changes)
1524
+ "git": {1: "Non-zero exit (often normal — e.g. 'git diff' returns 1 when files differ)"},
1525
+ }
1526
+
1527
+ cmd_semantics = semantics.get(base_cmd)
1528
+ if cmd_semantics and exit_code in cmd_semantics:
1529
+ return cmd_semantics[exit_code]
1530
+
1531
+ return None
1532
+
1533
+
1534
+ def _command_requires_pipe_stdin(command: str) -> bool:
1535
+ """Return True when PTY mode would break stdin-driven commands.
1536
+
1537
+ Some CLIs change behavior when stdin is a TTY. In particular,
1538
+ `gh auth login --with-token` expects the token to arrive via piped stdin and
1539
+ waits for EOF; when we launch it under a PTY, `process.submit()` only sends a
1540
+ newline, so the command appears to hang forever with no visible progress.
1541
+ """
1542
+ normalized = " ".join(command.lower().split())
1543
+ return (
1544
+ normalized.startswith("gh auth login")
1545
+ and "--with-token" in normalized
1546
+ )
1547
+
1548
+
1549
+ _SHELL_LEVEL_BACKGROUND_RE = re.compile(
1550
+ r"(?:^|[;&|]\s*|&&\s*|\|\|\s*|\$\(\s*)(?:nohup|disown|setsid)\b", re.IGNORECASE | re.MULTILINE
1551
+ )
1552
+ _INLINE_BACKGROUND_AMP_RE = re.compile(r"\s&\s")
1553
+ _TRAILING_BACKGROUND_AMP_RE = re.compile(r"\s&\s*(?:#.*)?$")
1554
+
1555
+
1556
+ def _strip_quotes(command: str) -> str:
1557
+ """Remove single- and double-quoted content so regex checks don't match inside strings.
1558
+
1559
+ This prevents false positives when keywords like 'nohup' or 'setsid' appear
1560
+ in commit messages, Python -c code, echo arguments, or PR body text.
1561
+ Also strips backtick-quoted content and heredoc-style inline text.
1562
+ """
1563
+ # Remove single-quoted strings (no escaping inside single quotes in shell)
1564
+ result = re.sub(r"'[^']*'", "''", command)
1565
+ # Remove double-quoted strings (handle escaped quotes)
1566
+ result = re.sub(r'"(?:[^"\\]|\\.)*"', '""', result)
1567
+ # Remove backtick-quoted strings
1568
+ result = re.sub(r"`[^`]*`", "``", result)
1569
+ return result
1570
+
1571
+
1572
+ _LONG_LIVED_FOREGROUND_PATTERNS = (
1573
+ re.compile(r"\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:dev|start|serve|watch)\b", re.IGNORECASE),
1574
+ re.compile(r"\bdocker\s+compose\s+up\b", re.IGNORECASE),
1575
+ re.compile(r"\bnext\s+dev\b", re.IGNORECASE),
1576
+ re.compile(r"\bvite(?:\s|$)", re.IGNORECASE),
1577
+ re.compile(r"\bnodemon\b", re.IGNORECASE),
1578
+ re.compile(r"\buvicorn\b", re.IGNORECASE),
1579
+ re.compile(r"\bgunicorn\b", re.IGNORECASE),
1580
+ re.compile(r"\bpython(?:3)?\s+-m\s+http\.server\b", re.IGNORECASE),
1581
+ )
1582
+
1583
+
1584
+ def _looks_like_help_or_version_command(command: str) -> bool:
1585
+ """Return True for informational invocations that should never be blocked."""
1586
+ normalized = " ".join(command.lower().split())
1587
+ return (
1588
+ " --help" in normalized
1589
+ or normalized.endswith(" -h")
1590
+ or " --version" in normalized
1591
+ or normalized.endswith(" -v")
1592
+ )
1593
+
1594
+
1595
+ def _foreground_background_guidance(command: str) -> str | None:
1596
+ """Suggest background mode when a foreground command looks long-lived.
1597
+
1598
+ Prevents workflows that start a server/watch process and then stall before
1599
+ follow-up checks or test commands run.
1600
+ """
1601
+ if _looks_like_help_or_version_command(command):
1602
+ return None
1603
+
1604
+ # Strip quoted content so keywords inside strings/arguments don't trigger
1605
+ # false positives (e.g., git commit -m "... setsid ...", python3 -c "os.setsid").
1606
+ unquoted = _strip_quotes(command)
1607
+
1608
+ if _SHELL_LEVEL_BACKGROUND_RE.search(unquoted):
1609
+ return (
1610
+ "Foreground command uses shell-level background wrappers (nohup/disown/setsid). "
1611
+ "Use terminal(background=true) so Hermes can track the process, then run "
1612
+ "readiness checks and tests in separate commands."
1613
+ )
1614
+
1615
+ if _INLINE_BACKGROUND_AMP_RE.search(unquoted) or _TRAILING_BACKGROUND_AMP_RE.search(unquoted):
1616
+ return (
1617
+ "Foreground command uses '&' backgrounding. Use terminal(background=true) for long-lived "
1618
+ "processes, then run health checks and tests in follow-up terminal calls."
1619
+ )
1620
+
1621
+ for pattern in _LONG_LIVED_FOREGROUND_PATTERNS:
1622
+ if pattern.search(unquoted):
1623
+ return (
1624
+ "This foreground command appears to start a long-lived server/watch process. "
1625
+ "Run it with background=true, verify readiness (health endpoint/log signal), "
1626
+ "then execute tests in a separate command."
1627
+ )
1628
+
1629
+ return None
1630
+
1631
+
1632
+ def _resolve_notification_flag_conflict(
1633
+ *,
1634
+ notify_on_complete: bool,
1635
+ watch_patterns,
1636
+ background: bool,
1637
+ ) -> tuple:
1638
+ """Decide what to do when both notify_on_complete and watch_patterns are set.
1639
+
1640
+ These flags produce duplicate, delayed notifications when combined — one
1641
+ notification per watch-pattern match AND one on process exit, with async
1642
+ delivery that can spam the user long after the process ends. When both are
1643
+ set, we drop watch_patterns in favor of notify_on_complete (the more useful
1644
+ "let me know when it's done" signal) and return a human-readable note.
1645
+
1646
+ Returns:
1647
+ (watch_patterns_to_use, conflict_note). conflict_note is "" when there
1648
+ is no conflict.
1649
+ """
1650
+ if background and notify_on_complete and watch_patterns:
1651
+ note = (
1652
+ "watch_patterns ignored because notify_on_complete=True; "
1653
+ "these two flags produce duplicate notifications when combined"
1654
+ )
1655
+ return None, note
1656
+ return watch_patterns, ""
1657
+
1658
+
1659
+ def terminal_tool(
1660
+ command: str,
1661
+ background: bool = False,
1662
+ timeout: Optional[int] = None,
1663
+ task_id: Optional[str] = None,
1664
+ force: bool = False,
1665
+ workdir: Optional[str] = None,
1666
+ pty: bool = False,
1667
+ notify_on_complete: bool = False,
1668
+ watch_patterns: Optional[List[str]] = None,
1669
+ ) -> str:
1670
+ """
1671
+ Execute a command in the configured terminal environment.
1672
+
1673
+ Args:
1674
+ command: The command to execute
1675
+ background: Whether to run in background (default: False)
1676
+ timeout: Command timeout in seconds (default: from config)
1677
+ task_id: Unique identifier for environment isolation (optional)
1678
+ force: If True, skip dangerous command check (use after user confirms)
1679
+ workdir: Working directory for this command (optional, uses session cwd if not set)
1680
+ pty: If True, use pseudo-terminal for interactive CLI tools (local backend only)
1681
+ notify_on_complete: If True and background=True, you'll be notified exactly once when the process exits. The right choice for almost every long task. MUTUALLY EXCLUSIVE with watch_patterns.
1682
+ watch_patterns: List of strings to watch for in background output. HARD rate limit: 1 notification per 15s per process. After 3 strike windows in a row, watch_patterns is disabled and the session is auto-promoted to notify_on_complete. Use ONLY for rare, one-shot mid-process signals on long-lived processes (server readiness, migration-done markers). NEVER use in loops/batch jobs — error patterns there will hit the strike limit and get disabled. MUTUALLY EXCLUSIVE with notify_on_complete — set one, not both.
1683
+
1684
+ Returns:
1685
+ str: JSON string with output, exit_code, and error fields
1686
+
1687
+ Examples:
1688
+ # Execute a simple command
1689
+ >>> result = terminal_tool(command="ls -la /tmp")
1690
+
1691
+ # Run a background task
1692
+ >>> result = terminal_tool(command="python server.py", background=True)
1693
+
1694
+ # With custom timeout
1695
+ >>> result = terminal_tool(command="long_task.sh", timeout=300)
1696
+
1697
+ # Force run after user confirmation
1698
+ # Note: force parameter is internal only, not exposed to model API
1699
+ """
1700
+ try:
1701
+ if not isinstance(command, str):
1702
+ logger.warning(
1703
+ "Rejected invalid terminal command value: %s",
1704
+ type(command).__name__,
1705
+ )
1706
+ return json.dumps({
1707
+ "output": "",
1708
+ "exit_code": -1,
1709
+ "error": f"Invalid command: expected string, got {type(command).__name__}",
1710
+ "status": "error",
1711
+ }, ensure_ascii=False)
1712
+
1713
+ # Get configuration
1714
+ config = _get_env_config()
1715
+ env_type = config["env_type"]
1716
+
1717
+ # Use task_id for environment isolation. By default all subagent
1718
+ # task_ids collapse back to "default" so the top-level agent and
1719
+ # every delegate_task child share one container; only task_ids with
1720
+ # a registered env override (RL benchmarks) get isolated sandboxes.
1721
+ effective_task_id = _resolve_container_task_id(task_id)
1722
+
1723
+ # Check per-task overrides (set by environments like TerminalBench2Env)
1724
+ # before falling back to global env var config
1725
+ overrides = _task_env_overrides.get(effective_task_id, {})
1726
+
1727
+ # Select image based on env type, with per-task override support
1728
+ if env_type == "docker":
1729
+ image = overrides.get("docker_image") or config["docker_image"]
1730
+ elif env_type == "singularity":
1731
+ image = overrides.get("singularity_image") or config["singularity_image"]
1732
+ elif env_type == "modal":
1733
+ image = overrides.get("modal_image") or config["modal_image"]
1734
+ elif env_type == "daytona":
1735
+ image = overrides.get("daytona_image") or config["daytona_image"]
1736
+ else:
1737
+ image = ""
1738
+
1739
+ cwd = overrides.get("cwd") or config["cwd"]
1740
+ default_timeout = config["timeout"]
1741
+ effective_timeout = timeout or default_timeout
1742
+
1743
+ # Reject foreground commands where the model explicitly requests
1744
+ # a timeout above FOREGROUND_MAX_TIMEOUT — nudge it toward background.
1745
+ if not background and timeout and timeout > FOREGROUND_MAX_TIMEOUT:
1746
+ return json.dumps({
1747
+ "error": (
1748
+ f"Foreground timeout {timeout}s exceeds the maximum of "
1749
+ f"{FOREGROUND_MAX_TIMEOUT}s. Use background=true with "
1750
+ f"notify_on_complete=true for long-running commands."
1751
+ ),
1752
+ }, ensure_ascii=False)
1753
+
1754
+ # Guardrail: long-lived server/watch commands should run as managed
1755
+ # background sessions, not foreground shell hacks.
1756
+ if not background:
1757
+ guidance = _foreground_background_guidance(command)
1758
+ if guidance:
1759
+ return json.dumps({
1760
+ "output": "",
1761
+ "exit_code": -1,
1762
+ "error": guidance,
1763
+ "status": "error",
1764
+ }, ensure_ascii=False)
1765
+
1766
+ # Start cleanup thread
1767
+ _start_cleanup_thread()
1768
+
1769
+ # Get or create environment.
1770
+ # Use a per-task creation lock so concurrent tool calls for the same
1771
+ # task_id wait for the first one to finish creating the sandbox,
1772
+ # instead of each creating their own (wasting Modal resources).
1773
+ with _env_lock:
1774
+ if effective_task_id in _active_environments:
1775
+ _last_activity[effective_task_id] = time.time()
1776
+ env = _active_environments[effective_task_id]
1777
+ needs_creation = False
1778
+ else:
1779
+ needs_creation = True
1780
+
1781
+ if needs_creation:
1782
+ # Per-task lock: only one thread creates the sandbox, others wait
1783
+ with _creation_locks_lock:
1784
+ if effective_task_id not in _creation_locks:
1785
+ _creation_locks[effective_task_id] = threading.Lock()
1786
+ task_lock = _creation_locks[effective_task_id]
1787
+
1788
+ with task_lock:
1789
+ # Double-check after acquiring the per-task lock
1790
+ with _env_lock:
1791
+ if effective_task_id in _active_environments:
1792
+ _last_activity[effective_task_id] = time.time()
1793
+ env = _active_environments[effective_task_id]
1794
+ needs_creation = False
1795
+
1796
+ if needs_creation:
1797
+ if env_type == "singularity":
1798
+ _check_disk_usage_warning()
1799
+ logger.info("Creating new %s environment for task %s...", env_type, effective_task_id[:8])
1800
+ try:
1801
+ ssh_config = None
1802
+ if env_type == "ssh":
1803
+ ssh_config = {
1804
+ "host": config.get("ssh_host", ""),
1805
+ "user": config.get("ssh_user", ""),
1806
+ "port": config.get("ssh_port", 22),
1807
+ "key": config.get("ssh_key", ""),
1808
+ "persistent": config.get("ssh_persistent", False),
1809
+ }
1810
+
1811
+ container_config = None
1812
+ if env_type in {"docker", "singularity", "modal", "daytona", "vercel_sandbox"}:
1813
+ container_config = {
1814
+ "container_cpu": config.get("container_cpu", 1),
1815
+ "container_memory": config.get("container_memory", 5120),
1816
+ "container_disk": config.get("container_disk", 51200),
1817
+ "container_persistent": config.get("container_persistent", True),
1818
+ "modal_mode": config.get("modal_mode", "auto"),
1819
+ "vercel_runtime": config.get("vercel_runtime", ""),
1820
+ "docker_volumes": config.get("docker_volumes", []),
1821
+ "docker_mount_cwd_to_workspace": config.get("docker_mount_cwd_to_workspace", False),
1822
+ "docker_forward_env": config.get("docker_forward_env", []),
1823
+ "docker_env": config.get("docker_env", {}),
1824
+ "docker_run_as_host_user": config.get("docker_run_as_host_user", False),
1825
+ "docker_extra_args": config.get("docker_extra_args", []),
1826
+ }
1827
+
1828
+ local_config = None
1829
+ if env_type == "local":
1830
+ local_config = {
1831
+ "persistent": config.get("local_persistent", False),
1832
+ }
1833
+
1834
+ new_env = _create_environment(
1835
+ env_type=env_type,
1836
+ image=image,
1837
+ cwd=cwd,
1838
+ timeout=effective_timeout,
1839
+ ssh_config=ssh_config,
1840
+ container_config=container_config,
1841
+ local_config=local_config,
1842
+ task_id=effective_task_id,
1843
+ host_cwd=config.get("host_cwd"),
1844
+ )
1845
+ except ImportError as e:
1846
+ return json.dumps({
1847
+ "output": "",
1848
+ "exit_code": -1,
1849
+ "error": f"Terminal tool disabled: environment creation failed ({e})",
1850
+ "status": "disabled"
1851
+ }, ensure_ascii=False)
1852
+
1853
+ with _env_lock:
1854
+ _active_environments[effective_task_id] = new_env
1855
+ _last_activity[effective_task_id] = time.time()
1856
+ env = new_env
1857
+ logger.info("%s environment ready for task %s", env_type, effective_task_id[:8])
1858
+
1859
+ # Pre-exec security checks (tirith + dangerous command detection)
1860
+ # Skip check if force=True (user has confirmed they want to run it)
1861
+ approval_note = None
1862
+ if not force:
1863
+ approval = _check_all_guards(command, env_type)
1864
+ if not approval["approved"]:
1865
+ # Check if this is an approval_required (gateway ask mode)
1866
+ if approval.get("status") == "approval_required":
1867
+ return json.dumps({
1868
+ "output": "",
1869
+ "exit_code": -1,
1870
+ "error": approval.get("message", "Waiting for user approval"),
1871
+ "status": "approval_required",
1872
+ "command": approval.get("command", command),
1873
+ "description": approval.get("description", "command flagged"),
1874
+ "pattern_key": approval.get("pattern_key", ""),
1875
+ }, ensure_ascii=False)
1876
+ # Command was blocked
1877
+ desc = approval.get("description", "command flagged")
1878
+ fallback_msg = (
1879
+ f"Command denied: {desc}. "
1880
+ "Use the approval prompt to allow it, or rephrase the command."
1881
+ )
1882
+ return json.dumps({
1883
+ "output": "",
1884
+ "exit_code": -1,
1885
+ "error": approval.get("message", fallback_msg),
1886
+ "status": "blocked"
1887
+ }, ensure_ascii=False)
1888
+ # Track whether approval was explicitly granted by the user
1889
+ if approval.get("user_approved"):
1890
+ desc = approval.get("description", "flagged as dangerous")
1891
+ approval_note = f"Command required approval ({desc}) and was approved by the user."
1892
+ elif approval.get("smart_approved"):
1893
+ desc = approval.get("description", "flagged as dangerous")
1894
+ approval_note = f"Command was flagged ({desc}) and auto-approved by smart approval."
1895
+
1896
+ # Validate workdir against shell injection
1897
+ if workdir:
1898
+ workdir_error = _validate_workdir(workdir)
1899
+ if workdir_error:
1900
+ logger.warning("Blocked dangerous workdir: %s (command: %s)",
1901
+ workdir[:200], _safe_command_preview(command))
1902
+ return json.dumps({
1903
+ "output": "",
1904
+ "exit_code": -1,
1905
+ "error": workdir_error,
1906
+ "status": "blocked"
1907
+ }, ensure_ascii=False)
1908
+
1909
+ # Prepare command for execution
1910
+ pty_disabled_reason = None
1911
+ effective_pty = pty
1912
+ if pty and _command_requires_pipe_stdin(command):
1913
+ effective_pty = False
1914
+ pty_disabled_reason = (
1915
+ "PTY disabled for this command because it expects piped stdin/EOF "
1916
+ "(for example gh auth login --with-token). For local background "
1917
+ "processes, call process(action='close') after writing so it receives "
1918
+ "EOF."
1919
+ )
1920
+
1921
+ if background:
1922
+ # Spawn a tracked background process via the process registry.
1923
+ # For local backends: uses subprocess.Popen with output buffering.
1924
+ # For non-local backends: runs inside the sandbox via env.execute().
1925
+ from tools.approval import get_current_session_key
1926
+ from tools.process_registry import process_registry
1927
+
1928
+ session_key = get_current_session_key(default="")
1929
+ effective_cwd = workdir or cwd
1930
+ try:
1931
+ if env_type == "local":
1932
+ proc_session = process_registry.spawn_local(
1933
+ command=command,
1934
+ cwd=effective_cwd,
1935
+ task_id=effective_task_id,
1936
+ session_key=session_key,
1937
+ env_vars=env.env if hasattr(env, 'env') else None,
1938
+ use_pty=effective_pty,
1939
+ )
1940
+ else:
1941
+ proc_session = process_registry.spawn_via_env(
1942
+ env=env,
1943
+ command=command,
1944
+ cwd=effective_cwd,
1945
+ task_id=effective_task_id,
1946
+ session_key=session_key,
1947
+ )
1948
+
1949
+ result_data = {
1950
+ "output": "Background process started",
1951
+ "session_id": proc_session.id,
1952
+ "pid": proc_session.pid,
1953
+ "exit_code": 0,
1954
+ "error": None,
1955
+ }
1956
+ if approval_note:
1957
+ result_data["approval"] = approval_note
1958
+ if pty_disabled_reason:
1959
+ result_data["pty_note"] = pty_disabled_reason
1960
+
1961
+ # Populate routing metadata on the session so that
1962
+ # watch-pattern and completion notifications can be
1963
+ # routed back to the correct chat/thread.
1964
+ if background and (notify_on_complete or watch_patterns):
1965
+ from gateway.session_context import get_session_env as _gse
1966
+ _gw_platform = _gse("HERMES_SESSION_PLATFORM", "")
1967
+ if _gw_platform:
1968
+ _gw_chat_id = _gse("HERMES_SESSION_CHAT_ID", "")
1969
+ _gw_thread_id = _gse("HERMES_SESSION_THREAD_ID", "")
1970
+ _gw_user_id = _gse("HERMES_SESSION_USER_ID", "")
1971
+ _gw_user_name = _gse("HERMES_SESSION_USER_NAME", "")
1972
+ proc_session.watcher_platform = _gw_platform
1973
+ proc_session.watcher_chat_id = _gw_chat_id
1974
+ proc_session.watcher_user_id = _gw_user_id
1975
+ proc_session.watcher_user_name = _gw_user_name
1976
+ proc_session.watcher_thread_id = _gw_thread_id
1977
+
1978
+ # Mutual exclusion: if both notify_on_complete and watch_patterns
1979
+ # are set, drop watch_patterns. The combination produces duplicate
1980
+ # notifications (one per match + one on exit) that deliver
1981
+ # asynchronously and can spam the user long after the process ends.
1982
+ # notify_on_complete is the more useful signal for "let me know
1983
+ # when the task finishes"; watch_patterns should be reserved for
1984
+ # standalone mid-process signals on long-lived processes.
1985
+ watch_patterns, conflict_note = _resolve_notification_flag_conflict(
1986
+ notify_on_complete=bool(notify_on_complete),
1987
+ watch_patterns=watch_patterns,
1988
+ background=bool(background),
1989
+ )
1990
+ if conflict_note:
1991
+ logger.warning("background proc %s: %s", proc_session.id, conflict_note)
1992
+ result_data["watch_patterns_ignored"] = conflict_note
1993
+
1994
+ # Mark for agent notification on completion
1995
+ if notify_on_complete and background:
1996
+ proc_session.notify_on_complete = True
1997
+ result_data["notify_on_complete"] = True
1998
+
1999
+ # In gateway mode, auto-register a fast watcher so the
2000
+ # gateway can detect completion and trigger a new agent
2001
+ # turn. CLI mode uses the completion_queue directly.
2002
+ if proc_session.watcher_platform:
2003
+ proc_session.watcher_interval = 5
2004
+ process_registry.pending_watchers.append({
2005
+ "session_id": proc_session.id,
2006
+ "check_interval": 5,
2007
+ "session_key": session_key,
2008
+ "platform": proc_session.watcher_platform,
2009
+ "chat_id": proc_session.watcher_chat_id,
2010
+ "user_id": proc_session.watcher_user_id,
2011
+ "user_name": proc_session.watcher_user_name,
2012
+ "thread_id": proc_session.watcher_thread_id,
2013
+ "notify_on_complete": True,
2014
+ })
2015
+
2016
+ # Set watch patterns for output monitoring
2017
+ if watch_patterns and background:
2018
+ proc_session.watch_patterns = list(watch_patterns)
2019
+ result_data["watch_patterns"] = proc_session.watch_patterns
2020
+
2021
+ return json.dumps(result_data, ensure_ascii=False)
2022
+ except Exception as e:
2023
+ return json.dumps({
2024
+ "output": "",
2025
+ "exit_code": -1,
2026
+ "error": f"Failed to start background process: {str(e)}"
2027
+ }, ensure_ascii=False)
2028
+ else:
2029
+ # Run foreground command with retry logic
2030
+ max_retries = 3
2031
+ retry_count = 0
2032
+ result = None
2033
+
2034
+ while retry_count <= max_retries:
2035
+ try:
2036
+ execute_kwargs = {
2037
+ "timeout": effective_timeout,
2038
+ "cwd": workdir or cwd,
2039
+ }
2040
+ result = env.execute(command, **execute_kwargs)
2041
+ except Exception as e:
2042
+ error_str = str(e).lower()
2043
+ if "timeout" in error_str:
2044
+ return json.dumps({
2045
+ "output": "",
2046
+ "exit_code": 124,
2047
+ "error": f"Command timed out after {effective_timeout} seconds"
2048
+ }, ensure_ascii=False)
2049
+
2050
+ # Retry on transient errors
2051
+ if retry_count < max_retries:
2052
+ retry_count += 1
2053
+ wait_time = 2 ** retry_count
2054
+ logger.warning("Execution error, retrying in %ds (attempt %d/%d) - Command: %s - Error: %s: %s - Task: %s, Backend: %s",
2055
+ wait_time, retry_count, max_retries, _safe_command_preview(command), type(e).__name__, e, effective_task_id, env_type)
2056
+ time.sleep(wait_time)
2057
+ continue
2058
+
2059
+ logger.error("Execution failed after %d retries - Command: %s - Error: %s: %s - Task: %s, Backend: %s",
2060
+ max_retries, _safe_command_preview(command), type(e).__name__, e, effective_task_id, env_type)
2061
+ return json.dumps({
2062
+ "output": "",
2063
+ "exit_code": -1,
2064
+ "error": f"Command execution failed: {type(e).__name__}: {str(e)}"
2065
+ }, ensure_ascii=False)
2066
+
2067
+ # Got a result
2068
+ break
2069
+
2070
+ # Extract output
2071
+ output = result.get("output", "")
2072
+ returncode = result.get("returncode", 0)
2073
+
2074
+ # Add helpful message for sudo failures in messaging context
2075
+ output = _handle_sudo_failure(output, env_type)
2076
+
2077
+ # Foreground terminal output canonicalization seam: plugins receive
2078
+ # the full output string before default truncation and may only
2079
+ # replace it by returning a string from transform_terminal_output.
2080
+ # The hook is fail-open, and the first valid string return wins.
2081
+ try:
2082
+ from hermes_cli.plugins import invoke_hook
2083
+ hook_results = invoke_hook(
2084
+ "transform_terminal_output",
2085
+ command=command,
2086
+ output=output,
2087
+ returncode=returncode,
2088
+ task_id=effective_task_id or "",
2089
+ env_type=env_type,
2090
+ )
2091
+ for hook_result in hook_results:
2092
+ if isinstance(hook_result, str):
2093
+ output = hook_result
2094
+ break
2095
+ except Exception:
2096
+ pass
2097
+
2098
+ # Truncate output if too long, keeping both head and tail
2099
+ from tools.tool_output_limits import get_max_bytes
2100
+ MAX_OUTPUT_CHARS = get_max_bytes()
2101
+ if len(output) > MAX_OUTPUT_CHARS:
2102
+ head_chars = int(MAX_OUTPUT_CHARS * 0.4) # 40% head (error messages often appear early)
2103
+ tail_chars = MAX_OUTPUT_CHARS - head_chars # 60% tail (most recent/relevant output)
2104
+ omitted = len(output) - head_chars - tail_chars
2105
+ truncated_notice = (
2106
+ f"\n\n... [OUTPUT TRUNCATED - {omitted} chars omitted "
2107
+ f"out of {len(output)} total] ...\n\n"
2108
+ )
2109
+ output = output[:head_chars] + truncated_notice + output[-tail_chars:]
2110
+
2111
+ # Strip ANSI escape sequences so the model never sees terminal
2112
+ # formatting — prevents it from copying escapes into file writes.
2113
+ from tools.ansi_strip import strip_ansi
2114
+ output = strip_ansi(output)
2115
+
2116
+ # Redact secrets from command output (catches env/printenv leaking keys)
2117
+ from agent.redact import redact_sensitive_text
2118
+ output = redact_sensitive_text(output.strip()) if output else ""
2119
+
2120
+ # Interpret non-zero exit codes that aren't real errors
2121
+ # (e.g. grep=1 means "no matches", diff=1 means "files differ")
2122
+ exit_note = _interpret_exit_code(command, returncode)
2123
+
2124
+ result_dict = {
2125
+ "output": output,
2126
+ "exit_code": returncode,
2127
+ "error": None,
2128
+ }
2129
+ if approval_note:
2130
+ result_dict["approval"] = approval_note
2131
+ if exit_note:
2132
+ result_dict["exit_code_meaning"] = exit_note
2133
+
2134
+ return json.dumps(result_dict, ensure_ascii=False)
2135
+
2136
+ except Exception as e:
2137
+ import traceback
2138
+ tb_str = traceback.format_exc()
2139
+ logger.error("terminal_tool exception:\n%s", tb_str)
2140
+ return json.dumps({
2141
+ "output": "",
2142
+ "exit_code": -1,
2143
+ "error": f"Failed to execute command: {str(e)}",
2144
+ "traceback": tb_str,
2145
+ "status": "error"
2146
+ }, ensure_ascii=False)
2147
+
2148
+
2149
+ def check_terminal_requirements() -> bool:
2150
+ """Check if all requirements for the terminal tool are met."""
2151
+ try:
2152
+ config = _get_env_config()
2153
+ env_type = config["env_type"]
2154
+
2155
+ if env_type == "local":
2156
+ return True
2157
+
2158
+ elif env_type == "docker":
2159
+ from tools.environments.docker import find_docker
2160
+ docker = find_docker()
2161
+ if not docker:
2162
+ logger.error("Docker executable not found in PATH or common install locations")
2163
+ return False
2164
+ result = subprocess.run([docker, "version"], capture_output=True, timeout=5)
2165
+ return result.returncode == 0
2166
+
2167
+ elif env_type == "singularity":
2168
+ executable = shutil.which("apptainer") or shutil.which("singularity")
2169
+ if executable:
2170
+ result = subprocess.run([executable, "--version"], capture_output=True, timeout=5)
2171
+ return result.returncode == 0
2172
+ return False
2173
+
2174
+ elif env_type == "ssh":
2175
+ if not config.get("ssh_host") or not config.get("ssh_user"):
2176
+ logger.error(
2177
+ "SSH backend selected but TERMINAL_SSH_HOST and TERMINAL_SSH_USER "
2178
+ "are not both set. Configure both or switch TERMINAL_ENV to 'local'."
2179
+ )
2180
+ return False
2181
+ return True
2182
+
2183
+ elif env_type == "modal":
2184
+ modal_state = _get_modal_backend_state(config.get("modal_mode"))
2185
+ if modal_state["selected_backend"] == "managed":
2186
+ return True
2187
+
2188
+ if modal_state["selected_backend"] != "direct":
2189
+ if modal_state["managed_mode_blocked"]:
2190
+ logger.error(
2191
+ "Modal backend selected with TERMINAL_MODAL_MODE=managed, but "
2192
+ "a paid Nous subscription is required for the Tool Gateway and no direct "
2193
+ "Modal credentials/config were found. Log in with `hermes model` "
2194
+ "or choose TERMINAL_MODAL_MODE=direct/auto."
2195
+ )
2196
+ return False
2197
+ if modal_state["mode"] == "managed":
2198
+ logger.error(
2199
+ "Modal backend selected with TERMINAL_MODAL_MODE=managed, but the managed "
2200
+ "tool gateway is unavailable. Configure the managed gateway or choose "
2201
+ "TERMINAL_MODAL_MODE=direct/auto."
2202
+ )
2203
+ return False
2204
+ elif modal_state["mode"] == "direct":
2205
+ if managed_nous_tools_enabled():
2206
+ logger.error(
2207
+ "Modal backend selected with TERMINAL_MODAL_MODE=direct, but no direct "
2208
+ "Modal credentials/config were found. Configure Modal or choose "
2209
+ "TERMINAL_MODAL_MODE=managed/auto."
2210
+ )
2211
+ else:
2212
+ logger.error(
2213
+ "Modal backend selected with TERMINAL_MODAL_MODE=direct, but no direct "
2214
+ "Modal credentials/config were found. Configure Modal or choose "
2215
+ "TERMINAL_MODAL_MODE=auto."
2216
+ )
2217
+ return False
2218
+ else:
2219
+ if managed_nous_tools_enabled():
2220
+ logger.error(
2221
+ "Modal backend selected but no direct Modal credentials/config or managed "
2222
+ "tool gateway was found. Configure Modal, set up the managed gateway, "
2223
+ "or choose a different TERMINAL_ENV."
2224
+ )
2225
+ else:
2226
+ logger.error(
2227
+ "Modal backend selected but no direct Modal credentials/config was found. "
2228
+ "Configure Modal or choose a different TERMINAL_ENV."
2229
+ )
2230
+ return False
2231
+
2232
+ if importlib.util.find_spec("modal") is None:
2233
+ logger.error("modal is required for direct modal terminal backend: pip install modal")
2234
+ return False
2235
+
2236
+ return True
2237
+
2238
+ elif env_type == "vercel_sandbox":
2239
+ return _check_vercel_sandbox_requirements(config)
2240
+
2241
+ elif env_type == "daytona":
2242
+ from daytona import Daytona # noqa: F401 — SDK presence check
2243
+ return os.getenv("DAYTONA_API_KEY") is not None
2244
+
2245
+ else:
2246
+ logger.error(
2247
+ "Unknown TERMINAL_ENV '%s'. Use one of: local, docker, singularity, "
2248
+ "modal, daytona, vercel_sandbox, ssh.",
2249
+ env_type,
2250
+ )
2251
+ return False
2252
+ except Exception as e:
2253
+ logger.error("Terminal requirements check failed: %s", e, exc_info=True)
2254
+ return False
2255
+
2256
+
2257
+ if __name__ == "__main__":
2258
+ # Simple test when run directly
2259
+ print("Terminal Tool Module")
2260
+ print("=" * 50)
2261
+
2262
+ config = _get_env_config()
2263
+ print("\nCurrent Configuration:")
2264
+ print(f" Environment type: {config['env_type']}")
2265
+ print(f" Docker image: {config['docker_image']}")
2266
+ print(f" Modal image: {config['modal_image']}")
2267
+ print(f" Working directory: {config['cwd']}")
2268
+ print(f" Default timeout: {config['timeout']}s")
2269
+ print(f" Lifetime: {config['lifetime_seconds']}s")
2270
+
2271
+ if not check_terminal_requirements():
2272
+ print("\n❌ Requirements not met. Please check the messages above.")
2273
+ sys.exit(1)
2274
+
2275
+ print("\n✅ All requirements met!")
2276
+ print("\nAvailable Tool:")
2277
+ print(" - terminal_tool: Execute commands in sandboxed environments")
2278
+
2279
+ print("\nUsage Examples:")
2280
+ print(" # Execute a command")
2281
+ print(" result = terminal_tool(command='ls -la')")
2282
+ print(" ")
2283
+ print(" # Run a background task")
2284
+ print(" result = terminal_tool(command='python server.py', background=True)")
2285
+
2286
+ print("\nEnvironment Variables:")
2287
+ default_img = "nikolaik/python-nodejs:python3.11-nodejs20"
2288
+ print(
2289
+ " TERMINAL_ENV: "
2290
+ f"{os.getenv('TERMINAL_ENV', 'local')} "
2291
+ "(local/docker/singularity/modal/daytona/vercel_sandbox/ssh)"
2292
+ )
2293
+ print(f" TERMINAL_DOCKER_IMAGE: {os.getenv('TERMINAL_DOCKER_IMAGE', default_img)}")
2294
+ print(f" TERMINAL_SINGULARITY_IMAGE: {os.getenv('TERMINAL_SINGULARITY_IMAGE', f'docker://{default_img}')}")
2295
+ print(f" TERMINAL_MODAL_IMAGE: {os.getenv('TERMINAL_MODAL_IMAGE', default_img)}")
2296
+ print(f" TERMINAL_DAYTONA_IMAGE: {os.getenv('TERMINAL_DAYTONA_IMAGE', default_img)}")
2297
+ print(f" TERMINAL_CWD: {os.getenv('TERMINAL_CWD', os.getcwd())}")
2298
+ from calvyn_constants import display_hermes_home as _dhh
2299
+ print(f" TERMINAL_SANDBOX_DIR: {os.getenv('TERMINAL_SANDBOX_DIR', f'{_dhh()}/sandboxes')}")
2300
+ print(f" TERMINAL_TIMEOUT: {os.getenv('TERMINAL_TIMEOUT', '60')}")
2301
+ print(f" TERMINAL_LIFETIME_SECONDS: {os.getenv('TERMINAL_LIFETIME_SECONDS', '300')}")
2302
+
2303
+
2304
+ # ---------------------------------------------------------------------------
2305
+ # Registry
2306
+ # ---------------------------------------------------------------------------
2307
+ from tools.registry import registry
2308
+
2309
+ TERMINAL_SCHEMA = {
2310
+ "name": "terminal",
2311
+ "description": TERMINAL_TOOL_DESCRIPTION,
2312
+ "parameters": {
2313
+ "type": "object",
2314
+ "properties": {
2315
+ "command": {
2316
+ "type": "string",
2317
+ "description": "The command to execute on the VM"
2318
+ },
2319
+ "background": {
2320
+ "type": "boolean",
2321
+ "description": "Run the command in the background. Two patterns: (1) Long-lived processes that never exit (servers, watchers). (2) Long-running tasks paired with notify_on_complete=true — you can keep working and get notified when the task finishes. For short commands, prefer foreground with a generous timeout instead.",
2322
+ "default": False
2323
+ },
2324
+ "timeout": {
2325
+ "type": "integer",
2326
+ "description": f"Max seconds to wait (default: 180, foreground max: {FOREGROUND_MAX_TIMEOUT}). Returns INSTANTLY when command finishes — set high for long tasks, you won't wait unnecessarily. Foreground timeout above {FOREGROUND_MAX_TIMEOUT}s is rejected; use background=true for longer commands.",
2327
+ "minimum": 1
2328
+ },
2329
+ "workdir": {
2330
+ "type": "string",
2331
+ "description": "Working directory for this command (absolute path). Defaults to the session working directory."
2332
+ },
2333
+ "pty": {
2334
+ "type": "boolean",
2335
+ "description": "Run in pseudo-terminal (PTY) mode for interactive CLI tools like Codex, Claude Code, or Python REPL. Only works with local and SSH backends. Default: false.",
2336
+ "default": False
2337
+ },
2338
+ "notify_on_complete": {
2339
+ "type": "boolean",
2340
+ "description": "When true (and background=true), you'll be automatically notified exactly once when the process finishes. **This is the right choice for almost every long-running task** — tests, builds, deployments, multi-item batch jobs, anything that takes over a minute and has a defined end. Use this and keep working on other things; the system notifies you on exit. MUTUALLY EXCLUSIVE with watch_patterns — when both are set, watch_patterns is dropped.",
2341
+ "default": False
2342
+ },
2343
+ "watch_patterns": {
2344
+ "type": "array",
2345
+ "items": {"type": "string"},
2346
+ "description": "Strings to watch for in background process output. HARD RATE LIMIT: at most 1 notification per 15 seconds per process — matches arriving inside the cooldown are dropped. After 3 consecutive 15-second windows with dropped matches, watch_patterns is automatically disabled for that process and promoted to notify_on_complete behavior (one notification on exit, no more mid-process spam). USE ONLY for truly rare, one-shot mid-process signals on LONG-LIVED processes that will never exit on their own — e.g. ['Application startup complete'] on a server so you know when to hit its endpoint, or ['migration done'] on a daemon. DO NOT use for: (1) end-of-run markers like 'DONE'/'PASS' — use notify_on_complete instead; (2) error patterns like 'ERROR'/'Traceback' in loops or multi-item batch jobs — they fire on every iteration and you'll hit the strike limit fast; (3) anything you'd ever combine with notify_on_complete. When in doubt, choose notify_on_complete. MUTUALLY EXCLUSIVE with notify_on_complete — set one, not both."
2347
+ }
2348
+ },
2349
+ "required": ["command"]
2350
+ }
2351
+ }
2352
+
2353
+
2354
+ def _handle_terminal(args, **kw):
2355
+ return terminal_tool(
2356
+ command=args.get("command"),
2357
+ background=args.get("background", False),
2358
+ timeout=args.get("timeout"),
2359
+ task_id=kw.get("task_id"),
2360
+ workdir=args.get("workdir"),
2361
+ pty=args.get("pty", False),
2362
+ notify_on_complete=args.get("notify_on_complete", False),
2363
+ watch_patterns=args.get("watch_patterns"),
2364
+ )
2365
+
2366
+
2367
+ registry.register(
2368
+ name="terminal",
2369
+ toolset="terminal",
2370
+ schema=TERMINAL_SCHEMA,
2371
+ handler=_handle_terminal,
2372
+ check_fn=check_terminal_requirements,
2373
+ emoji="💻",
2374
+ max_result_size_chars=100_000,
2375
+ )
2376
+