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,1780 @@
1
+ """Persistent multi-credential pool for same-provider failover."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import os
7
+ import random
8
+ import threading
9
+ import time
10
+ import uuid
11
+ import re
12
+ from dataclasses import dataclass, fields, replace
13
+ from datetime import datetime
14
+ from typing import Any, Dict, List, Optional, Set, Tuple
15
+
16
+ from calvyn_constants import OPENROUTER_BASE_URL
17
+ from hermes_cli.config import get_env_value, load_env
18
+ import hermes_cli.auth as auth_mod
19
+ from hermes_cli.auth import (
20
+ CODEX_ACCESS_TOKEN_REFRESH_SKEW_SECONDS,
21
+ DEFAULT_AGENT_KEY_MIN_TTL_SECONDS,
22
+ PROVIDER_REGISTRY,
23
+ _auth_store_lock,
24
+ _codex_access_token_is_expiring,
25
+ _decode_jwt_claims,
26
+ _load_auth_store,
27
+ _load_provider_state,
28
+ _resolve_kimi_base_url,
29
+ _resolve_zai_base_url,
30
+ _save_auth_store,
31
+ _save_provider_state,
32
+ _store_provider_state,
33
+ read_credential_pool,
34
+ write_credential_pool,
35
+ )
36
+
37
+ logger = logging.getLogger(__name__)
38
+
39
+
40
+ def _load_config_safe() -> Optional[dict]:
41
+ """Load config.yaml, returning None on any error."""
42
+ try:
43
+ from hermes_cli.config import load_config
44
+
45
+ return load_config()
46
+ except Exception:
47
+ return None
48
+
49
+
50
+ # --- Status and type constants ---
51
+
52
+ STATUS_OK = "ok"
53
+ STATUS_EXHAUSTED = "exhausted"
54
+
55
+ AUTH_TYPE_OAUTH = "oauth"
56
+ AUTH_TYPE_API_KEY = "api_key"
57
+
58
+ SOURCE_MANUAL = "manual"
59
+
60
+ STRATEGY_FILL_FIRST = "fill_first"
61
+ STRATEGY_ROUND_ROBIN = "round_robin"
62
+ STRATEGY_RANDOM = "random"
63
+ STRATEGY_LEAST_USED = "least_used"
64
+ SUPPORTED_POOL_STRATEGIES = {
65
+ STRATEGY_FILL_FIRST,
66
+ STRATEGY_ROUND_ROBIN,
67
+ STRATEGY_RANDOM,
68
+ STRATEGY_LEAST_USED,
69
+ }
70
+
71
+ # Cooldown before retrying an exhausted credential.
72
+ # Transient 401 auth failures cool down briefly so single-key setups can recover.
73
+ # 429 (rate-limited), 402 (billing/quota), and other failures cool down after 1 hour.
74
+ # Provider-supplied reset_at timestamps override these defaults.
75
+ EXHAUSTED_TTL_401_SECONDS = 5 * 60 # 5 minutes
76
+ EXHAUSTED_TTL_429_SECONDS = 60 * 60 # 1 hour
77
+ EXHAUSTED_TTL_DEFAULT_SECONDS = 60 * 60 # 1 hour
78
+
79
+ # Pool key prefix for custom OpenAI-compatible endpoints.
80
+ # Custom endpoints all share provider='custom' but are keyed by their
81
+ # custom_providers name: 'custom:<normalized_name>'.
82
+ CUSTOM_POOL_PREFIX = "custom:"
83
+
84
+
85
+ # Fields that are only round-tripped through JSON — never used for logic as attributes.
86
+ _EXTRA_KEYS = frozenset({
87
+ "token_type", "scope", "client_id", "portal_base_url", "obtained_at",
88
+ "expires_in", "agent_key_id", "agent_key_expires_in", "agent_key_reused",
89
+ "agent_key_obtained_at", "tls",
90
+ })
91
+
92
+
93
+ @dataclass
94
+ class PooledCredential:
95
+ provider: str
96
+ id: str
97
+ label: str
98
+ auth_type: str
99
+ priority: int
100
+ source: str
101
+ access_token: str
102
+ refresh_token: Optional[str] = None
103
+ last_status: Optional[str] = None
104
+ last_status_at: Optional[float] = None
105
+ last_error_code: Optional[int] = None
106
+ last_error_reason: Optional[str] = None
107
+ last_error_message: Optional[str] = None
108
+ last_error_reset_at: Optional[float] = None
109
+ base_url: Optional[str] = None
110
+ expires_at: Optional[str] = None
111
+ expires_at_ms: Optional[int] = None
112
+ last_refresh: Optional[str] = None
113
+ inference_base_url: Optional[str] = None
114
+ agent_key: Optional[str] = None
115
+ agent_key_expires_at: Optional[str] = None
116
+ request_count: int = 0
117
+ extra: Dict[str, Any] = None # type: ignore[assignment]
118
+
119
+ def __post_init__(self):
120
+ if self.extra is None:
121
+ self.extra = {}
122
+
123
+ def __getattr__(self, name: str):
124
+ if name in _EXTRA_KEYS:
125
+ return self.extra.get(name)
126
+ raise AttributeError(f"'{type(self).__name__}' object has no attribute {name!r}")
127
+
128
+ @classmethod
129
+ def from_dict(cls, provider: str, payload: Dict[str, Any]) -> "PooledCredential":
130
+ field_names = {f.name for f in fields(cls) if f.name != "provider"}
131
+ data = {k: payload.get(k) for k in field_names if k in payload}
132
+ extra = {k: payload[k] for k in _EXTRA_KEYS if k in payload and payload[k] is not None}
133
+ data["extra"] = extra
134
+ data.setdefault("id", uuid.uuid4().hex[:6])
135
+ data.setdefault("label", payload.get("source", provider))
136
+ data.setdefault("auth_type", AUTH_TYPE_API_KEY)
137
+ data.setdefault("priority", 0)
138
+ data.setdefault("source", SOURCE_MANUAL)
139
+ data.setdefault("access_token", "")
140
+ return cls(provider=provider, **data)
141
+
142
+ def to_dict(self) -> Dict[str, Any]:
143
+ _ALWAYS_EMIT = {
144
+ "last_status",
145
+ "last_status_at",
146
+ "last_error_code",
147
+ "last_error_reason",
148
+ "last_error_message",
149
+ "last_error_reset_at",
150
+ }
151
+ result: Dict[str, Any] = {}
152
+ for field_def in fields(self):
153
+ if field_def.name in {"provider", "extra"}:
154
+ continue
155
+ value = getattr(self, field_def.name)
156
+ if value is not None or field_def.name in _ALWAYS_EMIT:
157
+ result[field_def.name] = value
158
+ for k, v in self.extra.items():
159
+ if v is not None:
160
+ result[k] = v
161
+ return result
162
+
163
+ @property
164
+ def runtime_api_key(self) -> str:
165
+ if self.provider == "nous":
166
+ return str(self.agent_key or self.access_token or "")
167
+ return str(self.access_token or "")
168
+
169
+ @property
170
+ def runtime_base_url(self) -> Optional[str]:
171
+ if self.provider == "nous":
172
+ return self.inference_base_url or self.base_url
173
+ return self.base_url
174
+
175
+
176
+ def label_from_token(token: str, fallback: str) -> str:
177
+ claims = _decode_jwt_claims(token)
178
+ for key in ("email", "preferred_username", "upn"):
179
+ value = claims.get(key)
180
+ if isinstance(value, str) and value.strip():
181
+ return value.strip()
182
+ return fallback
183
+
184
+
185
+ def _next_priority(entries: List[PooledCredential]) -> int:
186
+ return max((entry.priority for entry in entries), default=-1) + 1
187
+
188
+
189
+ def _is_manual_source(source: str) -> bool:
190
+ normalized = (source or "").strip().lower()
191
+ return normalized == SOURCE_MANUAL or normalized.startswith(f"{SOURCE_MANUAL}:")
192
+
193
+
194
+ def _exhausted_ttl(error_code: Optional[int]) -> int:
195
+ """Return cooldown seconds based on the HTTP status that caused exhaustion."""
196
+ if error_code == 401:
197
+ return EXHAUSTED_TTL_401_SECONDS
198
+ if error_code == 429:
199
+ return EXHAUSTED_TTL_429_SECONDS
200
+ return EXHAUSTED_TTL_DEFAULT_SECONDS
201
+
202
+
203
+ def _parse_absolute_timestamp(value: Any) -> Optional[float]:
204
+ """Best-effort parse for provider reset timestamps.
205
+
206
+ Accepts epoch seconds, epoch milliseconds, and ISO-8601 strings.
207
+ Returns seconds since epoch.
208
+ """
209
+ if value is None or value == "":
210
+ return None
211
+ if isinstance(value, (int, float)):
212
+ numeric = float(value)
213
+ if numeric <= 0:
214
+ return None
215
+ return numeric / 1000.0 if numeric > 1_000_000_000_000 else numeric
216
+ if isinstance(value, str):
217
+ raw = value.strip()
218
+ if not raw:
219
+ return None
220
+ try:
221
+ numeric = float(raw)
222
+ except ValueError:
223
+ numeric = None
224
+ if numeric is not None:
225
+ return numeric / 1000.0 if numeric > 1_000_000_000_000 else numeric
226
+ try:
227
+ return datetime.fromisoformat(raw.replace("Z", "+00:00")).timestamp()
228
+ except ValueError:
229
+ return None
230
+ return None
231
+
232
+
233
+ def _extract_retry_delay_seconds(message: str) -> Optional[float]:
234
+ if not message:
235
+ return None
236
+ delay_match = re.search(r"quotaResetDelay[:\s\"]+(\d+(?:\.\d+)?)(ms|s)", message, re.IGNORECASE)
237
+ if delay_match:
238
+ value = float(delay_match.group(1))
239
+ return value / 1000.0 if delay_match.group(2).lower() == "ms" else value
240
+ sec_match = re.search(r"retry\s+(?:after\s+)?(\d+(?:\.\d+)?)\s*(?:sec|secs|seconds|s\b)", message, re.IGNORECASE)
241
+ if sec_match:
242
+ return float(sec_match.group(1))
243
+ return None
244
+
245
+
246
+ def _normalize_error_context(error_context: Optional[Dict[str, Any]]) -> Dict[str, Any]:
247
+ if not isinstance(error_context, dict):
248
+ return {}
249
+ normalized: Dict[str, Any] = {}
250
+ reason = error_context.get("reason")
251
+ if isinstance(reason, str) and reason.strip():
252
+ normalized["reason"] = reason.strip()
253
+ message = error_context.get("message")
254
+ if isinstance(message, str) and message.strip():
255
+ normalized["message"] = message.strip()
256
+ reset_at = (
257
+ error_context.get("reset_at")
258
+ or error_context.get("resets_at")
259
+ or error_context.get("retry_until")
260
+ )
261
+ parsed_reset_at = _parse_absolute_timestamp(reset_at)
262
+ if parsed_reset_at is None and isinstance(message, str):
263
+ retry_delay_seconds = _extract_retry_delay_seconds(message)
264
+ if retry_delay_seconds is not None:
265
+ parsed_reset_at = time.time() + retry_delay_seconds
266
+ if parsed_reset_at is not None:
267
+ normalized["reset_at"] = parsed_reset_at
268
+ return normalized
269
+
270
+
271
+ def _exhausted_until(entry: PooledCredential) -> Optional[float]:
272
+ if entry.last_status != STATUS_EXHAUSTED:
273
+ return None
274
+ reset_at = _parse_absolute_timestamp(getattr(entry, "last_error_reset_at", None))
275
+ if reset_at is not None:
276
+ return reset_at
277
+ if entry.last_status_at:
278
+ return entry.last_status_at + _exhausted_ttl(entry.last_error_code)
279
+ return None
280
+
281
+
282
+ def _normalize_custom_pool_name(name: str) -> str:
283
+ """Normalize a custom provider name for use as a pool key suffix."""
284
+ return name.strip().lower().replace(" ", "-")
285
+
286
+
287
+ def _iter_custom_providers(config: Optional[dict] = None):
288
+ """Yield (normalized_name, entry_dict) for each valid custom_providers entry."""
289
+ if config is None:
290
+ config = _load_config_safe()
291
+ if config is None:
292
+ return
293
+ custom_providers = config.get("custom_providers")
294
+ if not isinstance(custom_providers, list):
295
+ # Fall back to the v12+ providers dict via the compatibility layer
296
+ try:
297
+ from hermes_cli.config import get_compatible_custom_providers
298
+
299
+ custom_providers = get_compatible_custom_providers(config)
300
+ except Exception:
301
+ return
302
+ if not custom_providers:
303
+ return
304
+ for entry in custom_providers:
305
+ if not isinstance(entry, dict):
306
+ continue
307
+ name = entry.get("name")
308
+ if not isinstance(name, str):
309
+ continue
310
+ yield _normalize_custom_pool_name(name), entry
311
+
312
+
313
+ def get_custom_provider_pool_key(base_url: str, provider_name: Optional[str] = None) -> Optional[str]:
314
+ """Look up the custom_providers list in config.yaml and return 'custom:<name>' for a matching base_url.
315
+
316
+ When provider_name is given, prefer matching by name first (solving the case where
317
+ multiple custom providers share the same base_url but have different API keys).
318
+ Falls back to base_url matching when no name match is found.
319
+
320
+ Returns None if no match is found.
321
+ """
322
+ if not base_url:
323
+ return None
324
+ normalized_url = base_url.strip().rstrip("/")
325
+
326
+ # When a provider name is given, try to match by name first.
327
+ # This fixes the P1 bug where two custom providers sharing the same
328
+ # base_url always resolve to the first one's credentials.
329
+ if provider_name:
330
+ normalized_name = _normalize_custom_pool_name(provider_name)
331
+ for norm_name, entry in _iter_custom_providers():
332
+ if norm_name == normalized_name:
333
+ return f"{CUSTOM_POOL_PREFIX}{norm_name}"
334
+
335
+ # Fall back to base_url matching (original behavior)
336
+ for norm_name, entry in _iter_custom_providers():
337
+ entry_url = str(entry.get("base_url") or "").strip().rstrip("/")
338
+ if entry_url and entry_url == normalized_url:
339
+ return f"{CUSTOM_POOL_PREFIX}{norm_name}"
340
+ return None
341
+
342
+
343
+ def list_custom_pool_providers() -> List[str]:
344
+ """Return all 'custom:*' pool keys that have entries in auth.json."""
345
+ pool_data = read_credential_pool(None)
346
+ return sorted(
347
+ key for key in pool_data
348
+ if key.startswith(CUSTOM_POOL_PREFIX)
349
+ and isinstance(pool_data.get(key), list)
350
+ and pool_data[key]
351
+ )
352
+
353
+
354
+ def _get_custom_provider_config(pool_key: str) -> Optional[Dict[str, Any]]:
355
+ """Return the custom_providers config entry matching a pool key like 'custom:together.ai'."""
356
+ if not pool_key.startswith(CUSTOM_POOL_PREFIX):
357
+ return None
358
+ suffix = pool_key[len(CUSTOM_POOL_PREFIX):]
359
+ for norm_name, entry in _iter_custom_providers():
360
+ if norm_name == suffix:
361
+ return entry
362
+ return None
363
+
364
+
365
+ def get_pool_strategy(provider: str) -> str:
366
+ """Return the configured selection strategy for a provider."""
367
+ config = _load_config_safe()
368
+ if config is None:
369
+ return STRATEGY_FILL_FIRST
370
+
371
+ strategies = config.get("credential_pool_strategies")
372
+ if not isinstance(strategies, dict):
373
+ return STRATEGY_FILL_FIRST
374
+
375
+ strategy = str(strategies.get(provider, "") or "").strip().lower()
376
+ if strategy in SUPPORTED_POOL_STRATEGIES:
377
+ return strategy
378
+ return STRATEGY_FILL_FIRST
379
+
380
+
381
+ DEFAULT_MAX_CONCURRENT_PER_CREDENTIAL = 1
382
+
383
+
384
+ class CredentialPool:
385
+ def __init__(self, provider: str, entries: List[PooledCredential]):
386
+ self.provider = provider
387
+ self._entries = sorted(entries, key=lambda entry: entry.priority)
388
+ self._current_id: Optional[str] = None
389
+ self._strategy = get_pool_strategy(provider)
390
+ self._lock = threading.Lock()
391
+ self._active_leases: Dict[str, int] = {}
392
+ self._max_concurrent = DEFAULT_MAX_CONCURRENT_PER_CREDENTIAL
393
+
394
+ def has_credentials(self) -> bool:
395
+ return bool(self._entries)
396
+
397
+ def has_available(self) -> bool:
398
+ """True if at least one entry is not currently in exhaustion cooldown."""
399
+ return bool(self._available_entries())
400
+
401
+ def entries(self) -> List[PooledCredential]:
402
+ return list(self._entries)
403
+
404
+ def current(self) -> Optional[PooledCredential]:
405
+ if not self._current_id:
406
+ return None
407
+ return next((entry for entry in self._entries if entry.id == self._current_id), None)
408
+
409
+ def _replace_entry(self, old: PooledCredential, new: PooledCredential) -> None:
410
+ """Swap an entry in-place by id, preserving sort order."""
411
+ for idx, entry in enumerate(self._entries):
412
+ if entry.id == old.id:
413
+ self._entries[idx] = new
414
+ return
415
+
416
+ def _persist(self) -> None:
417
+ write_credential_pool(
418
+ self.provider,
419
+ [entry.to_dict() for entry in self._entries],
420
+ )
421
+
422
+ def _mark_exhausted(
423
+ self,
424
+ entry: PooledCredential,
425
+ status_code: Optional[int],
426
+ error_context: Optional[Dict[str, Any]] = None,
427
+ ) -> PooledCredential:
428
+ normalized_error = _normalize_error_context(error_context)
429
+ updated = replace(
430
+ entry,
431
+ last_status=STATUS_EXHAUSTED,
432
+ last_status_at=time.time(),
433
+ last_error_code=status_code,
434
+ last_error_reason=normalized_error.get("reason"),
435
+ last_error_message=normalized_error.get("message"),
436
+ last_error_reset_at=normalized_error.get("reset_at"),
437
+ )
438
+ self._replace_entry(entry, updated)
439
+ self._persist()
440
+ return updated
441
+
442
+ def _sync_anthropic_entry_from_credentials_file(self, entry: PooledCredential) -> PooledCredential:
443
+ """Sync a claude_code pool entry from ~/.claude/.credentials.json if tokens differ.
444
+
445
+ OAuth refresh tokens are single-use. When something external (e.g.
446
+ Claude Code CLI, or another profile's pool) refreshes the token, it
447
+ writes the new pair to ~/.claude/.credentials.json. The pool entry's
448
+ refresh token becomes stale. This method detects that and syncs.
449
+ """
450
+ if self.provider != "anthropic" or entry.source != "claude_code":
451
+ return entry
452
+ try:
453
+ from agent.anthropic_adapter import read_claude_code_credentials
454
+ creds = read_claude_code_credentials()
455
+ if not creds:
456
+ return entry
457
+ file_refresh = creds.get("refreshToken", "")
458
+ file_access = creds.get("accessToken", "")
459
+ file_expires = creds.get("expiresAt", 0)
460
+ # If the credentials file has a different token pair, sync it
461
+ if file_refresh and file_refresh != entry.refresh_token:
462
+ logger.debug("Pool entry %s: syncing tokens from credentials file (refresh token changed)", entry.id)
463
+ updated = replace(
464
+ entry,
465
+ access_token=file_access,
466
+ refresh_token=file_refresh,
467
+ expires_at_ms=file_expires,
468
+ last_status=None,
469
+ last_status_at=None,
470
+ last_error_code=None,
471
+ )
472
+ self._replace_entry(entry, updated)
473
+ self._persist()
474
+ return updated
475
+ except Exception as exc:
476
+ logger.debug("Failed to sync from credentials file: %s", exc)
477
+ return entry
478
+
479
+ def _sync_codex_entry_from_auth_store(self, entry: PooledCredential) -> PooledCredential:
480
+ """Sync a Codex device_code pool entry from auth.json if tokens differ.
481
+
482
+ When a Codex OAuth access token expires (or the ChatGPT account hits
483
+ its 5h/weekly quota), the pool entry gets marked ``STATUS_EXHAUSTED``
484
+ with a ``last_error_reset_at`` that can be many hours in the future.
485
+ Meanwhile the user may run ``hermes model`` / ``hermes auth`` which
486
+ performs a fresh device-code login and writes new tokens to
487
+ ``auth.json`` under ``_auth_store_lock``. Without this sync the pool
488
+ entry stays frozen until ``last_error_reset_at`` elapses — even
489
+ though fresh credentials are sitting on disk — and every request
490
+ fails with "no available entries (all exhausted or empty)".
491
+
492
+ Mirrors the Nous/Anthropic resync paths above. Only applies to
493
+ device_code-sourced entries; env/API-key-sourced entries have no
494
+ auth.json shadow to sync from.
495
+ """
496
+ if self.provider != "openai-codex" or entry.source != "device_code":
497
+ return entry
498
+ try:
499
+ with _auth_store_lock():
500
+ auth_store = _load_auth_store()
501
+ state = _load_provider_state(auth_store, "openai-codex")
502
+ if not isinstance(state, dict):
503
+ return entry
504
+ tokens = state.get("tokens")
505
+ if not isinstance(tokens, dict):
506
+ return entry
507
+ store_access = tokens.get("access_token", "")
508
+ store_refresh = tokens.get("refresh_token", "")
509
+ # Adopt auth.json tokens when either side differs. Codex refresh
510
+ # tokens are single-use too, so a fresh refresh_token from
511
+ # another process means our entry's pair is consumed/stale.
512
+ entry_access = entry.access_token or ""
513
+ entry_refresh = entry.refresh_token or ""
514
+ if store_access and (
515
+ store_access != entry_access
516
+ or (store_refresh and store_refresh != entry_refresh)
517
+ ):
518
+ logger.debug(
519
+ "Pool entry %s: syncing Codex tokens from auth.json "
520
+ "(refreshed by another process)",
521
+ entry.id,
522
+ )
523
+ field_updates: Dict[str, Any] = {
524
+ "access_token": store_access,
525
+ "refresh_token": store_refresh or entry.refresh_token,
526
+ "last_status": None,
527
+ "last_status_at": None,
528
+ "last_error_code": None,
529
+ "last_error_reason": None,
530
+ "last_error_message": None,
531
+ "last_error_reset_at": None,
532
+ }
533
+ if state.get("last_refresh"):
534
+ field_updates["last_refresh"] = state["last_refresh"]
535
+ updated = replace(entry, **field_updates)
536
+ self._replace_entry(entry, updated)
537
+ self._persist()
538
+ return updated
539
+ except Exception as exc:
540
+ logger.debug("Failed to sync Codex entry from auth.json: %s", exc)
541
+ return entry
542
+
543
+ def _sync_xai_oauth_entry_from_auth_store(self, entry: PooledCredential) -> PooledCredential:
544
+ """Sync an xAI OAuth pool entry from auth.json if tokens differ.
545
+
546
+ xAI OAuth refresh tokens are single-use. When another Hermes process
547
+ (or another profile sharing the same auth.json) refreshes the token,
548
+ it writes the new pair to ``providers["xai-oauth"]["tokens"]`` under
549
+ ``_auth_store_lock``. Without this resync, our in-memory pool entry
550
+ keeps the consumed refresh_token and the next ``_refresh_entry`` call
551
+ would replay it and get a ``refresh_token_reused``-style 4xx.
552
+
553
+ Only applies to entries seeded from the singleton (``loopback_pkce``);
554
+ manually added entries (``manual:xai_pkce``) are independent
555
+ credentials with their own refresh-token lifecycle.
556
+ """
557
+ if self.provider != "xai-oauth" or entry.source != "loopback_pkce":
558
+ return entry
559
+ try:
560
+ with _auth_store_lock():
561
+ auth_store = _load_auth_store()
562
+ state = _load_provider_state(auth_store, "xai-oauth")
563
+ if not isinstance(state, dict):
564
+ return entry
565
+ tokens = state.get("tokens")
566
+ if not isinstance(tokens, dict):
567
+ return entry
568
+ store_access = tokens.get("access_token", "")
569
+ store_refresh = tokens.get("refresh_token", "")
570
+ entry_access = entry.access_token or ""
571
+ entry_refresh = entry.refresh_token or ""
572
+ if store_access and (
573
+ store_access != entry_access
574
+ or (store_refresh and store_refresh != entry_refresh)
575
+ ):
576
+ logger.debug(
577
+ "Pool entry %s: syncing xAI OAuth tokens from auth.json "
578
+ "(refreshed by another process)",
579
+ entry.id,
580
+ )
581
+ field_updates: Dict[str, Any] = {
582
+ "access_token": store_access,
583
+ "refresh_token": store_refresh or entry.refresh_token,
584
+ "last_status": None,
585
+ "last_status_at": None,
586
+ "last_error_code": None,
587
+ "last_error_reason": None,
588
+ "last_error_message": None,
589
+ "last_error_reset_at": None,
590
+ }
591
+ if state.get("last_refresh"):
592
+ field_updates["last_refresh"] = state["last_refresh"]
593
+ updated = replace(entry, **field_updates)
594
+ self._replace_entry(entry, updated)
595
+ self._persist()
596
+ return updated
597
+ except Exception as exc:
598
+ logger.debug("Failed to sync xAI OAuth entry from auth.json: %s", exc)
599
+ return entry
600
+
601
+ def _sync_nous_entry_from_auth_store(self, entry: PooledCredential) -> PooledCredential:
602
+ """Sync a Nous pool entry from auth.json if tokens differ.
603
+
604
+ Nous OAuth refresh tokens are single-use. When another process
605
+ (e.g. a concurrent cron) refreshes the token via
606
+ ``resolve_nous_runtime_credentials``, it writes fresh tokens to
607
+ auth.json under ``_auth_store_lock``. The pool entry's tokens
608
+ become stale. This method detects that and adopts the newer pair,
609
+ avoiding a "refresh token reuse" revocation on the Nous Portal.
610
+ """
611
+ if self.provider != "nous" or entry.source != "device_code":
612
+ return entry
613
+ try:
614
+ with _auth_store_lock():
615
+ auth_store = _load_auth_store()
616
+ state = _load_provider_state(auth_store, "nous")
617
+ if not state:
618
+ return entry
619
+ store_refresh = state.get("refresh_token", "")
620
+ store_access = state.get("access_token", "")
621
+ if store_refresh and store_refresh != entry.refresh_token:
622
+ logger.debug(
623
+ "Pool entry %s: syncing tokens from auth.json (Nous refresh token changed)",
624
+ entry.id,
625
+ )
626
+ field_updates: Dict[str, Any] = {
627
+ "access_token": store_access,
628
+ "refresh_token": store_refresh,
629
+ "last_status": None,
630
+ "last_status_at": None,
631
+ "last_error_code": None,
632
+ }
633
+ if state.get("expires_at"):
634
+ field_updates["expires_at"] = state["expires_at"]
635
+ if state.get("agent_key"):
636
+ field_updates["agent_key"] = state["agent_key"]
637
+ if state.get("agent_key_expires_at"):
638
+ field_updates["agent_key_expires_at"] = state["agent_key_expires_at"]
639
+ if state.get("inference_base_url"):
640
+ field_updates["inference_base_url"] = state["inference_base_url"]
641
+ extra_updates = dict(entry.extra)
642
+ for extra_key in ("obtained_at", "expires_in", "agent_key_id",
643
+ "agent_key_expires_in", "agent_key_reused",
644
+ "agent_key_obtained_at"):
645
+ val = state.get(extra_key)
646
+ if val is not None:
647
+ extra_updates[extra_key] = val
648
+ updated = replace(entry, extra=extra_updates, **field_updates)
649
+ self._replace_entry(entry, updated)
650
+ self._persist()
651
+ return updated
652
+ except Exception as exc:
653
+ logger.debug("Failed to sync Nous entry from auth.json: %s", exc)
654
+ return entry
655
+
656
+ def _sync_device_code_entry_to_auth_store(self, entry: PooledCredential) -> None:
657
+ """Write refreshed pool entry tokens back to auth.json providers.
658
+
659
+ After a pool-level refresh, the pool entry has fresh tokens but
660
+ auth.json's ``providers.<id>`` still holds the pre-refresh state.
661
+ On the next ``load_pool()``, ``_seed_from_singletons()`` reads that
662
+ stale state and can overwrite the fresh pool entry — potentially
663
+ re-seeding a consumed single-use refresh token.
664
+
665
+ Applies to any OAuth provider whose singleton lives in auth.json
666
+ (currently Nous, OpenAI Codex, and xAI Grok OAuth).
667
+
668
+ ``set_active=False`` on every write: a pool sync-back is a
669
+ token-rotation side effect, not the user choosing a provider.
670
+ Using ``_save_provider_state`` (which sets ``active_provider``)
671
+ here would mean every Nous/Codex/xAI refresh in a multi-provider
672
+ setup silently flips the ``active_provider`` flag — the next
673
+ ``hermes`` invocation that defaults to the active provider
674
+ (e.g. setup wizard, ``hermes auth status``) would land on
675
+ whatever provider happened to refresh last, not whatever the
676
+ user actually chose.
677
+ """
678
+ # Only sync entries that were seeded *from* a singleton. Manually
679
+ # added pool entries (source="manual:*") are independent credentials
680
+ # and must not write back to the singleton.
681
+ if entry.source not in {"device_code", "loopback_pkce"}:
682
+ return
683
+ try:
684
+ with _auth_store_lock():
685
+ auth_store = _load_auth_store()
686
+ if self.provider == "nous":
687
+ state = _load_provider_state(auth_store, "nous")
688
+ if state is None:
689
+ return
690
+ state["access_token"] = entry.access_token
691
+ if entry.refresh_token:
692
+ state["refresh_token"] = entry.refresh_token
693
+ if entry.expires_at:
694
+ state["expires_at"] = entry.expires_at
695
+ if entry.agent_key:
696
+ state["agent_key"] = entry.agent_key
697
+ if entry.agent_key_expires_at:
698
+ state["agent_key_expires_at"] = entry.agent_key_expires_at
699
+ for extra_key in ("obtained_at", "expires_in", "agent_key_id",
700
+ "agent_key_expires_in", "agent_key_reused",
701
+ "agent_key_obtained_at"):
702
+ val = entry.extra.get(extra_key)
703
+ if val is not None:
704
+ state[extra_key] = val
705
+ if entry.inference_base_url:
706
+ state["inference_base_url"] = entry.inference_base_url
707
+ _store_provider_state(auth_store, "nous", state, set_active=False)
708
+
709
+ elif self.provider == "openai-codex":
710
+ state = _load_provider_state(auth_store, "openai-codex")
711
+ if not isinstance(state, dict):
712
+ return
713
+ tokens = state.get("tokens")
714
+ if not isinstance(tokens, dict):
715
+ return
716
+ tokens["access_token"] = entry.access_token
717
+ if entry.refresh_token:
718
+ tokens["refresh_token"] = entry.refresh_token
719
+ if entry.last_refresh:
720
+ state["last_refresh"] = entry.last_refresh
721
+ _store_provider_state(auth_store, "openai-codex", state, set_active=False)
722
+
723
+ elif self.provider == "xai-oauth":
724
+ state = _load_provider_state(auth_store, "xai-oauth")
725
+ if not isinstance(state, dict):
726
+ return
727
+ tokens = state.get("tokens")
728
+ if not isinstance(tokens, dict):
729
+ return
730
+ tokens["access_token"] = entry.access_token
731
+ if entry.refresh_token:
732
+ tokens["refresh_token"] = entry.refresh_token
733
+ if entry.last_refresh:
734
+ state["last_refresh"] = entry.last_refresh
735
+ _store_provider_state(auth_store, "xai-oauth", state, set_active=False)
736
+
737
+ else:
738
+ return
739
+
740
+ _save_auth_store(auth_store)
741
+ except Exception as exc:
742
+ logger.debug("Failed to sync %s pool entry back to auth store: %s", self.provider, exc)
743
+
744
+ def _refresh_entry(self, entry: PooledCredential, *, force: bool) -> Optional[PooledCredential]:
745
+ if entry.auth_type != AUTH_TYPE_OAUTH or not entry.refresh_token:
746
+ if force:
747
+ self._mark_exhausted(entry, None)
748
+ return None
749
+
750
+ try:
751
+ if self.provider == "anthropic":
752
+ from agent.anthropic_adapter import refresh_anthropic_oauth_pure
753
+
754
+ refreshed = refresh_anthropic_oauth_pure(
755
+ entry.refresh_token,
756
+ use_json=entry.source.endswith("hermes_pkce"),
757
+ )
758
+ updated = replace(
759
+ entry,
760
+ access_token=refreshed["access_token"],
761
+ refresh_token=refreshed["refresh_token"],
762
+ expires_at_ms=refreshed["expires_at_ms"],
763
+ )
764
+ # Keep ~/.claude/.credentials.json in sync so that the
765
+ # fallback path (resolve_anthropic_token) and other profiles
766
+ # see the latest tokens.
767
+ if entry.source == "claude_code":
768
+ try:
769
+ from agent.anthropic_adapter import _write_claude_code_credentials
770
+ _write_claude_code_credentials(
771
+ refreshed["access_token"],
772
+ refreshed["refresh_token"],
773
+ refreshed["expires_at_ms"],
774
+ )
775
+ except Exception as wexc:
776
+ logger.debug("Failed to write refreshed token to credentials file: %s", wexc)
777
+ elif self.provider == "openai-codex":
778
+ refreshed = auth_mod.refresh_codex_oauth_pure(
779
+ entry.access_token,
780
+ entry.refresh_token,
781
+ )
782
+ updated = replace(
783
+ entry,
784
+ access_token=refreshed["access_token"],
785
+ refresh_token=refreshed["refresh_token"],
786
+ last_refresh=refreshed.get("last_refresh"),
787
+ )
788
+ elif self.provider == "xai-oauth":
789
+ # Adopt fresher tokens from auth.json before spending the
790
+ # refresh_token — single-use tokens consumed by another
791
+ # process (or another profile sharing the singleton) would
792
+ # otherwise trigger ``refresh_token_reused`` on the next
793
+ # POST. Only meaningful for singleton-seeded entries.
794
+ synced = self._sync_xai_oauth_entry_from_auth_store(entry)
795
+ if synced is not entry:
796
+ entry = synced
797
+ refreshed = auth_mod.refresh_xai_oauth_pure(
798
+ entry.access_token,
799
+ entry.refresh_token,
800
+ )
801
+ updated = replace(
802
+ entry,
803
+ access_token=refreshed["access_token"],
804
+ refresh_token=refreshed["refresh_token"],
805
+ last_refresh=refreshed.get("last_refresh"),
806
+ )
807
+ elif self.provider == "nous":
808
+ synced = self._sync_nous_entry_from_auth_store(entry)
809
+ if synced is not entry:
810
+ entry = synced
811
+ nous_state = {
812
+ "access_token": entry.access_token,
813
+ "refresh_token": entry.refresh_token,
814
+ "client_id": entry.client_id,
815
+ "portal_base_url": entry.portal_base_url,
816
+ "inference_base_url": entry.inference_base_url,
817
+ "token_type": entry.token_type,
818
+ "scope": entry.scope,
819
+ "obtained_at": entry.obtained_at,
820
+ "expires_at": entry.expires_at,
821
+ "agent_key": entry.agent_key,
822
+ "agent_key_expires_at": entry.agent_key_expires_at,
823
+ "tls": entry.tls,
824
+ }
825
+ refreshed = auth_mod.refresh_nous_oauth_from_state(
826
+ nous_state,
827
+ min_key_ttl_seconds=DEFAULT_AGENT_KEY_MIN_TTL_SECONDS,
828
+ force_refresh=force,
829
+ force_mint=force,
830
+ )
831
+ # Apply returned fields: dataclass fields via replace, extras via dict update
832
+ field_updates = {}
833
+ extra_updates = dict(entry.extra)
834
+ _field_names = {f.name for f in fields(entry)}
835
+ for k, v in refreshed.items():
836
+ if k in _field_names:
837
+ field_updates[k] = v
838
+ elif k in _EXTRA_KEYS:
839
+ extra_updates[k] = v
840
+ updated = replace(entry, extra=extra_updates, **field_updates)
841
+ else:
842
+ return entry
843
+ except Exception as exc:
844
+ logger.debug("Credential refresh failed for %s/%s: %s", self.provider, entry.id, exc)
845
+ # For anthropic claude_code entries: the refresh token may have been
846
+ # consumed by another process. Check if ~/.claude/.credentials.json
847
+ # has a newer token pair and retry once.
848
+ if self.provider == "anthropic" and entry.source == "claude_code":
849
+ synced = self._sync_anthropic_entry_from_credentials_file(entry)
850
+ if synced.refresh_token != entry.refresh_token:
851
+ logger.debug("Retrying refresh with synced token from credentials file")
852
+ try:
853
+ from agent.anthropic_adapter import refresh_anthropic_oauth_pure
854
+ refreshed = refresh_anthropic_oauth_pure(
855
+ synced.refresh_token,
856
+ use_json=synced.source.endswith("hermes_pkce"),
857
+ )
858
+ updated = replace(
859
+ synced,
860
+ access_token=refreshed["access_token"],
861
+ refresh_token=refreshed["refresh_token"],
862
+ expires_at_ms=refreshed["expires_at_ms"],
863
+ last_status=STATUS_OK,
864
+ last_status_at=None,
865
+ last_error_code=None,
866
+ )
867
+ self._replace_entry(synced, updated)
868
+ self._persist()
869
+ try:
870
+ from agent.anthropic_adapter import _write_claude_code_credentials
871
+ _write_claude_code_credentials(
872
+ refreshed["access_token"],
873
+ refreshed["refresh_token"],
874
+ refreshed["expires_at_ms"],
875
+ )
876
+ except Exception as wexc:
877
+ logger.debug("Failed to write refreshed token to credentials file (retry path): %s", wexc)
878
+ return updated
879
+ except Exception as retry_exc:
880
+ logger.debug("Retry refresh also failed: %s", retry_exc)
881
+ elif not self._entry_needs_refresh(synced):
882
+ # Credentials file had a valid (non-expired) token — use it directly
883
+ logger.debug("Credentials file has valid token, using without refresh")
884
+ return synced
885
+ # For xai-oauth: same race as nous — another process may have
886
+ # consumed the refresh token between our proactive sync and the
887
+ # HTTP call. Re-check auth.json and adopt the fresh tokens if
888
+ # they have rotated since. Only meaningful for singleton-seeded
889
+ # (loopback_pkce) entries; manual entries don't share state with
890
+ # the singleton.
891
+ if self.provider == "xai-oauth":
892
+ synced = self._sync_xai_oauth_entry_from_auth_store(entry)
893
+ if synced.refresh_token != entry.refresh_token:
894
+ logger.debug(
895
+ "xAI OAuth refresh failed but auth.json has newer tokens — adopting"
896
+ )
897
+ updated = replace(
898
+ synced,
899
+ last_status=STATUS_OK,
900
+ last_status_at=None,
901
+ last_error_code=None,
902
+ last_error_reason=None,
903
+ last_error_message=None,
904
+ last_error_reset_at=None,
905
+ )
906
+ self._replace_entry(synced, updated)
907
+ self._persist()
908
+ return updated
909
+ # For nous: another process may have consumed the refresh token
910
+ # between our proactive sync and the HTTP call. Re-sync from
911
+ # auth.json and adopt the fresh tokens if available.
912
+ if self.provider == "nous":
913
+ synced = self._sync_nous_entry_from_auth_store(entry)
914
+ if synced.refresh_token != entry.refresh_token:
915
+ logger.debug("Nous refresh failed but auth.json has newer tokens — adopting")
916
+ updated = replace(
917
+ synced,
918
+ last_status=STATUS_OK,
919
+ last_status_at=None,
920
+ last_error_code=None,
921
+ last_error_reason=None,
922
+ last_error_message=None,
923
+ last_error_reset_at=None,
924
+ )
925
+ self._replace_entry(synced, updated)
926
+ self._persist()
927
+ self._sync_device_code_entry_to_auth_store(updated)
928
+ return updated
929
+ self._mark_exhausted(entry, None)
930
+ return None
931
+
932
+ updated = replace(
933
+ updated,
934
+ last_status=STATUS_OK,
935
+ last_status_at=None,
936
+ last_error_code=None,
937
+ last_error_reason=None,
938
+ last_error_message=None,
939
+ last_error_reset_at=None,
940
+ )
941
+ self._replace_entry(entry, updated)
942
+ self._persist()
943
+ # Sync refreshed tokens back to auth.json providers so that
944
+ # _seed_from_singletons() on the next load_pool() sees fresh state
945
+ # instead of re-seeding stale/consumed tokens.
946
+ self._sync_device_code_entry_to_auth_store(updated)
947
+ return updated
948
+
949
+ def _entry_needs_refresh(self, entry: PooledCredential) -> bool:
950
+ if entry.auth_type != AUTH_TYPE_OAUTH:
951
+ return False
952
+ if self.provider == "anthropic":
953
+ if entry.expires_at_ms is None:
954
+ return False
955
+ return int(entry.expires_at_ms) <= int(time.time() * 1000) + 120_000
956
+ if self.provider == "openai-codex":
957
+ return _codex_access_token_is_expiring(
958
+ entry.access_token,
959
+ CODEX_ACCESS_TOKEN_REFRESH_SKEW_SECONDS,
960
+ )
961
+ if self.provider == "xai-oauth":
962
+ return auth_mod._xai_access_token_is_expiring(
963
+ entry.access_token,
964
+ auth_mod.XAI_ACCESS_TOKEN_REFRESH_SKEW_SECONDS,
965
+ )
966
+ if self.provider == "nous":
967
+ # Nous refresh/mint can require network access and should happen when
968
+ # runtime credentials are actually resolved, not merely when the pool
969
+ # is enumerated for listing, migration, or selection.
970
+ return False
971
+ return False
972
+
973
+ def select(self) -> Optional[PooledCredential]:
974
+ with self._lock:
975
+ return self._select_unlocked()
976
+
977
+ def _available_entries(self, *, clear_expired: bool = False, refresh: bool = False) -> List[PooledCredential]:
978
+ """Return entries not currently in exhaustion cooldown.
979
+
980
+ When *clear_expired* is True, entries whose cooldown has elapsed are
981
+ reset to STATUS_OK and persisted. When *refresh* is True, entries
982
+ that need a token refresh are refreshed (skipped on failure).
983
+ """
984
+ now = time.time()
985
+ cleared_any = False
986
+ available: List[PooledCredential] = []
987
+ for entry in self._entries:
988
+ # For anthropic claude_code entries, sync from the credentials file
989
+ # before any status/refresh checks. This picks up tokens refreshed
990
+ # by other processes (Claude Code CLI, other Hermes profiles).
991
+ if (self.provider == "anthropic" and entry.source == "claude_code"
992
+ and entry.last_status == STATUS_EXHAUSTED):
993
+ synced = self._sync_anthropic_entry_from_credentials_file(entry)
994
+ if synced is not entry:
995
+ entry = synced
996
+ cleared_any = True
997
+ # For nous entries, sync from auth.json before status checks.
998
+ # Another process may have successfully refreshed via
999
+ # resolve_nous_runtime_credentials(), making this entry's
1000
+ # exhausted status stale.
1001
+ if (self.provider == "nous"
1002
+ and entry.source == "device_code"
1003
+ and entry.last_status == STATUS_EXHAUSTED):
1004
+ synced = self._sync_nous_entry_from_auth_store(entry)
1005
+ if synced is not entry:
1006
+ entry = synced
1007
+ cleared_any = True
1008
+ # For openai-codex entries, same pattern: the user may have
1009
+ # re-authed via `hermes model` / `hermes auth` after a 429/401,
1010
+ # leaving fresh tokens on disk while the pool entry is still
1011
+ # frozen behind last_error_reset_at (can be hours in the
1012
+ # future for ChatGPT weekly windows).
1013
+ if (self.provider == "openai-codex"
1014
+ and entry.source == "device_code"
1015
+ and entry.last_status == STATUS_EXHAUSTED):
1016
+ synced = self._sync_codex_entry_from_auth_store(entry)
1017
+ if synced is not entry:
1018
+ entry = synced
1019
+ cleared_any = True
1020
+ # For xai-oauth singleton-seeded entries, identical pattern:
1021
+ # an entry frozen as exhausted may simply be holding stale
1022
+ # tokens that another process (or a fresh `hermes model` ->
1023
+ # xAI Grok OAuth login) has since rotated in auth.json.
1024
+ if (self.provider == "xai-oauth"
1025
+ and entry.source == "loopback_pkce"
1026
+ and entry.last_status == STATUS_EXHAUSTED):
1027
+ synced = self._sync_xai_oauth_entry_from_auth_store(entry)
1028
+ if synced is not entry:
1029
+ entry = synced
1030
+ cleared_any = True
1031
+ if entry.last_status == STATUS_EXHAUSTED:
1032
+ exhausted_until = _exhausted_until(entry)
1033
+ if exhausted_until is not None and now < exhausted_until:
1034
+ continue
1035
+ if clear_expired:
1036
+ cleared = replace(
1037
+ entry,
1038
+ last_status=STATUS_OK,
1039
+ last_status_at=None,
1040
+ last_error_code=None,
1041
+ last_error_reason=None,
1042
+ last_error_message=None,
1043
+ last_error_reset_at=None,
1044
+ )
1045
+ self._replace_entry(entry, cleared)
1046
+ entry = cleared
1047
+ cleared_any = True
1048
+ if refresh and self._entry_needs_refresh(entry):
1049
+ refreshed = self._refresh_entry(entry, force=False)
1050
+ if refreshed is None:
1051
+ continue
1052
+ entry = refreshed
1053
+ available.append(entry)
1054
+ if cleared_any:
1055
+ self._persist()
1056
+ return available
1057
+
1058
+ def _select_unlocked(self) -> Optional[PooledCredential]:
1059
+ available = self._available_entries(clear_expired=True, refresh=True)
1060
+ if not available:
1061
+ self._current_id = None
1062
+ logger.info("credential pool: no available entries (all exhausted or empty)")
1063
+ return None
1064
+
1065
+ if self._strategy == STRATEGY_RANDOM:
1066
+ entry = random.choice(available)
1067
+ self._current_id = entry.id
1068
+ return entry
1069
+
1070
+ if self._strategy == STRATEGY_LEAST_USED and len(available) > 1:
1071
+ entry = min(available, key=lambda e: e.request_count)
1072
+ # Increment usage counter so subsequent selections distribute load
1073
+ updated = replace(entry, request_count=entry.request_count + 1)
1074
+ self._replace_entry(entry, updated)
1075
+ self._current_id = entry.id
1076
+ return updated
1077
+
1078
+ if self._strategy == STRATEGY_ROUND_ROBIN and len(available) > 1:
1079
+ entry = available[0]
1080
+ rotated = [candidate for candidate in self._entries if candidate.id != entry.id]
1081
+ rotated.append(replace(entry, priority=len(self._entries) - 1))
1082
+ self._entries = [replace(candidate, priority=idx) for idx, candidate in enumerate(rotated)]
1083
+ self._persist()
1084
+ self._current_id = entry.id
1085
+ return self.current() or entry
1086
+
1087
+ entry = available[0]
1088
+ self._current_id = entry.id
1089
+ return entry
1090
+
1091
+ def peek(self) -> Optional[PooledCredential]:
1092
+ current = self.current()
1093
+ if current is not None:
1094
+ return current
1095
+ available = self._available_entries()
1096
+ return available[0] if available else None
1097
+
1098
+ def mark_exhausted_and_rotate(
1099
+ self,
1100
+ *,
1101
+ status_code: Optional[int],
1102
+ error_context: Optional[Dict[str, Any]] = None,
1103
+ ) -> Optional[PooledCredential]:
1104
+ with self._lock:
1105
+ entry = self.current() or self._select_unlocked()
1106
+ if entry is None:
1107
+ return None
1108
+ _label = entry.label or entry.id[:8]
1109
+ logger.info(
1110
+ "credential pool: marking %s exhausted (status=%s), rotating",
1111
+ _label, status_code,
1112
+ )
1113
+ self._mark_exhausted(entry, status_code, error_context)
1114
+ self._current_id = None
1115
+ next_entry = self._select_unlocked()
1116
+ if next_entry:
1117
+ _next_label = next_entry.label or next_entry.id[:8]
1118
+ logger.info("credential pool: rotated to %s", _next_label)
1119
+ return next_entry
1120
+
1121
+ def acquire_lease(self, credential_id: Optional[str] = None) -> Optional[str]:
1122
+ """Acquire a soft lease on a credential.
1123
+
1124
+ If a specific credential_id is provided, lease that entry directly.
1125
+ Otherwise prefer the least-leased available credential, using priority as
1126
+ a stable tie-breaker. When every credential is already at the soft cap,
1127
+ still return the least-leased one instead of blocking.
1128
+ """
1129
+ with self._lock:
1130
+ if credential_id:
1131
+ self._active_leases[credential_id] = self._active_leases.get(credential_id, 0) + 1
1132
+ self._current_id = credential_id
1133
+ return credential_id
1134
+
1135
+ available = self._available_entries(clear_expired=True, refresh=True)
1136
+ if not available:
1137
+ return None
1138
+
1139
+ below_cap = [
1140
+ entry for entry in available
1141
+ if self._active_leases.get(entry.id, 0) < self._max_concurrent
1142
+ ]
1143
+ candidates = below_cap if below_cap else available
1144
+ chosen = min(
1145
+ candidates,
1146
+ key=lambda entry: (self._active_leases.get(entry.id, 0), entry.priority),
1147
+ )
1148
+ self._active_leases[chosen.id] = self._active_leases.get(chosen.id, 0) + 1
1149
+ self._current_id = chosen.id
1150
+ return chosen.id
1151
+
1152
+ def release_lease(self, credential_id: str) -> None:
1153
+ """Release a previously acquired credential lease."""
1154
+ with self._lock:
1155
+ count = self._active_leases.get(credential_id, 0)
1156
+ if count <= 1:
1157
+ self._active_leases.pop(credential_id, None)
1158
+ else:
1159
+ self._active_leases[credential_id] = count - 1
1160
+
1161
+ def try_refresh_current(self) -> Optional[PooledCredential]:
1162
+ with self._lock:
1163
+ return self._try_refresh_current_unlocked()
1164
+
1165
+ def _try_refresh_current_unlocked(self) -> Optional[PooledCredential]:
1166
+ entry = self.current()
1167
+ if entry is None:
1168
+ return None
1169
+ refreshed = self._refresh_entry(entry, force=True)
1170
+ if refreshed is not None:
1171
+ self._current_id = refreshed.id
1172
+ return refreshed
1173
+
1174
+ def reset_statuses(self) -> int:
1175
+ count = 0
1176
+ new_entries = []
1177
+ for entry in self._entries:
1178
+ if entry.last_status or entry.last_status_at or entry.last_error_code:
1179
+ new_entries.append(
1180
+ replace(
1181
+ entry,
1182
+ last_status=None,
1183
+ last_status_at=None,
1184
+ last_error_code=None,
1185
+ last_error_reason=None,
1186
+ last_error_message=None,
1187
+ last_error_reset_at=None,
1188
+ )
1189
+ )
1190
+ count += 1
1191
+ else:
1192
+ new_entries.append(entry)
1193
+ if count:
1194
+ self._entries = new_entries
1195
+ self._persist()
1196
+ return count
1197
+
1198
+ def remove_index(self, index: int) -> Optional[PooledCredential]:
1199
+ if index < 1 or index > len(self._entries):
1200
+ return None
1201
+ removed = self._entries.pop(index - 1)
1202
+ self._entries = [
1203
+ replace(entry, priority=new_priority)
1204
+ for new_priority, entry in enumerate(self._entries)
1205
+ ]
1206
+ self._persist()
1207
+ if self._current_id == removed.id:
1208
+ self._current_id = None
1209
+ return removed
1210
+
1211
+ def resolve_target(self, target: Any) -> Tuple[Optional[int], Optional[PooledCredential], Optional[str]]:
1212
+ raw = str(target or "").strip()
1213
+ if not raw:
1214
+ return None, None, "No credential target provided."
1215
+
1216
+ for idx, entry in enumerate(self._entries, start=1):
1217
+ if entry.id == raw:
1218
+ return idx, entry, None
1219
+
1220
+ label_matches = [
1221
+ (idx, entry)
1222
+ for idx, entry in enumerate(self._entries, start=1)
1223
+ if entry.label.strip().lower() == raw.lower()
1224
+ ]
1225
+ if len(label_matches) == 1:
1226
+ return label_matches[0][0], label_matches[0][1], None
1227
+ if len(label_matches) > 1:
1228
+ return None, None, f'Ambiguous credential label "{raw}". Use the numeric index or entry id instead.'
1229
+ if raw.isdigit():
1230
+ index = int(raw)
1231
+ if 1 <= index <= len(self._entries):
1232
+ return index, self._entries[index - 1], None
1233
+ return None, None, f"No credential #{index}."
1234
+ return None, None, f'No credential matching "{raw}".'
1235
+
1236
+ def add_entry(self, entry: PooledCredential) -> PooledCredential:
1237
+ entry = replace(entry, priority=_next_priority(self._entries))
1238
+ self._entries.append(entry)
1239
+ self._persist()
1240
+ return entry
1241
+
1242
+
1243
+ def _upsert_entry(entries: List[PooledCredential], provider: str, source: str, payload: Dict[str, Any]) -> bool:
1244
+ existing_idx = None
1245
+ for idx, entry in enumerate(entries):
1246
+ if entry.source == source:
1247
+ existing_idx = idx
1248
+ break
1249
+
1250
+ if existing_idx is None:
1251
+ payload.setdefault("id", uuid.uuid4().hex[:6])
1252
+ payload.setdefault("priority", _next_priority(entries))
1253
+ payload.setdefault("label", payload.get("label") or source)
1254
+ entries.append(PooledCredential.from_dict(provider, payload))
1255
+ return True
1256
+
1257
+ existing = entries[existing_idx]
1258
+ field_updates = {}
1259
+ extra_updates = {}
1260
+ _field_names = {f.name for f in fields(existing)}
1261
+ for key, value in payload.items():
1262
+ if key in {"id", "priority"} or value is None:
1263
+ continue
1264
+ if key == "label" and existing.label:
1265
+ continue
1266
+ if key in _field_names:
1267
+ if getattr(existing, key) != value:
1268
+ field_updates[key] = value
1269
+ elif key in _EXTRA_KEYS:
1270
+ if existing.extra.get(key) != value:
1271
+ extra_updates[key] = value
1272
+ if field_updates or extra_updates:
1273
+ if extra_updates:
1274
+ field_updates["extra"] = {**existing.extra, **extra_updates}
1275
+ entries[existing_idx] = replace(existing, **field_updates)
1276
+ return True
1277
+ return False
1278
+
1279
+
1280
+ def _normalize_pool_priorities(provider: str, entries: List[PooledCredential]) -> bool:
1281
+ if provider != "anthropic":
1282
+ return False
1283
+
1284
+ source_rank = {
1285
+ "env:ANTHROPIC_TOKEN": 0,
1286
+ "env:CLAUDE_CODE_OAUTH_TOKEN": 1,
1287
+ "hermes_pkce": 2,
1288
+ "claude_code": 3,
1289
+ "env:ANTHROPIC_API_KEY": 4,
1290
+ }
1291
+ manual_entries = sorted(
1292
+ (entry for entry in entries if _is_manual_source(entry.source)),
1293
+ key=lambda entry: entry.priority,
1294
+ )
1295
+ seeded_entries = sorted(
1296
+ (entry for entry in entries if not _is_manual_source(entry.source)),
1297
+ key=lambda entry: (
1298
+ source_rank.get(entry.source, len(source_rank)),
1299
+ entry.priority,
1300
+ entry.label,
1301
+ ),
1302
+ )
1303
+
1304
+ ordered = [*manual_entries, *seeded_entries]
1305
+ id_to_idx = {entry.id: idx for idx, entry in enumerate(entries)}
1306
+ changed = False
1307
+ for new_priority, entry in enumerate(ordered):
1308
+ if entry.priority != new_priority:
1309
+ entries[id_to_idx[entry.id]] = replace(entry, priority=new_priority)
1310
+ changed = True
1311
+ return changed
1312
+
1313
+
1314
+ def _seed_from_singletons(provider: str, entries: List[PooledCredential]) -> Tuple[bool, Set[str]]:
1315
+ changed = False
1316
+ active_sources: Set[str] = set()
1317
+ auth_store = _load_auth_store()
1318
+
1319
+ # Shared suppression gate — used at every upsert site so
1320
+ # `hermes auth remove <provider> <N>` is stable across all source types.
1321
+ try:
1322
+ from hermes_cli.auth import is_source_suppressed as _is_suppressed
1323
+ except ImportError:
1324
+ def _is_suppressed(_p, _s): # type: ignore[misc]
1325
+ return False
1326
+
1327
+ if provider == "anthropic":
1328
+ # Only auto-discover external credentials (Claude Code, Hermes PKCE)
1329
+ # when the user has explicitly configured anthropic as their provider.
1330
+ # Without this gate, auxiliary client fallback chains silently read
1331
+ # ~/.claude/.credentials.json without user consent. See PR #4210.
1332
+ try:
1333
+ from hermes_cli.auth import is_provider_explicitly_configured
1334
+ if not is_provider_explicitly_configured("anthropic"):
1335
+ return changed, active_sources
1336
+ except ImportError:
1337
+ pass
1338
+
1339
+ from agent.anthropic_adapter import read_claude_code_credentials, read_hermes_oauth_credentials
1340
+
1341
+ for source_name, creds in (
1342
+ ("hermes_pkce", read_hermes_oauth_credentials()),
1343
+ ("claude_code", read_claude_code_credentials()),
1344
+ ):
1345
+ if creds and creds.get("accessToken"):
1346
+ if _is_suppressed(provider, source_name):
1347
+ continue
1348
+ active_sources.add(source_name)
1349
+ changed |= _upsert_entry(
1350
+ entries,
1351
+ provider,
1352
+ source_name,
1353
+ {
1354
+ "source": source_name,
1355
+ "auth_type": AUTH_TYPE_OAUTH,
1356
+ "access_token": creds.get("accessToken", ""),
1357
+ "refresh_token": creds.get("refreshToken"),
1358
+ "expires_at_ms": creds.get("expiresAt"),
1359
+ "label": label_from_token(creds.get("accessToken", ""), source_name),
1360
+ },
1361
+ )
1362
+
1363
+ elif provider == "nous":
1364
+ state = _load_provider_state(auth_store, "nous")
1365
+ if state and not _is_suppressed(provider, "device_code"):
1366
+ active_sources.add("device_code")
1367
+ # Prefer a user-supplied label embedded in the singleton state
1368
+ # (set by persist_nous_credentials(label=...) when the user ran
1369
+ # `hermes auth add nous --label <name>`). Fall back to the
1370
+ # auto-derived token fingerprint for logins that didn't supply one.
1371
+ custom_label = str(state.get("label") or "").strip()
1372
+ seeded_label = custom_label or label_from_token(
1373
+ state.get("access_token", ""), "device_code"
1374
+ )
1375
+ changed |= _upsert_entry(
1376
+ entries,
1377
+ provider,
1378
+ "device_code",
1379
+ {
1380
+ "source": "device_code",
1381
+ "auth_type": AUTH_TYPE_OAUTH,
1382
+ "access_token": state.get("access_token", ""),
1383
+ "refresh_token": state.get("refresh_token"),
1384
+ "expires_at": state.get("expires_at"),
1385
+ "token_type": state.get("token_type"),
1386
+ "scope": state.get("scope"),
1387
+ "client_id": state.get("client_id"),
1388
+ "portal_base_url": state.get("portal_base_url"),
1389
+ "inference_base_url": state.get("inference_base_url"),
1390
+ "agent_key": state.get("agent_key"),
1391
+ "agent_key_expires_at": state.get("agent_key_expires_at"),
1392
+ # Carry the mint/refresh timestamps into the pool so
1393
+ # freshness-sensitive consumers (self-heal hooks, pool
1394
+ # pruning by age) can distinguish just-minted credentials
1395
+ # from stale ones. Without these, fresh device_code
1396
+ # entries get obtained_at=None and look older than they
1397
+ # are (#15099).
1398
+ "obtained_at": state.get("obtained_at"),
1399
+ "expires_in": state.get("expires_in"),
1400
+ "agent_key_id": state.get("agent_key_id"),
1401
+ "agent_key_expires_in": state.get("agent_key_expires_in"),
1402
+ "agent_key_reused": state.get("agent_key_reused"),
1403
+ "agent_key_obtained_at": state.get("agent_key_obtained_at"),
1404
+ "tls": state.get("tls") if isinstance(state.get("tls"), dict) else None,
1405
+ "label": seeded_label,
1406
+ },
1407
+ )
1408
+
1409
+ elif provider == "copilot":
1410
+ # Copilot tokens are resolved dynamically via `gh auth token` or
1411
+ # env vars (COPILOT_GITHUB_TOKEN / GH_TOKEN). They don't live in
1412
+ # the auth store or credential pool, so we resolve them here.
1413
+ try:
1414
+ from hermes_cli.copilot_auth import resolve_copilot_token, get_copilot_api_token
1415
+ token, source = resolve_copilot_token()
1416
+ if token:
1417
+ api_token = get_copilot_api_token(token)
1418
+ source_name = "gh_cli" if "gh" in source.lower() else f"env:{source}"
1419
+ if not _is_suppressed(provider, source_name):
1420
+ active_sources.add(source_name)
1421
+ pconfig = PROVIDER_REGISTRY.get(provider)
1422
+ changed |= _upsert_entry(
1423
+ entries,
1424
+ provider,
1425
+ source_name,
1426
+ {
1427
+ "source": source_name,
1428
+ "auth_type": AUTH_TYPE_API_KEY,
1429
+ "access_token": api_token,
1430
+ "base_url": pconfig.inference_base_url if pconfig else "",
1431
+ "label": source,
1432
+ },
1433
+ )
1434
+ except Exception as exc:
1435
+ logger.debug("Copilot token seed failed: %s", exc)
1436
+
1437
+ elif provider == "qwen-oauth":
1438
+ # Qwen OAuth tokens live in ~/.qwen/oauth_creds.json, written by
1439
+ # the Qwen CLI (`qwen auth qwen-oauth`). They aren't in the
1440
+ # Hermes auth store or env vars, so resolve them here.
1441
+ # Use refresh_if_expiring=False to avoid network calls during
1442
+ # pool loading / provider discovery.
1443
+ try:
1444
+ from hermes_cli.auth import resolve_qwen_runtime_credentials
1445
+ creds = resolve_qwen_runtime_credentials(refresh_if_expiring=False)
1446
+ token = creds.get("api_key", "")
1447
+ if token:
1448
+ source_name = creds.get("source", "qwen-cli")
1449
+ if not _is_suppressed(provider, source_name):
1450
+ active_sources.add(source_name)
1451
+ changed |= _upsert_entry(
1452
+ entries,
1453
+ provider,
1454
+ source_name,
1455
+ {
1456
+ "source": source_name,
1457
+ "auth_type": AUTH_TYPE_OAUTH,
1458
+ "access_token": token,
1459
+ "expires_at_ms": creds.get("expires_at_ms"),
1460
+ "base_url": creds.get("base_url", ""),
1461
+ "label": creds.get("auth_file", source_name),
1462
+ },
1463
+ )
1464
+ except Exception as exc:
1465
+ logger.debug("Qwen OAuth token seed failed: %s", exc)
1466
+
1467
+ elif provider == "minimax-oauth":
1468
+ # MiniMax OAuth tokens live in ~/.hermes/auth.json providers.minimax-oauth.
1469
+ # Seed the pool so `/auth list` reflects the logged-in state and the
1470
+ # standard `hermes auth remove minimax-oauth <N>` flow works.
1471
+ # Use refresh_if_expiring=False equivalent: resolve_minimax_oauth_runtime_credentials
1472
+ # always refreshes on expiry, so instead read raw state here to avoid
1473
+ # surprise network calls during provider discovery.
1474
+ try:
1475
+ from hermes_cli.auth import get_provider_auth_state
1476
+ state = get_provider_auth_state("minimax-oauth")
1477
+ if state and state.get("access_token"):
1478
+ source_name = "oauth"
1479
+ if not _is_suppressed(provider, source_name):
1480
+ active_sources.add(source_name)
1481
+ expires_at_ms = None
1482
+ try:
1483
+ from datetime import datetime as _dt
1484
+ raw = state.get("expires_at", "")
1485
+ if raw:
1486
+ expires_at_ms = int(_dt.fromisoformat(raw).timestamp() * 1000)
1487
+ except Exception:
1488
+ expires_at_ms = None
1489
+ base_url = str(state.get("inference_base_url", "") or "").rstrip("/")
1490
+ changed |= _upsert_entry(
1491
+ entries,
1492
+ provider,
1493
+ source_name,
1494
+ {
1495
+ "source": source_name,
1496
+ "auth_type": AUTH_TYPE_OAUTH,
1497
+ "access_token": state["access_token"],
1498
+ "refresh_token": state.get("refresh_token"),
1499
+ "expires_at_ms": expires_at_ms,
1500
+ "base_url": base_url,
1501
+ "label": state.get("label", "") or label_from_token(
1502
+ state.get("access_token", ""), source_name
1503
+ ),
1504
+ },
1505
+ )
1506
+ except Exception as exc:
1507
+ logger.debug("MiniMax OAuth token seed failed: %s", exc)
1508
+
1509
+ elif provider == "openai-codex":
1510
+ # Respect user suppression — `hermes auth remove openai-codex` marks
1511
+ # the device_code source as suppressed so it won't be re-seeded from
1512
+ # the Hermes auth store. Without this gate the removal is instantly
1513
+ # undone on the next load_pool() call.
1514
+ if _is_suppressed(provider, "device_code"):
1515
+ return changed, active_sources
1516
+
1517
+ state = _load_provider_state(auth_store, "openai-codex")
1518
+ tokens = state.get("tokens") if isinstance(state, dict) else None
1519
+ # Hermes owns its own Codex auth state — we do NOT auto-import from
1520
+ # ~/.codex/auth.json at pool-load time. OAuth refresh tokens are
1521
+ # single-use, so sharing them with Codex CLI / VS Code causes
1522
+ # refresh_token_reused race failures. Users who want to adopt
1523
+ # existing Codex CLI credentials get a one-time, explicit prompt
1524
+ # via `hermes auth openai-codex`.
1525
+ if isinstance(tokens, dict) and tokens.get("access_token"):
1526
+ active_sources.add("device_code")
1527
+ changed |= _upsert_entry(
1528
+ entries,
1529
+ provider,
1530
+ "device_code",
1531
+ {
1532
+ "source": "device_code",
1533
+ "auth_type": AUTH_TYPE_OAUTH,
1534
+ "access_token": tokens.get("access_token", ""),
1535
+ "refresh_token": tokens.get("refresh_token"),
1536
+ "base_url": "https://chatgpt.com/backend-api/codex",
1537
+ "last_refresh": state.get("last_refresh"),
1538
+ "label": label_from_token(tokens.get("access_token", ""), "device_code"),
1539
+ },
1540
+ )
1541
+
1542
+ elif provider == "xai-oauth":
1543
+ # When the user logs in via ``hermes model`` -> xAI Grok OAuth,
1544
+ # tokens are written to the auth.json singleton
1545
+ # (``providers["xai-oauth"]``). Surface them in the pool too so
1546
+ # ``hermes auth list`` reflects the logged-in state and so the pool
1547
+ # is the single source of truth for refresh during runtime resolution.
1548
+ if _is_suppressed(provider, "loopback_pkce"):
1549
+ return changed, active_sources
1550
+
1551
+ state = _load_provider_state(auth_store, "xai-oauth")
1552
+ tokens = state.get("tokens") if isinstance(state, dict) else None
1553
+ if isinstance(tokens, dict) and tokens.get("access_token"):
1554
+ active_sources.add("loopback_pkce")
1555
+ from hermes_cli.auth import DEFAULT_XAI_OAUTH_BASE_URL
1556
+
1557
+ base_url = DEFAULT_XAI_OAUTH_BASE_URL
1558
+ changed |= _upsert_entry(
1559
+ entries,
1560
+ provider,
1561
+ "loopback_pkce",
1562
+ {
1563
+ "source": "loopback_pkce",
1564
+ "auth_type": AUTH_TYPE_OAUTH,
1565
+ "access_token": tokens.get("access_token", ""),
1566
+ "refresh_token": tokens.get("refresh_token"),
1567
+ "base_url": base_url,
1568
+ "last_refresh": state.get("last_refresh"),
1569
+ "label": label_from_token(tokens.get("access_token", ""), "loopback_pkce"),
1570
+ },
1571
+ )
1572
+
1573
+ return changed, active_sources
1574
+
1575
+
1576
+ def _seed_from_env(provider: str, entries: List[PooledCredential]) -> Tuple[bool, Set[str]]:
1577
+ changed = False
1578
+ active_sources: Set[str] = set()
1579
+
1580
+ # Prefer ~/.hermes/.env over os.environ — the user's config file is the
1581
+ # authoritative source for Hermes credentials. Stale env vars from parent
1582
+ # processes (Codex CLI, test scripts, etc.) should not override deliberate
1583
+ # changes to the .env file.
1584
+ def _get_env_prefer_dotenv(key: str) -> str:
1585
+ env_file = load_env()
1586
+ val = env_file.get(key) or os.environ.get(key) or ""
1587
+ return val.strip()
1588
+
1589
+ # Honour user suppression — `hermes auth remove <provider> <N>` for an
1590
+ # env-seeded credential marks the env:<VAR> source as suppressed so it
1591
+ # won't be re-seeded from the user's shell environment or ~/.hermes/.env.
1592
+ # Without this gate the removal is silently undone on the next
1593
+ # load_pool() call whenever the var is still exported by the shell.
1594
+ try:
1595
+ from hermes_cli.auth import is_source_suppressed as _is_source_suppressed
1596
+ except ImportError:
1597
+ def _is_source_suppressed(_p, _s): # type: ignore[misc]
1598
+ return False
1599
+ if provider == "openrouter":
1600
+ # Prefer ~/.hermes/.env over os.environ
1601
+ token = _get_env_prefer_dotenv("OPENROUTER_API_KEY")
1602
+ if token:
1603
+ source = "env:OPENROUTER_API_KEY"
1604
+ if _is_source_suppressed(provider, source):
1605
+ return changed, active_sources
1606
+ active_sources.add(source)
1607
+ changed |= _upsert_entry(
1608
+ entries,
1609
+ provider,
1610
+ source,
1611
+ {
1612
+ "source": source,
1613
+ "auth_type": AUTH_TYPE_API_KEY,
1614
+ "access_token": token,
1615
+ "base_url": OPENROUTER_BASE_URL,
1616
+ "label": "OPENROUTER_API_KEY",
1617
+ },
1618
+ )
1619
+ return changed, active_sources
1620
+
1621
+ pconfig = PROVIDER_REGISTRY.get(provider)
1622
+ if not pconfig or pconfig.auth_type != AUTH_TYPE_API_KEY:
1623
+ return changed, active_sources
1624
+
1625
+ env_url = ""
1626
+ if pconfig.base_url_env_var:
1627
+ env_url = _get_env_prefer_dotenv(pconfig.base_url_env_var).rstrip("/")
1628
+
1629
+ env_vars = list(pconfig.api_key_env_vars)
1630
+ if provider == "anthropic":
1631
+ env_vars = [
1632
+ "ANTHROPIC_TOKEN",
1633
+ "CLAUDE_CODE_OAUTH_TOKEN",
1634
+ "ANTHROPIC_API_KEY",
1635
+ ]
1636
+
1637
+ for env_var in env_vars:
1638
+ # Prefer ~/.hermes/.env over os.environ
1639
+ token = _get_env_prefer_dotenv(env_var)
1640
+ if not token:
1641
+ continue
1642
+ source = f"env:{env_var}"
1643
+ if _is_source_suppressed(provider, source):
1644
+ continue
1645
+ active_sources.add(source)
1646
+ auth_type = AUTH_TYPE_OAUTH if provider == "anthropic" and not token.startswith("sk-ant-api") else AUTH_TYPE_API_KEY
1647
+ base_url = env_url or pconfig.inference_base_url
1648
+ if provider == "kimi-coding":
1649
+ base_url = _resolve_kimi_base_url(token, pconfig.inference_base_url, env_url)
1650
+ elif provider == "zai":
1651
+ base_url = _resolve_zai_base_url(token, pconfig.inference_base_url, env_url)
1652
+ changed |= _upsert_entry(
1653
+ entries,
1654
+ provider,
1655
+ source,
1656
+ {
1657
+ "source": source,
1658
+ "auth_type": auth_type,
1659
+ "access_token": token,
1660
+ "base_url": base_url,
1661
+ "label": env_var,
1662
+ },
1663
+ )
1664
+ return changed, active_sources
1665
+
1666
+
1667
+ def _prune_stale_seeded_entries(entries: List[PooledCredential], active_sources: Set[str]) -> bool:
1668
+ retained = [
1669
+ entry
1670
+ for entry in entries
1671
+ if _is_manual_source(entry.source)
1672
+ or entry.source in active_sources
1673
+ or not (
1674
+ entry.source.startswith("env:")
1675
+ or entry.source in {"claude_code", "hermes_pkce"}
1676
+ )
1677
+ ]
1678
+ if len(retained) == len(entries):
1679
+ return False
1680
+ entries[:] = retained
1681
+ return True
1682
+
1683
+
1684
+ def _seed_custom_pool(pool_key: str, entries: List[PooledCredential]) -> Tuple[bool, Set[str]]:
1685
+ """Seed a custom endpoint pool from custom_providers config and model config."""
1686
+ changed = False
1687
+ active_sources: Set[str] = set()
1688
+
1689
+ # Shared suppression gate — same pattern as _seed_from_env/_seed_from_singletons.
1690
+ try:
1691
+ from hermes_cli.auth import is_source_suppressed as _is_suppressed
1692
+ except ImportError:
1693
+ def _is_suppressed(_p, _s): # type: ignore[misc]
1694
+ return False
1695
+
1696
+ # Seed from the custom_providers config entry's api_key field
1697
+ cp_config = _get_custom_provider_config(pool_key)
1698
+ if cp_config:
1699
+ api_key = str(cp_config.get("api_key") or "").strip()
1700
+ base_url = str(cp_config.get("base_url") or "").strip().rstrip("/")
1701
+ name = str(cp_config.get("name") or "").strip()
1702
+ if api_key:
1703
+ source = f"config:{name}"
1704
+ if not _is_suppressed(pool_key, source):
1705
+ active_sources.add(source)
1706
+ changed |= _upsert_entry(
1707
+ entries,
1708
+ pool_key,
1709
+ source,
1710
+ {
1711
+ "source": source,
1712
+ "auth_type": AUTH_TYPE_API_KEY,
1713
+ "access_token": api_key,
1714
+ "base_url": base_url,
1715
+ "label": name or source,
1716
+ },
1717
+ )
1718
+
1719
+ # Seed from model.api_key if model.provider=='custom' and model.base_url matches
1720
+ try:
1721
+ config = _load_config_safe()
1722
+ model_cfg = config.get("model") if config else None
1723
+ if isinstance(model_cfg, dict):
1724
+ model_provider = str(model_cfg.get("provider") or "").strip().lower()
1725
+ model_base_url = str(model_cfg.get("base_url") or "").strip().rstrip("/")
1726
+ model_api_key = ""
1727
+ for k in ("api_key", "api"):
1728
+ v = model_cfg.get(k)
1729
+ if isinstance(v, str) and v.strip():
1730
+ model_api_key = v.strip()
1731
+ break
1732
+ if model_provider == "custom" and model_base_url and model_api_key:
1733
+ # Check if this model's base_url matches our custom provider
1734
+ matched_key = get_custom_provider_pool_key(model_base_url)
1735
+ if matched_key == pool_key:
1736
+ source = "model_config"
1737
+ if not _is_suppressed(pool_key, source):
1738
+ active_sources.add(source)
1739
+ changed |= _upsert_entry(
1740
+ entries,
1741
+ pool_key,
1742
+ source,
1743
+ {
1744
+ "source": source,
1745
+ "auth_type": AUTH_TYPE_API_KEY,
1746
+ "access_token": model_api_key,
1747
+ "base_url": model_base_url,
1748
+ "label": "model_config",
1749
+ },
1750
+ )
1751
+ except Exception:
1752
+ pass
1753
+
1754
+ return changed, active_sources
1755
+
1756
+
1757
+ def load_pool(provider: str) -> CredentialPool:
1758
+ provider = (provider or "").strip().lower()
1759
+ raw_entries = read_credential_pool(provider)
1760
+ entries = [PooledCredential.from_dict(provider, payload) for payload in raw_entries]
1761
+
1762
+ if provider.startswith(CUSTOM_POOL_PREFIX):
1763
+ # Custom endpoint pool — seed from custom_providers config and model config
1764
+ custom_changed, custom_sources = _seed_custom_pool(provider, entries)
1765
+ changed = custom_changed
1766
+ changed |= _prune_stale_seeded_entries(entries, custom_sources)
1767
+ else:
1768
+ singleton_changed, singleton_sources = _seed_from_singletons(provider, entries)
1769
+ env_changed, env_sources = _seed_from_env(provider, entries)
1770
+ changed = singleton_changed or env_changed
1771
+ changed |= _prune_stale_seeded_entries(entries, singleton_sources | env_sources)
1772
+ changed |= _normalize_pool_priorities(provider, entries)
1773
+
1774
+ if changed:
1775
+ write_credential_pool(
1776
+ provider,
1777
+ [entry.to_dict() for entry in sorted(entries, key=lambda item: item.priority)],
1778
+ )
1779
+ return CredentialPool(provider, entries)
1780
+