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,3136 @@
1
+ #!/usr/bin/env python3
2
+ """OpenClaw -> Hermes migration helper.
3
+
4
+ This script migrates the parts of an OpenClaw user footprint that map cleanly
5
+ into Hermes Agent, archives selected unmapped docs for manual review, and
6
+ reports exactly what was skipped and why.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import hashlib
13
+ import json
14
+ import os
15
+ import re
16
+ import shutil
17
+ from dataclasses import asdict, dataclass, field
18
+ from datetime import datetime
19
+ from pathlib import Path
20
+ from typing import Any, Dict, List, Optional, Sequence, Tuple
21
+
22
+ try:
23
+ import yaml
24
+ except Exception: # pragma: no cover - handled at runtime
25
+ yaml = None
26
+
27
+
28
+ ENTRY_DELIMITER = "\n§\n"
29
+ DEFAULT_MEMORY_CHAR_LIMIT = 2200
30
+ DEFAULT_USER_CHAR_LIMIT = 1375
31
+ SKILL_CATEGORY_DIRNAME = "openclaw-imports"
32
+ SKILL_CATEGORY_DESCRIPTION = (
33
+ "Skills migrated from an OpenClaw workspace."
34
+ )
35
+ SKILL_CONFLICT_MODES = {"skip", "overwrite", "rename"}
36
+ SUPPORTED_SECRET_TARGETS={
37
+ "TELEGRAM_BOT_TOKEN",
38
+ "OPENROUTER_API_KEY",
39
+ "OPENAI_API_KEY",
40
+ "ANTHROPIC_API_KEY",
41
+ "ELEVENLABS_API_KEY",
42
+ "VOICE_TOOLS_OPENAI_KEY",
43
+ }
44
+ WORKSPACE_INSTRUCTIONS_FILENAME = "AGENTS" + ".md"
45
+ MIGRATION_OPTION_METADATA: Dict[str, Dict[str, str]] = {
46
+ "soul": {
47
+ "label": "SOUL.md",
48
+ "description": "Import the OpenClaw persona file into Hermes.",
49
+ },
50
+ "workspace-agents": {
51
+ "label": "Workspace instructions",
52
+ "description": "Copy the OpenClaw workspace instructions file into a chosen workspace.",
53
+ },
54
+ "memory": {
55
+ "label": "MEMORY.md",
56
+ "description": "Import long-term memory entries into Hermes memories.",
57
+ },
58
+ "user-profile": {
59
+ "label": "USER.md",
60
+ "description": "Import user profile entries into Hermes memories.",
61
+ },
62
+ "messaging-settings": {
63
+ "label": "Messaging settings",
64
+ "description": "Import Hermes-compatible messaging settings such as allowlists and working directory.",
65
+ },
66
+ "secret-settings": {
67
+ "label": "Allowlisted secrets",
68
+ "description": "Import the small allowlist of Hermes-compatible secrets when explicitly enabled.",
69
+ },
70
+ "command-allowlist": {
71
+ "label": "Command allowlist",
72
+ "description": "Merge OpenClaw exec approval patterns into Hermes command_allowlist.",
73
+ },
74
+ "skills": {
75
+ "label": "User skills",
76
+ "description": "Copy OpenClaw skills into ~/.hermes/skills/openclaw-imports/.",
77
+ },
78
+ "tts-assets": {
79
+ "label": "TTS assets",
80
+ "description": "Copy compatible workspace TTS assets into ~/.hermes/tts/.",
81
+ },
82
+ "discord-settings": {
83
+ "label": "Discord settings",
84
+ "description": "Import Discord bot token and allowlist into Hermes .env.",
85
+ },
86
+ "slack-settings": {
87
+ "label": "Slack settings",
88
+ "description": "Import Slack bot/app tokens and allowlist into Hermes .env.",
89
+ },
90
+ "whatsapp-settings": {
91
+ "label": "WhatsApp settings",
92
+ "description": "Import WhatsApp allowlist into Hermes .env.",
93
+ },
94
+ "signal-settings": {
95
+ "label": "Signal settings",
96
+ "description": "Import Signal account, HTTP URL, and allowlist into Hermes .env.",
97
+ },
98
+ "provider-keys": {
99
+ "label": "Provider API keys",
100
+ "description": "Import model provider API keys into Hermes .env (requires --migrate-secrets).",
101
+ },
102
+ "model-config": {
103
+ "label": "Default model",
104
+ "description": "Import the default model setting into Hermes config.yaml.",
105
+ },
106
+ "tts-config": {
107
+ "label": "TTS configuration",
108
+ "description": "Import TTS provider and voice settings into Hermes config.yaml.",
109
+ },
110
+ "shared-skills": {
111
+ "label": "Shared skills",
112
+ "description": "Copy shared OpenClaw skills from ~/.openclaw/skills/ into Hermes.",
113
+ },
114
+ "daily-memory": {
115
+ "label": "Daily memory files",
116
+ "description": "Merge daily memory entries from workspace/memory/ into Hermes MEMORY.md.",
117
+ },
118
+ "archive": {
119
+ "label": "Archive unmapped docs",
120
+ "description": "Archive compatible-but-unmapped docs for later manual review.",
121
+ },
122
+ "mcp-servers": {
123
+ "label": "MCP servers",
124
+ "description": "Import MCP server definitions from OpenClaw into Hermes config.yaml.",
125
+ },
126
+ "plugins-config": {
127
+ "label": "Plugins configuration",
128
+ "description": "Archive OpenClaw plugin configuration and installed extensions for manual review.",
129
+ },
130
+ "cron-jobs": {
131
+ "label": "Cron / scheduled tasks",
132
+ "description": "Import cron job definitions. Archive for manual recreation via 'hermes cron'.",
133
+ },
134
+ "hooks-config": {
135
+ "label": "Hooks and webhooks",
136
+ "description": "Archive OpenClaw hook configuration (internal hooks, webhooks, Gmail integration).",
137
+ },
138
+ "agent-config": {
139
+ "label": "Agent defaults and multi-agent setup",
140
+ "description": "Import agent defaults (compaction, context, thinking) into Hermes config. Archive multi-agent list.",
141
+ },
142
+ "gateway-config": {
143
+ "label": "Gateway configuration",
144
+ "description": "Import gateway port and auth settings. Archive full gateway config for manual setup.",
145
+ },
146
+ "session-config": {
147
+ "label": "Session configuration",
148
+ "description": "Import session reset policies (daily/idle) into Hermes session_reset config.",
149
+ },
150
+ "full-providers": {
151
+ "label": "Full model provider definitions",
152
+ "description": "Import custom model providers (baseUrl, apiType, headers) into Hermes custom_providers.",
153
+ },
154
+ "deep-channels": {
155
+ "label": "Deep channel configuration",
156
+ "description": "Import extended channel settings (Matrix, Mattermost, IRC, group configs). Archive complex settings.",
157
+ },
158
+ "browser-config": {
159
+ "label": "Browser configuration",
160
+ "description": "Import browser automation settings into Hermes config.yaml.",
161
+ },
162
+ "tools-config": {
163
+ "label": "Tools configuration",
164
+ "description": "Import tool settings (exec timeout, sandbox, web search) into Hermes config.yaml.",
165
+ },
166
+ "approvals-config": {
167
+ "label": "Approval rules",
168
+ "description": "Import approval mode and rules into Hermes config.yaml approvals section.",
169
+ },
170
+ "memory-backend": {
171
+ "label": "Memory backend configuration",
172
+ "description": "Archive OpenClaw memory backend settings (QMD, vector search, citations) for manual review.",
173
+ },
174
+ "skills-config": {
175
+ "label": "Skills registry configuration",
176
+ "description": "Archive per-skill enabled/config/env settings from OpenClaw skills.entries.",
177
+ },
178
+ "ui-identity": {
179
+ "label": "UI and identity settings",
180
+ "description": "Archive OpenClaw UI theme, assistant identity, and display preferences.",
181
+ },
182
+ "logging-config": {
183
+ "label": "Logging and diagnostics",
184
+ "description": "Archive OpenClaw logging and diagnostics configuration.",
185
+ },
186
+ }
187
+ MIGRATION_PRESETS: Dict[str, set[str]] = {
188
+ "user-data": {
189
+ "soul",
190
+ "workspace-agents",
191
+ "memory",
192
+ "user-profile",
193
+ "messaging-settings",
194
+ "command-allowlist",
195
+ "skills",
196
+ "tts-assets",
197
+ "discord-settings",
198
+ "slack-settings",
199
+ "whatsapp-settings",
200
+ "signal-settings",
201
+ "model-config",
202
+ "tts-config",
203
+ "shared-skills",
204
+ "daily-memory",
205
+ "archive",
206
+ "mcp-servers",
207
+ "agent-config",
208
+ "session-config",
209
+ "browser-config",
210
+ "tools-config",
211
+ "approvals-config",
212
+ "deep-channels",
213
+ "full-providers",
214
+ "plugins-config",
215
+ "cron-jobs",
216
+ "hooks-config",
217
+ "memory-backend",
218
+ "skills-config",
219
+ "ui-identity",
220
+ "logging-config",
221
+ "gateway-config",
222
+ },
223
+ "full": set(MIGRATION_OPTION_METADATA),
224
+ }
225
+
226
+
227
+ # ───────────────────────────────────────────────────────────────────────
228
+ # Item shape constants — kept stable for downstream consumers of report.json.
229
+ # Inspired by OpenClaw's src/plugin-sdk/migration.ts so both sides speak the
230
+ # same vocabulary. Values intentionally match the strings already produced
231
+ # by this script (migrated/archived/skipped/conflict/error) so the addition
232
+ # is backward-compatible.
233
+ # ───────────────────────────────────────────────────────────────────────
234
+ STATUS_MIGRATED = "migrated"
235
+ STATUS_ARCHIVED = "archived"
236
+ STATUS_SKIPPED = "skipped"
237
+ STATUS_CONFLICT = "conflict"
238
+ STATUS_ERROR = "error"
239
+ STATUS_PLANNED = "planned"
240
+
241
+ REASON_TARGET_EXISTS = "Target exists and overwrite is disabled"
242
+ REASON_BLOCKED_BY_APPLY_CONFLICT = "blocked by earlier apply conflict"
243
+
244
+
245
+ @dataclass
246
+ class ItemResult:
247
+ kind: str
248
+ source: Optional[str]
249
+ destination: Optional[str]
250
+ status: str
251
+ reason: str = ""
252
+ details: Dict[str, Any] = field(default_factory=dict)
253
+ sensitive: bool = False
254
+
255
+
256
+ def parse_selection_values(values: Optional[Sequence[str]]) -> List[str]:
257
+ parsed: List[str] = []
258
+ for value in values or ():
259
+ for part in str(value).split(","):
260
+ part = part.strip().lower()
261
+ if part:
262
+ parsed.append(part)
263
+ return parsed
264
+
265
+
266
+ def resolve_selected_options(
267
+ include: Optional[Sequence[str]] = None,
268
+ exclude: Optional[Sequence[str]] = None,
269
+ preset: Optional[str] = None,
270
+ ) -> set[str]:
271
+ include_values = parse_selection_values(include)
272
+ exclude_values = parse_selection_values(exclude)
273
+ valid = set(MIGRATION_OPTION_METADATA)
274
+ preset_name = (preset or "").strip().lower()
275
+
276
+ if preset_name and preset_name not in MIGRATION_PRESETS:
277
+ raise ValueError(
278
+ "Unknown migration preset: "
279
+ + preset_name
280
+ + ". Valid presets: "
281
+ + ", ".join(sorted(MIGRATION_PRESETS))
282
+ )
283
+
284
+ unknown = (set(include_values) - {"all"} - valid) | (set(exclude_values) - {"all"} - valid)
285
+ if unknown:
286
+ raise ValueError(
287
+ "Unknown migration option(s): "
288
+ + ", ".join(sorted(unknown))
289
+ + ". Valid options: "
290
+ + ", ".join(sorted(valid))
291
+ )
292
+
293
+ if preset_name:
294
+ selected = set(MIGRATION_PRESETS[preset_name])
295
+ elif not include_values or "all" in include_values:
296
+ selected = set(valid)
297
+ else:
298
+ selected = set(include_values)
299
+
300
+ if "all" in exclude_values:
301
+ selected.clear()
302
+ selected -= (set(exclude_values) - {"all"})
303
+ return selected
304
+
305
+
306
+ def sha256_file(path: Path) -> str:
307
+ h = hashlib.sha256()
308
+ with path.open("rb") as fh:
309
+ for chunk in iter(lambda: fh.read(65536), b""):
310
+ h.update(chunk)
311
+ return h.hexdigest()
312
+
313
+
314
+ def read_text(path: Path) -> str:
315
+ return path.read_text(encoding="utf-8")
316
+
317
+
318
+ def normalize_text(text: str) -> str:
319
+ return re.sub(r"\s+", " ", text.strip())
320
+
321
+
322
+ def ensure_parent(path: Path) -> None:
323
+ path.parent.mkdir(parents=True, exist_ok=True)
324
+
325
+
326
+ def resolve_secret_input(value: Any, env: Optional[Dict[str, str]] = None) -> Optional[str]:
327
+ """Resolve an OpenClaw SecretInput value to a plain string.
328
+
329
+ SecretInput can be:
330
+ - A plain string: "sk-..."
331
+ - An env template: "${OPENROUTER_API_KEY}"
332
+ - A SecretRef object: {"source": "env", "id": "OPENROUTER_API_KEY"}
333
+ """
334
+ if isinstance(value, str):
335
+ # Check for env template: "${VAR_NAME}"
336
+ m = re.match(r"^\$\{(\w+)\}$", value.strip())
337
+ if m and env:
338
+ return env.get(m.group(1), "").strip() or None
339
+ return value.strip() or None
340
+ if isinstance(value, dict):
341
+ source = value.get("source", "")
342
+ ref_id = value.get("id", "")
343
+ if source == "env" and ref_id and env:
344
+ return env.get(ref_id, "").strip() or None
345
+ # File/exec sources can't be resolved here — return None
346
+ return None
347
+
348
+
349
+ def load_yaml_file(path: Path) -> Dict[str, Any]:
350
+ if yaml is None or not path.exists():
351
+ return {}
352
+ data = yaml.safe_load(path.read_text(encoding="utf-8"))
353
+ return data if isinstance(data, dict) else {}
354
+
355
+
356
+ def dump_yaml_file(path: Path, data: Dict[str, Any]) -> None:
357
+ if yaml is None:
358
+ raise RuntimeError("PyYAML is required to update Hermes config.yaml")
359
+ ensure_parent(path)
360
+ path.write_text(
361
+ yaml.safe_dump(data, sort_keys=False, allow_unicode=False),
362
+ encoding="utf-8",
363
+ )
364
+
365
+
366
+ def parse_env_file(path: Path) -> Dict[str, str]:
367
+ if not path.exists():
368
+ return {}
369
+ data: Dict[str, str] = {}
370
+ for raw_line in path.read_text(encoding="utf-8").splitlines():
371
+ line = raw_line.strip()
372
+ if not line or line.startswith("#") or "=" not in line:
373
+ continue
374
+ key, _, value = line.partition("=")
375
+ data[key.strip()] = value.strip()
376
+ return data
377
+
378
+
379
+ def save_env_file(path: Path, data: Dict[str, str]) -> None:
380
+ ensure_parent(path)
381
+ lines = [f"{key}={value}" for key, value in data.items()]
382
+ path.write_text("\n".join(lines) + ("\n" if lines else ""), encoding="utf-8")
383
+
384
+
385
+ def backup_existing(path: Path, backup_root: Path) -> Optional[Path]:
386
+ if not path.exists():
387
+ return None
388
+ rel = Path(*path.parts[1:]) if path.is_absolute() and len(path.parts) > 1 else path
389
+ dest = backup_root / rel
390
+ ensure_parent(dest)
391
+ if path.is_dir():
392
+ shutil.copytree(path, dest, dirs_exist_ok=True)
393
+ else:
394
+ shutil.copy2(path, dest)
395
+ return dest
396
+
397
+
398
+ # ── Brand rewriting ─────────────────────────────────────────
399
+ # Replace OpenClaw brand names with Hermes in migrated text so that
400
+ # memory entries, user profiles, SOUL.md, and workspace instructions
401
+ # read as self-referential to the new agent identity.
402
+ #
403
+ # Case-preserving: ``OpenClaw`` → ``Hermes`` (prose), but lowercase matches
404
+ # like ``openclaw`` → ``hermes`` (so filesystem paths like ``~/.openclaw``
405
+ # become ``~/.hermes`` — the real Hermes home — not the broken ``~/.Hermes``).
406
+ _REBRAND_PATTERNS: List[Tuple[re.Pattern, str]] = [
407
+ (re.compile(r'\bOpen[\s-]?Claw\b', re.IGNORECASE), 'Hermes'),
408
+ (re.compile(r'\bClawdBot\b', re.IGNORECASE), 'Hermes'),
409
+ (re.compile(r'\bMoltBot\b', re.IGNORECASE), 'Hermes'),
410
+ ]
411
+
412
+
413
+ def _case_preserving_replacement(replacement: str):
414
+ """Return a re.sub replacement fn that lowercases the result when the
415
+ matched text was all-lowercase.
416
+
417
+ Keeps ``OpenClaw`` → ``Hermes`` but maps ``openclaw`` → ``hermes`` so a
418
+ filesystem path like ``~/.openclaw/config.yaml`` rewrites to
419
+ ``~/.hermes/config.yaml`` (the real Hermes home) instead of the broken
420
+ ``~/.Hermes/config.yaml``.
421
+ """
422
+ def _sub(match: "re.Match[str]") -> str:
423
+ matched = match.group(0)
424
+ if matched and matched.islower():
425
+ return replacement.lower()
426
+ return replacement
427
+ return _sub
428
+
429
+
430
+ def rebrand_text(text: str) -> str:
431
+ """Replace OpenClaw / ClawdBot / MoltBot brand names with Hermes.
432
+
433
+ Preserves case so filesystem-path matches (lowercase) don't become
434
+ capitalized directory names that don't exist.
435
+ """
436
+ for pattern, replacement in _REBRAND_PATTERNS:
437
+ text = pattern.sub(_case_preserving_replacement(replacement), text)
438
+ return text
439
+
440
+
441
+ def parse_existing_memory_entries(path: Path) -> List[str]:
442
+ if not path.exists():
443
+ return []
444
+ raw = read_text(path)
445
+ if not raw.strip():
446
+ return []
447
+ if ENTRY_DELIMITER in raw:
448
+ return [e.strip() for e in raw.split(ENTRY_DELIMITER) if e.strip()]
449
+ return extract_markdown_entries(raw)
450
+
451
+
452
+ def extract_markdown_entries(text: str) -> List[str]:
453
+ entries: List[str] = []
454
+ headings: List[str] = []
455
+ paragraph_lines: List[str] = []
456
+
457
+ def context_prefix() -> str:
458
+ filtered = [h for h in headings if h and not re.search(r"\b(MEMORY|USER|SOUL|AGENTS|TOOLS|IDENTITY)\.md\b", h, re.I)]
459
+ return " > ".join(filtered)
460
+
461
+ def flush_paragraph() -> None:
462
+ nonlocal paragraph_lines
463
+ if not paragraph_lines:
464
+ return
465
+ text_block = " ".join(line.strip() for line in paragraph_lines).strip()
466
+ paragraph_lines = []
467
+ if not text_block:
468
+ return
469
+ prefix = context_prefix()
470
+ if prefix:
471
+ entries.append(f"{prefix}: {text_block}")
472
+ else:
473
+ entries.append(text_block)
474
+
475
+ in_code_block = False
476
+ for raw_line in text.splitlines():
477
+ line = raw_line.rstrip()
478
+ stripped = line.strip()
479
+
480
+ if stripped.startswith("```"):
481
+ in_code_block = not in_code_block
482
+ flush_paragraph()
483
+ continue
484
+ if in_code_block:
485
+ continue
486
+
487
+ heading_match = re.match(r"^(#{1,6})\s+(.*\S)\s*$", stripped)
488
+ if heading_match:
489
+ flush_paragraph()
490
+ level = len(heading_match.group(1))
491
+ text_value = heading_match.group(2).strip()
492
+ while len(headings) >= level:
493
+ headings.pop()
494
+ headings.append(text_value)
495
+ continue
496
+
497
+ bullet_match = re.match(r"^\s*(?:[-*]|\d+\.)\s+(.*\S)\s*$", line)
498
+ if bullet_match:
499
+ flush_paragraph()
500
+ content = bullet_match.group(1).strip()
501
+ prefix = context_prefix()
502
+ entries.append(f"{prefix}: {content}" if prefix else content)
503
+ continue
504
+
505
+ if not stripped:
506
+ flush_paragraph()
507
+ continue
508
+
509
+ if stripped.startswith("|") and stripped.endswith("|"):
510
+ flush_paragraph()
511
+ continue
512
+
513
+ paragraph_lines.append(stripped)
514
+
515
+ flush_paragraph()
516
+
517
+ deduped: List[str] = []
518
+ seen = set()
519
+ for entry in entries:
520
+ normalized = normalize_text(entry)
521
+ if not normalized or normalized in seen:
522
+ continue
523
+ seen.add(normalized)
524
+ deduped.append(entry.strip())
525
+ return deduped
526
+
527
+
528
+ def merge_entries(
529
+ existing: Sequence[str],
530
+ incoming: Sequence[str],
531
+ limit: int,
532
+ ) -> Tuple[List[str], Dict[str, int], List[str]]:
533
+ merged = list(existing)
534
+ seen = {normalize_text(entry) for entry in existing if entry.strip()}
535
+ stats = {"existing": len(existing), "added": 0, "duplicates": 0, "overflowed": 0}
536
+ overflowed: List[str] = []
537
+
538
+ current_len = len(ENTRY_DELIMITER.join(merged)) if merged else 0
539
+
540
+ for entry in incoming:
541
+ normalized = normalize_text(entry)
542
+ if not normalized:
543
+ continue
544
+ if normalized in seen:
545
+ stats["duplicates"] += 1
546
+ continue
547
+
548
+ candidate_len = len(entry) if not merged else current_len + len(ENTRY_DELIMITER) + len(entry)
549
+ if candidate_len > limit:
550
+ stats["overflowed"] += 1
551
+ overflowed.append(entry)
552
+ continue
553
+
554
+ merged.append(entry)
555
+ seen.add(normalized)
556
+ current_len = candidate_len
557
+ stats["added"] += 1
558
+
559
+ return merged, stats, overflowed
560
+
561
+
562
+ def relative_label(path: Path, root: Path) -> str:
563
+ try:
564
+ return str(path.relative_to(root))
565
+ except ValueError:
566
+ return str(path)
567
+
568
+
569
+ # ───────────────────────────────────────────────────────────────────────
570
+ # Secret redaction for migration reports.
571
+ #
572
+ # The report JSON persists to disk inside the migration output directory and
573
+ # frequently ends up in bug reports or support channels. Anything that looks
574
+ # like a credential — by key name or by value shape — is replaced with
575
+ # "[redacted]" before the report is written.
576
+ #
577
+ # Modelled on OpenClaw's src/plugin-sdk/migration.ts so both migration tools
578
+ # redact consistently. Pure function — safe to call on any plain-data dict.
579
+ # ───────────────────────────────────────────────────────────────────────
580
+ REDACTED_MIGRATION_VALUE = "[redacted]"
581
+
582
+ _SECRET_KEY_MARKERS = (
583
+ "accesstoken",
584
+ "apikey",
585
+ "authorization",
586
+ "bearertoken",
587
+ "clientsecret",
588
+ "cookie",
589
+ "credential",
590
+ "password",
591
+ "privatekey",
592
+ "refreshtoken",
593
+ "secret",
594
+ )
595
+
596
+ _SECRET_VALUE_PATTERNS = (
597
+ re.compile(r"\bBearer\s+[A-Za-z0-9._~+/=\-]+"),
598
+ re.compile(r"\bsk-[A-Za-z0-9_\-]{8,}\b"),
599
+ re.compile(r"\bgh[pousr]_[A-Za-z0-9_]{16,}\b"),
600
+ re.compile(r"\bxox[abprs]-[A-Za-z0-9\-]{8,}\b"),
601
+ re.compile(r"\bAIza[0-9A-Za-z_\-]{12,}\b"),
602
+ )
603
+
604
+
605
+ def _normalize_secret_key(key: str) -> str:
606
+ return re.sub(r"[^a-z0-9]", "", key.lower())
607
+
608
+
609
+ def _is_secret_key(key: str) -> bool:
610
+ normalized = _normalize_secret_key(key)
611
+ if normalized == "token" or normalized.endswith("token"):
612
+ return True
613
+ if normalized in ("auth", "authorization"):
614
+ return True
615
+ return any(marker in normalized for marker in _SECRET_KEY_MARKERS)
616
+
617
+
618
+ def _redact_string(value: str) -> str:
619
+ for pattern in _SECRET_VALUE_PATTERNS:
620
+ value = pattern.sub(REDACTED_MIGRATION_VALUE, value)
621
+ return value
622
+
623
+
624
+ def redact_migration_value(value: Any) -> Any:
625
+ """Return a deep copy of ``value`` with secret-looking content replaced.
626
+
627
+ Applied to every report written to disk. Keys whose normalized form
628
+ matches a credential marker get their value replaced wholesale. Strings
629
+ anywhere in the tree are scanned for common token patterns (sk-..., ghp_...,
630
+ xox*-, AIza*, Bearer ...) and those substrings are replaced inline.
631
+ """
632
+ return _redact_internal(value, set())
633
+
634
+
635
+ def _redact_internal(value: Any, seen: set) -> Any:
636
+ if isinstance(value, str):
637
+ return _redact_string(value)
638
+ if isinstance(value, (list, tuple)):
639
+ return [_redact_internal(entry, seen) for entry in value]
640
+ if isinstance(value, dict):
641
+ obj_id = id(value)
642
+ if obj_id in seen:
643
+ return REDACTED_MIGRATION_VALUE
644
+ seen.add(obj_id)
645
+ out: Dict[str, Any] = {}
646
+ for key, entry in value.items():
647
+ if isinstance(key, str) and _is_secret_key(key):
648
+ out[key] = REDACTED_MIGRATION_VALUE
649
+ else:
650
+ out[key] = _redact_internal(entry, seen)
651
+ return out
652
+ return value
653
+
654
+
655
+ def write_report(output_dir: Path, report: Dict[str, Any]) -> None:
656
+ output_dir.mkdir(parents=True, exist_ok=True)
657
+ # Always redact before persisting. Callers who need the raw object
658
+ # (in-process) still get it back from build_report(); only the on-disk
659
+ # copy is redacted.
660
+ redacted = redact_migration_value(report)
661
+ (output_dir / "report.json").write_text(
662
+ json.dumps(redacted, indent=2, ensure_ascii=False) + "\n",
663
+ encoding="utf-8",
664
+ )
665
+
666
+ grouped: Dict[str, List[Dict[str, Any]]] = {}
667
+ for item in redacted["items"]:
668
+ grouped.setdefault(item["status"], []).append(item)
669
+
670
+ lines = [
671
+ "# OpenClaw -> Hermes Migration Report",
672
+ "",
673
+ f"- Timestamp: {redacted['timestamp']}",
674
+ f"- Mode: {redacted['mode']}",
675
+ f"- Source: `{redacted['source_root']}`",
676
+ f"- Target: `{redacted['target_root']}`",
677
+ "",
678
+ "## Summary",
679
+ "",
680
+ ]
681
+
682
+ for key, value in redacted["summary"].items():
683
+ lines.append(f"- {key}: {value}")
684
+
685
+ warnings = redacted.get("warnings") or []
686
+ if warnings:
687
+ lines.extend(["", "## Warnings", ""])
688
+ for warning in warnings:
689
+ lines.append(f"- {warning}")
690
+
691
+ lines.extend(["", "## What Was Not Fully Brought Over", ""])
692
+ skipped = grouped.get("skipped", []) + grouped.get("conflict", []) + grouped.get("error", [])
693
+ if not skipped:
694
+ lines.append("- Nothing. All discovered items were either migrated or archived.")
695
+ else:
696
+ for item in skipped:
697
+ source = item["source"] or "(n/a)"
698
+ dest = item["destination"] or "(n/a)"
699
+ reason = item["reason"] or item["status"]
700
+ lines.append(f"- `{source}` -> `{dest}`: {reason}")
701
+
702
+ next_steps = redacted.get("next_steps") or []
703
+ if next_steps:
704
+ lines.extend(["", "## Next Steps", ""])
705
+ for step in next_steps:
706
+ lines.append(f"- {step}")
707
+
708
+ (output_dir / "summary.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
709
+
710
+
711
+ class Migrator:
712
+ def __init__(
713
+ self,
714
+ source_root: Path,
715
+ target_root: Path,
716
+ execute: bool,
717
+ workspace_target: Optional[Path],
718
+ overwrite: bool,
719
+ migrate_secrets: bool,
720
+ output_dir: Optional[Path],
721
+ selected_options: Optional[set[str]] = None,
722
+ preset_name: str = "",
723
+ skill_conflict_mode: str = "skip",
724
+ ):
725
+ self.source_root = source_root
726
+ self.target_root = target_root
727
+ self.execute = execute
728
+ self.workspace_target = workspace_target
729
+ self.overwrite = overwrite
730
+ self.migrate_secrets = migrate_secrets
731
+ self.selected_options = set(selected_options or MIGRATION_OPTION_METADATA.keys())
732
+ self.preset_name = preset_name.strip().lower()
733
+ self.skill_conflict_mode = skill_conflict_mode.strip().lower() or "skip"
734
+ self.timestamp = datetime.now().strftime("%Y%m%dT%H%M%S")
735
+ self.output_dir = output_dir or (
736
+ target_root / "migration" / "openclaw" / self.timestamp if execute else None
737
+ )
738
+ self.archive_dir = self.output_dir / "archive" if self.output_dir else None
739
+ self.backup_dir = self.output_dir / "backups" if self.output_dir else None
740
+ self.overflow_dir = self.output_dir / "overflow" if self.output_dir else None
741
+ self.items: List[ItemResult] = []
742
+ # Once a config.yaml write hits conflict/error mid-run, later
743
+ # config.yaml writes are deliberately short-circuited to avoid
744
+ # leaving config in a partially-written state. Modelled on
745
+ # OpenClaw's extensions/migrate-hermes/apply.ts "blocked by earlier
746
+ # apply conflict" sequencing.
747
+ self._config_apply_blocked: bool = False
748
+
749
+ # Resolve the configured workspace directory from openclaw.json.
750
+ # Many users (especially those who started before the OpenClaw rebrand)
751
+ # have a custom workspace path (e.g. ~/clawd/) that differs from the
752
+ # default ~/.openclaw/workspace/. Reading agents.defaults.workspace
753
+ # lets source_candidate() find files in the actual workspace.
754
+ self._custom_workspace: Optional[Path] = None
755
+ oc_config = self.load_openclaw_config()
756
+ ws = (oc_config.get("agents", {}).get("defaults", {}).get("workspace") or "").strip()
757
+ if ws:
758
+ ws_path = Path(ws).expanduser().resolve()
759
+ # Only use it if it exists and is outside the source_root tree
760
+ # (otherwise the standard relative-path logic already covers it).
761
+ if ws_path.is_dir():
762
+ try:
763
+ ws_path.relative_to(self.source_root)
764
+ except ValueError:
765
+ # ws_path is outside source_root — use it as custom workspace
766
+ self._custom_workspace = ws_path
767
+
768
+ config = load_yaml_file(self.target_root / "config.yaml")
769
+ mem_cfg = config.get("memory", {}) if isinstance(config.get("memory"), dict) else {}
770
+ self.memory_limit = int(mem_cfg.get("memory_char_limit", DEFAULT_MEMORY_CHAR_LIMIT))
771
+ self.user_limit = int(mem_cfg.get("user_char_limit", DEFAULT_USER_CHAR_LIMIT))
772
+
773
+ if self.skill_conflict_mode not in SKILL_CONFLICT_MODES:
774
+ raise ValueError(
775
+ "Unknown skill conflict mode: "
776
+ + self.skill_conflict_mode
777
+ + ". Valid modes: "
778
+ + ", ".join(sorted(SKILL_CONFLICT_MODES))
779
+ )
780
+
781
+ def is_selected(self, option_id: str) -> bool:
782
+ return option_id in self.selected_options
783
+
784
+ # Option ids that mutate the Hermes config.yaml file. Once any one of
785
+ # them records a conflict/error on config.yaml, subsequent ones are
786
+ # short-circuited to avoid partial writes. Keep in sync with methods
787
+ # that call load_yaml_file(target_root / "config.yaml") + dump_yaml_file.
788
+ _CONFIG_MUTATING_OPTIONS = frozenset({
789
+ "model-config",
790
+ "tts-config",
791
+ "mcp-servers",
792
+ "plugins-config",
793
+ "cron-jobs",
794
+ "hooks-config",
795
+ "agent-config",
796
+ "gateway-config",
797
+ "session-config",
798
+ "full-providers",
799
+ "deep-channels",
800
+ "browser-config",
801
+ "tools-config",
802
+ "approvals-config",
803
+ "memory-backend",
804
+ "skills-config",
805
+ "ui-identity",
806
+ "logging-config",
807
+ "command-allowlist",
808
+ })
809
+
810
+ def record(
811
+ self,
812
+ kind: str,
813
+ source: Optional[Path],
814
+ destination: Optional[Path],
815
+ status: str,
816
+ reason: str = "",
817
+ **details: Any,
818
+ ) -> None:
819
+ sensitive = bool(details.pop("sensitive", False))
820
+ self.items.append(
821
+ ItemResult(
822
+ kind=kind,
823
+ source=str(source) if source else None,
824
+ destination=str(destination) if destination else None,
825
+ status=status,
826
+ reason=reason,
827
+ details=details,
828
+ sensitive=sensitive,
829
+ )
830
+ )
831
+ # Flip the config-block flag when a conflict/error occurs on a
832
+ # config.yaml write. Later config-mutating options will skip rather
833
+ # than attempting a partial write.
834
+ if status in (STATUS_CONFLICT, STATUS_ERROR) and destination is not None:
835
+ dest_str = str(destination)
836
+ if dest_str.endswith("config.yaml") or dest_str.endswith("config.yml"):
837
+ self._config_apply_blocked = True
838
+
839
+ def source_candidate(self, *relative_paths: str) -> Optional[Path]:
840
+ for rel in relative_paths:
841
+ candidate = self.source_root / rel
842
+ if candidate.exists():
843
+ return candidate
844
+ # OpenClaw renamed workspace/ to workspace-main/ (and workspace-{agentId}
845
+ # for multi-agent). Try the new path as a fallback.
846
+ if rel.startswith("workspace/"):
847
+ suffix = rel[len("workspace/"):]
848
+ for variant in ("workspace-main", "workspace-assistant"):
849
+ alt = self.source_root / variant / suffix
850
+ if alt.exists():
851
+ return alt
852
+ elif rel.startswith("workspace.default/"):
853
+ suffix = rel[len("workspace.default/"):]
854
+ alt = self.source_root / "workspace-main" / suffix
855
+ if alt.exists():
856
+ return alt
857
+
858
+ # Final fallback: check the configured workspace directory from
859
+ # agents.defaults.workspace in openclaw.json. Users who started
860
+ # before the OpenClaw rebrand (when the project was named clawd /
861
+ # clawdbot) often have a custom workspace path outside ~/.openclaw/.
862
+ if self._custom_workspace:
863
+ for rel in relative_paths:
864
+ # Strip the leading "workspace/" or "workspace.default/"
865
+ # prefix to get the bare filename/subpath.
866
+ for prefix in ("workspace/", "workspace.default/"):
867
+ if rel.startswith(prefix):
868
+ suffix = rel[len(prefix):]
869
+ alt = self._custom_workspace / suffix
870
+ if alt.exists():
871
+ return alt
872
+ break
873
+
874
+ return None
875
+
876
+ def resolve_skill_destination(self, destination: Path) -> Path:
877
+ if self.skill_conflict_mode != "rename" or not destination.exists():
878
+ return destination
879
+
880
+ suffix = "-imported"
881
+ candidate = destination.with_name(destination.name + suffix)
882
+ counter = 2
883
+ while candidate.exists():
884
+ candidate = destination.with_name(f"{destination.name}{suffix}-{counter}")
885
+ counter += 1
886
+ return candidate
887
+
888
+ def migrate(self) -> Dict[str, Any]:
889
+ if not self.source_root.exists():
890
+ self.record("source", self.source_root, None, "error", "OpenClaw directory does not exist")
891
+ return self.build_report()
892
+
893
+ config = self.load_openclaw_config()
894
+
895
+ self.run_if_selected("soul", self.migrate_soul)
896
+ self.run_if_selected("workspace-agents", self.migrate_workspace_agents)
897
+ self.run_if_selected(
898
+ "memory",
899
+ lambda: self.migrate_memory(
900
+ self.source_candidate("workspace/MEMORY.md", "workspace.default/MEMORY.md"),
901
+ self.target_root / "memories" / "MEMORY.md",
902
+ self.memory_limit,
903
+ kind="memory",
904
+ ),
905
+ )
906
+ self.run_if_selected(
907
+ "user-profile",
908
+ lambda: self.migrate_memory(
909
+ self.source_candidate("workspace/USER.md", "workspace.default/USER.md"),
910
+ self.target_root / "memories" / "USER.md",
911
+ self.user_limit,
912
+ kind="user-profile",
913
+ ),
914
+ )
915
+ self.run_if_selected("messaging-settings", lambda: self.migrate_messaging_settings(config))
916
+ self.run_if_selected("secret-settings", lambda: self.handle_secret_settings(config))
917
+ self.run_if_selected("discord-settings", lambda: self.migrate_discord_settings(config))
918
+ self.run_if_selected("slack-settings", lambda: self.migrate_slack_settings(config))
919
+ self.run_if_selected("whatsapp-settings", lambda: self.migrate_whatsapp_settings(config))
920
+ self.run_if_selected("signal-settings", lambda: self.migrate_signal_settings(config))
921
+ self.run_if_selected("provider-keys", lambda: self.handle_provider_keys(config))
922
+ self.run_if_selected("model-config", lambda: self.migrate_model_config(config))
923
+ self.run_if_selected("tts-config", lambda: self.migrate_tts_config(config))
924
+ self.run_if_selected("command-allowlist", self.migrate_command_allowlist)
925
+ self.run_if_selected("skills", self.migrate_skills)
926
+ self.run_if_selected("shared-skills", self.migrate_shared_skills)
927
+ self.run_if_selected("daily-memory", self.migrate_daily_memory)
928
+ self.run_if_selected(
929
+ "tts-assets",
930
+ lambda: self.copy_tree_non_destructive(
931
+ self.source_candidate("workspace/tts"),
932
+ self.target_root / "tts",
933
+ kind="tts-assets",
934
+ ignore_dir_names={".venv", "generated", "__pycache__"},
935
+ ),
936
+ )
937
+ self.run_if_selected("archive", self.archive_docs)
938
+
939
+ # ── v2 migration modules ──────────────────────────────
940
+ self.run_if_selected("mcp-servers", lambda: self.migrate_mcp_servers(config))
941
+ self.run_if_selected("plugins-config", lambda: self.migrate_plugins_config(config))
942
+ self.run_if_selected("cron-jobs", lambda: self.migrate_cron_jobs(config))
943
+ self.run_if_selected("hooks-config", lambda: self.migrate_hooks_config(config))
944
+ self.run_if_selected("agent-config", lambda: self.migrate_agent_config(config))
945
+ self.run_if_selected("gateway-config", lambda: self.migrate_gateway_config(config))
946
+ self.run_if_selected("session-config", lambda: self.migrate_session_config(config))
947
+ self.run_if_selected("full-providers", lambda: self.migrate_full_providers(config))
948
+ self.run_if_selected("deep-channels", lambda: self.migrate_deep_channels(config))
949
+ self.run_if_selected("browser-config", lambda: self.migrate_browser_config(config))
950
+ self.run_if_selected("tools-config", lambda: self.migrate_tools_config(config))
951
+ self.run_if_selected("approvals-config", lambda: self.migrate_approvals_config(config))
952
+ self.run_if_selected("memory-backend", lambda: self.migrate_memory_backend(config))
953
+ self.run_if_selected("skills-config", lambda: self.migrate_skills_config(config))
954
+ self.run_if_selected("ui-identity", lambda: self.migrate_ui_identity(config))
955
+ self.run_if_selected("logging-config", lambda: self.migrate_logging_config(config))
956
+
957
+ # Generate migration notes
958
+ self.generate_migration_notes()
959
+
960
+ return self.build_report()
961
+
962
+ def run_if_selected(self, option_id: str, func) -> None:
963
+ if not self.is_selected(option_id):
964
+ meta = MIGRATION_OPTION_METADATA[option_id]
965
+ self.record(option_id, None, None, "skipped", "Not selected for this run", option_label=meta["label"])
966
+ return
967
+ # If a previous config.yaml write hit a conflict/error during apply,
968
+ # skip remaining config-mutating options rather than risk a partial
969
+ # write. Dry-run mode never blocks — the user needs the full preview
970
+ # to decide how to proceed (re-run with --overwrite, etc.).
971
+ if (
972
+ self.execute
973
+ and self._config_apply_blocked
974
+ and option_id in self._CONFIG_MUTATING_OPTIONS
975
+ ):
976
+ meta = MIGRATION_OPTION_METADATA[option_id]
977
+ self.record(
978
+ option_id,
979
+ None,
980
+ None,
981
+ STATUS_SKIPPED,
982
+ REASON_BLOCKED_BY_APPLY_CONFLICT,
983
+ option_label=meta["label"],
984
+ )
985
+ return
986
+ func()
987
+
988
+ def build_report(self) -> Dict[str, Any]:
989
+ summary: Dict[str, int] = {
990
+ "migrated": 0,
991
+ "archived": 0,
992
+ "skipped": 0,
993
+ "conflict": 0,
994
+ "error": 0,
995
+ }
996
+ for item in self.items:
997
+ summary[item.status] = summary.get(item.status, 0) + 1
998
+
999
+ report = {
1000
+ "timestamp": self.timestamp,
1001
+ "mode": "execute" if self.execute else "dry-run",
1002
+ "source_root": str(self.source_root),
1003
+ "target_root": str(self.target_root),
1004
+ "workspace_target": str(self.workspace_target) if self.workspace_target else None,
1005
+ "output_dir": str(self.output_dir) if self.output_dir else None,
1006
+ "migrate_secrets": self.migrate_secrets,
1007
+ "preset": self.preset_name or None,
1008
+ "skill_conflict_mode": self.skill_conflict_mode,
1009
+ "selection": {
1010
+ "selected": sorted(self.selected_options),
1011
+ "preset": self.preset_name or None,
1012
+ "skill_conflict_mode": self.skill_conflict_mode,
1013
+ "available": [
1014
+ {"id": option_id, **meta}
1015
+ for option_id, meta in MIGRATION_OPTION_METADATA.items()
1016
+ ],
1017
+ "presets": [
1018
+ {"id": preset_id, "selected": sorted(option_ids)}
1019
+ for preset_id, option_ids in MIGRATION_PRESETS.items()
1020
+ ],
1021
+ },
1022
+ "summary": summary,
1023
+ "items": [asdict(item) for item in self.items],
1024
+ "warnings": self._build_warnings(summary),
1025
+ "next_steps": self._build_next_steps(summary),
1026
+ }
1027
+
1028
+ if self.output_dir:
1029
+ write_report(self.output_dir, report)
1030
+
1031
+ return report
1032
+
1033
+ def _build_warnings(self, summary: Dict[str, int]) -> List[str]:
1034
+ """Structured warnings surfaced on the report for downstream consumers.
1035
+
1036
+ Modelled on OpenClaw's extensions/migrate-hermes/plan.ts warnings[].
1037
+ Keep the messages actionable — they show up in summary.md and the
1038
+ JSON report.
1039
+ """
1040
+ warnings: List[str] = []
1041
+ if summary.get("conflict", 0) > 0:
1042
+ warnings.append(
1043
+ "Conflicts were found. Re-run with --overwrite to replace conflicting "
1044
+ "targets after item-level backups."
1045
+ )
1046
+ if summary.get("error", 0) > 0:
1047
+ warnings.append(
1048
+ "One or more items failed. Inspect the report and re-run after fixing "
1049
+ "the underlying cause."
1050
+ )
1051
+ if self._config_apply_blocked and self.execute:
1052
+ warnings.append(
1053
+ "A config.yaml write hit a conflict or error mid-apply; later config "
1054
+ "items were skipped to avoid a partial write."
1055
+ )
1056
+ # Detect whether secrets were detected but not migrated.
1057
+ provider_keys_skipped = any(
1058
+ item.kind == "provider-keys" and item.status == STATUS_SKIPPED
1059
+ for item in self.items
1060
+ )
1061
+ if provider_keys_skipped and not self.migrate_secrets:
1062
+ warnings.append(
1063
+ "API keys and other credentials were detected but not imported. "
1064
+ "Re-run with --migrate-secrets to copy supported keys into the "
1065
+ "Hermes env file."
1066
+ )
1067
+ return warnings
1068
+
1069
+ def _build_next_steps(self, summary: Dict[str, int]) -> List[str]:
1070
+ """Human-readable next-step guidance baked into the report."""
1071
+ if not self.execute:
1072
+ return [
1073
+ "Re-run without --dry-run to apply the migration.",
1074
+ "Pass --overwrite to resolve conflicts, or --migrate-secrets to "
1075
+ "include API keys.",
1076
+ ]
1077
+ steps: List[str] = []
1078
+ if summary.get("migrated", 0) > 0:
1079
+ steps.append(
1080
+ "Review the migration report at "
1081
+ f"{self.output_dir}/summary.md"
1082
+ if self.output_dir
1083
+ else "Review the migration report."
1084
+ )
1085
+ steps.append(
1086
+ "Start a new Hermes session (or /reset) to pick up the imported config."
1087
+ )
1088
+ if summary.get("conflict", 0) > 0:
1089
+ steps.append(
1090
+ "Re-run with --overwrite to apply items that were blocked by conflicts."
1091
+ )
1092
+ return steps
1093
+
1094
+ def maybe_backup(self, path: Path) -> Optional[Path]:
1095
+ if not self.execute or not self.backup_dir or not path.exists():
1096
+ return None
1097
+ return backup_existing(path, self.backup_dir)
1098
+
1099
+ def write_overflow_entries(self, kind: str, entries: Sequence[str]) -> Optional[Path]:
1100
+ if not entries or not self.overflow_dir:
1101
+ return None
1102
+ self.overflow_dir.mkdir(parents=True, exist_ok=True)
1103
+ filename = f"{kind.replace('-', '_')}_overflow.txt"
1104
+ path = self.overflow_dir / filename
1105
+ path.write_text("\n".join(entries) + "\n", encoding="utf-8")
1106
+ return path
1107
+
1108
+ def copy_file(self, source: Path, destination: Path, kind: str,
1109
+ transform: Optional[Any] = None) -> None:
1110
+ if not source or not source.exists():
1111
+ return
1112
+
1113
+ if destination.exists():
1114
+ if not transform and sha256_file(source) == sha256_file(destination):
1115
+ self.record(kind, source, destination, "skipped", "Target already matches source")
1116
+ return
1117
+ if not self.overwrite:
1118
+ self.record(kind, source, destination, "conflict", "Target exists and overwrite is disabled")
1119
+ return
1120
+
1121
+ if self.execute:
1122
+ backup_path = self.maybe_backup(destination)
1123
+ ensure_parent(destination)
1124
+ if transform:
1125
+ content = read_text(source)
1126
+ content = transform(content)
1127
+ destination.write_text(content, encoding="utf-8")
1128
+ shutil.copystat(source, destination)
1129
+ else:
1130
+ shutil.copy2(source, destination)
1131
+ self.record(kind, source, destination, "migrated", backup=str(backup_path) if backup_path else None)
1132
+ else:
1133
+ self.record(kind, source, destination, "migrated", "Would copy")
1134
+
1135
+ def migrate_soul(self) -> None:
1136
+ source = self.source_candidate("workspace/SOUL.md", "workspace.default/SOUL.md")
1137
+ if not source:
1138
+ self.record("soul", None, self.target_root / "SOUL.md", "skipped", "No OpenClaw SOUL.md found")
1139
+ return
1140
+ self.copy_file(source, self.target_root / "SOUL.md", kind="soul", transform=rebrand_text)
1141
+
1142
+ def migrate_workspace_agents(self) -> None:
1143
+ source = self.source_candidate(
1144
+ f"workspace/{WORKSPACE_INSTRUCTIONS_FILENAME}",
1145
+ f"workspace.default/{WORKSPACE_INSTRUCTIONS_FILENAME}",
1146
+ )
1147
+ if source is None:
1148
+ self.record("workspace-agents", "workspace/AGENTS.md", "", "skipped", "Source file not found")
1149
+ return
1150
+ if not self.workspace_target:
1151
+ self.record("workspace-agents", source, None, "skipped", "No workspace target was provided")
1152
+ return
1153
+ destination = self.workspace_target / WORKSPACE_INSTRUCTIONS_FILENAME
1154
+ self.copy_file(source, destination, kind="workspace-agents", transform=rebrand_text)
1155
+
1156
+ def migrate_memory(self, source: Optional[Path], destination: Path, limit: int, kind: str) -> None:
1157
+ if not source or not source.exists():
1158
+ self.record(kind, None, destination, "skipped", "Source file not found")
1159
+ return
1160
+
1161
+ incoming = extract_markdown_entries(read_text(source))
1162
+ if not incoming:
1163
+ self.record(kind, source, destination, "skipped", "No importable entries found")
1164
+ return
1165
+ incoming = [rebrand_text(entry) for entry in incoming]
1166
+
1167
+ existing = parse_existing_memory_entries(destination)
1168
+ merged, stats, overflowed = merge_entries(existing, incoming, limit)
1169
+ details = {
1170
+ "existing_entries": stats["existing"],
1171
+ "added_entries": stats["added"],
1172
+ "duplicate_entries": stats["duplicates"],
1173
+ "overflowed_entries": stats["overflowed"],
1174
+ "char_limit": limit,
1175
+ "final_char_count": len(ENTRY_DELIMITER.join(merged)) if merged else 0,
1176
+ }
1177
+ overflow_file = self.write_overflow_entries(kind, overflowed)
1178
+ if overflow_file is not None:
1179
+ details["overflow_file"] = str(overflow_file)
1180
+
1181
+ if self.execute:
1182
+ if stats["added"] == 0 and not overflowed:
1183
+ self.record(kind, source, destination, "skipped", "No new entries to import", **details)
1184
+ return
1185
+ backup_path = self.maybe_backup(destination)
1186
+ ensure_parent(destination)
1187
+ destination.write_text(ENTRY_DELIMITER.join(merged) + ("\n" if merged else ""), encoding="utf-8")
1188
+ self.record(
1189
+ kind,
1190
+ source,
1191
+ destination,
1192
+ "migrated",
1193
+ backup=str(backup_path) if backup_path else "",
1194
+ overflow_preview=overflowed[:5],
1195
+ **details,
1196
+ )
1197
+ else:
1198
+ self.record(kind, source, destination, "migrated", "Would merge entries", overflow_preview=overflowed[:5], **details)
1199
+
1200
+ def migrate_command_allowlist(self) -> None:
1201
+ source = self.source_root / "exec-approvals.json"
1202
+ destination = self.target_root / "config.yaml"
1203
+ if not source.exists():
1204
+ self.record("command-allowlist", None, destination, "skipped", "No OpenClaw exec approvals file found")
1205
+ return
1206
+ if yaml is None:
1207
+ self.record("command-allowlist", source, destination, "error", "PyYAML is not available")
1208
+ return
1209
+
1210
+ try:
1211
+ data = json.loads(source.read_text(encoding="utf-8"))
1212
+ except json.JSONDecodeError as exc:
1213
+ self.record("command-allowlist", source, destination, "error", f"Invalid JSON: {exc}")
1214
+ return
1215
+
1216
+ patterns: List[str] = []
1217
+ agents = data.get("agents", {})
1218
+ if isinstance(agents, dict):
1219
+ for agent_data in agents.values():
1220
+ allowlist = agent_data.get("allowlist", []) if isinstance(agent_data, dict) else []
1221
+ for entry in allowlist:
1222
+ pattern = entry.get("pattern") if isinstance(entry, dict) else None
1223
+ if pattern:
1224
+ patterns.append(pattern)
1225
+
1226
+ patterns = sorted(dict.fromkeys(patterns))
1227
+ if not patterns:
1228
+ self.record("command-allowlist", source, destination, "skipped", "No allowlist patterns found")
1229
+ return
1230
+ if not destination.exists():
1231
+ self.record("command-allowlist", source, destination, "skipped", "Hermes config.yaml does not exist yet")
1232
+ return
1233
+
1234
+ config = load_yaml_file(destination)
1235
+ current = config.get("command_allowlist", [])
1236
+ if not isinstance(current, list):
1237
+ current = []
1238
+ merged = sorted(dict.fromkeys(list(current) + patterns))
1239
+ added = [pattern for pattern in merged if pattern not in current]
1240
+ if not added:
1241
+ self.record("command-allowlist", source, destination, "skipped", "All patterns already present")
1242
+ return
1243
+
1244
+ if self.execute:
1245
+ backup_path = self.maybe_backup(destination)
1246
+ config["command_allowlist"] = merged
1247
+ dump_yaml_file(destination, config)
1248
+ self.record(
1249
+ "command-allowlist",
1250
+ source,
1251
+ destination,
1252
+ "migrated",
1253
+ backup=str(backup_path) if backup_path else "",
1254
+ added_patterns=added,
1255
+ )
1256
+ else:
1257
+ self.record("command-allowlist", source, destination, "migrated", "Would merge patterns", added_patterns=added)
1258
+
1259
+ def load_openclaw_config(self) -> Dict[str, Any]:
1260
+ # Check current name and legacy config filenames
1261
+ for name in ("openclaw.json", "clawdbot.json", "moltbot.json"):
1262
+ config_path = self.source_root / name
1263
+ if config_path.exists():
1264
+ try:
1265
+ data = json.loads(config_path.read_text(encoding="utf-8"))
1266
+ return data if isinstance(data, dict) else {}
1267
+ except json.JSONDecodeError:
1268
+ continue
1269
+ return {}
1270
+
1271
+ def load_openclaw_env(self) -> Dict[str, str]:
1272
+ """Load the OpenClaw .env file for secrets that live there instead of config."""
1273
+ return parse_env_file(self.source_root / ".env")
1274
+
1275
+ def merge_env_values(self, additions: Dict[str, str], kind: str, source: Path) -> None:
1276
+ destination = self.target_root / ".env"
1277
+ env_data = parse_env_file(destination)
1278
+ added: Dict[str, str] = {}
1279
+ conflicts: List[str] = []
1280
+
1281
+ for key, value in additions.items():
1282
+ current = env_data.get(key)
1283
+ if current == value:
1284
+ continue
1285
+ if current and not self.overwrite:
1286
+ conflicts.append(key)
1287
+ continue
1288
+ env_data[key] = value
1289
+ added[key] = value
1290
+
1291
+ if conflicts and not added:
1292
+ self.record(kind, source, destination, "conflict", "Destination .env already has different values", conflicting_keys=conflicts)
1293
+ return
1294
+ if not conflicts and not added:
1295
+ self.record(kind, source, destination, "skipped", "All env values already present")
1296
+ return
1297
+
1298
+ if self.execute:
1299
+ backup_path = self.maybe_backup(destination)
1300
+ save_env_file(destination, env_data)
1301
+ self.record(
1302
+ kind,
1303
+ source,
1304
+ destination,
1305
+ "migrated",
1306
+ backup=str(backup_path) if backup_path else "",
1307
+ added_keys=sorted(added.keys()),
1308
+ conflicting_keys=conflicts,
1309
+ )
1310
+ else:
1311
+ self.record(
1312
+ kind,
1313
+ source,
1314
+ destination,
1315
+ "migrated",
1316
+ "Would merge env values",
1317
+ added_keys=sorted(added.keys()),
1318
+ conflicting_keys=conflicts,
1319
+ )
1320
+
1321
+ def migrate_messaging_settings(self, config: Optional[Dict[str, Any]] = None) -> None:
1322
+ config = config or self.load_openclaw_config()
1323
+ additions: Dict[str, str] = {}
1324
+
1325
+ workspace = (
1326
+ config.get("agents", {})
1327
+ .get("defaults", {})
1328
+ .get("workspace")
1329
+ )
1330
+ if isinstance(workspace, str) and workspace.strip():
1331
+ ws_path = workspace.strip()
1332
+ # Skip if the workspace points inside the OpenClaw source directory —
1333
+ # that path will be stale after migration and would cause the Hermes
1334
+ # gateway to use the old OpenClaw workspace as its cwd, picking up
1335
+ # OpenClaw's AGENTS.md, MEMORY.md, etc.
1336
+ try:
1337
+ inside_source = Path(ws_path).resolve().is_relative_to(self.source_root.resolve())
1338
+ except (ValueError, OSError):
1339
+ inside_source = False
1340
+ if not inside_source:
1341
+ additions["MESSAGING_CWD"] = ws_path
1342
+
1343
+ allowlist_path = self.source_root / "credentials" / "telegram-default-allowFrom.json"
1344
+ if allowlist_path.exists():
1345
+ try:
1346
+ allow_data = json.loads(allowlist_path.read_text(encoding="utf-8"))
1347
+ except json.JSONDecodeError:
1348
+ self.record("messaging-settings", allowlist_path, self.target_root / ".env", "error", "Invalid JSON in Telegram allowlist file")
1349
+ else:
1350
+ allow_from = allow_data.get("allowFrom", [])
1351
+ if isinstance(allow_from, list):
1352
+ users = [str(user).strip() for user in allow_from if str(user).strip()]
1353
+ if users:
1354
+ additions["TELEGRAM_ALLOWED_USERS"] = ",".join(users)
1355
+
1356
+ if additions:
1357
+ self.merge_env_values(additions, "messaging-settings", self.source_root / "openclaw.json")
1358
+ else:
1359
+ self.record("messaging-settings", self.source_root / "openclaw.json", self.target_root / ".env", "skipped", "No Hermes-compatible messaging settings found")
1360
+
1361
+ def handle_secret_settings(self, config: Optional[Dict[str, Any]] = None) -> None:
1362
+ config = config or self.load_openclaw_config()
1363
+ if self.migrate_secrets:
1364
+ self.migrate_secret_settings(config)
1365
+ return
1366
+
1367
+ config_path = self.source_root / "openclaw.json"
1368
+ if config_path.exists():
1369
+ self.record(
1370
+ "secret-settings",
1371
+ config_path,
1372
+ self.target_root / ".env",
1373
+ "skipped",
1374
+ "Secret migration disabled. Re-run with --migrate-secrets to import allowlisted secrets.",
1375
+ supported_targets=sorted(SUPPORTED_SECRET_TARGETS),
1376
+ )
1377
+ else:
1378
+ self.record(
1379
+ "secret-settings",
1380
+ config_path,
1381
+ self.target_root / ".env",
1382
+ "skipped",
1383
+ "OpenClaw config file not found",
1384
+ supported_targets=sorted(SUPPORTED_SECRET_TARGETS),
1385
+ )
1386
+
1387
+ def migrate_secret_settings(self, config: Dict[str, Any]) -> None:
1388
+ secret_additions: Dict[str, str] = {}
1389
+
1390
+ tg_cfg = config.get("channels", {}).get("telegram", {})
1391
+ telegram_token = self._get_channel_field(tg_cfg, "botToken") if isinstance(tg_cfg, dict) else None
1392
+ if isinstance(telegram_token, str) and telegram_token.strip():
1393
+ secret_additions["TELEGRAM_BOT_TOKEN"] = telegram_token.strip()
1394
+
1395
+ if secret_additions:
1396
+ self.merge_env_values(secret_additions, "secret-settings", self.source_root / "openclaw.json")
1397
+ else:
1398
+ self.record(
1399
+ "secret-settings",
1400
+ self.source_root / "openclaw.json",
1401
+ self.target_root / ".env",
1402
+ "skipped",
1403
+ "No allowlisted Hermes-compatible secrets found",
1404
+ supported_targets=sorted(SUPPORTED_SECRET_TARGETS),
1405
+ )
1406
+
1407
+ def _resolve_channel_secret(self, value: Any) -> Optional[str]:
1408
+ """Resolve a channel config value that may be a SecretRef."""
1409
+ return resolve_secret_input(value, self.load_openclaw_env())
1410
+
1411
+ @staticmethod
1412
+ def _get_channel_field(ch_cfg: Dict[str, Any], field: str) -> Any:
1413
+ """Get a field from channel config, checking both flat and accounts.default layout."""
1414
+ val = ch_cfg.get(field)
1415
+ if val is not None:
1416
+ return val
1417
+ accounts = ch_cfg.get("accounts")
1418
+ if isinstance(accounts, dict):
1419
+ default = accounts.get("default")
1420
+ if isinstance(default, dict):
1421
+ return default.get(field)
1422
+ return None
1423
+
1424
+ def migrate_discord_settings(self, config: Optional[Dict[str, Any]] = None) -> None:
1425
+ config = config or self.load_openclaw_config()
1426
+ additions: Dict[str, str] = {}
1427
+ discord = config.get("channels", {}).get("discord", {})
1428
+ if isinstance(discord, dict):
1429
+ token = self._get_channel_field(discord, "token")
1430
+ if isinstance(token, str) and token.strip():
1431
+ additions["DISCORD_BOT_TOKEN"] = token.strip()
1432
+ allow_from = self._get_channel_field(discord, "allowFrom") or []
1433
+ if isinstance(allow_from, list):
1434
+ users = [str(u).strip() for u in allow_from if str(u).strip()]
1435
+ if users:
1436
+ additions["DISCORD_ALLOWED_USERS"] = ",".join(users)
1437
+ if additions:
1438
+ self.merge_env_values(additions, "discord-settings", self.source_root / "openclaw.json")
1439
+ else:
1440
+ self.record("discord-settings", self.source_root / "openclaw.json", self.target_root / ".env", "skipped", "No Discord settings found")
1441
+
1442
+ def migrate_slack_settings(self, config: Optional[Dict[str, Any]] = None) -> None:
1443
+ config = config or self.load_openclaw_config()
1444
+ additions: Dict[str, str] = {}
1445
+ slack = config.get("channels", {}).get("slack", {})
1446
+ if isinstance(slack, dict):
1447
+ bot_token = self._get_channel_field(slack, "botToken")
1448
+ if isinstance(bot_token, str) and bot_token.strip():
1449
+ additions["SLACK_BOT_TOKEN"] = bot_token.strip()
1450
+ app_token = self._get_channel_field(slack, "appToken")
1451
+ if isinstance(app_token, str) and app_token.strip():
1452
+ additions["SLACK_APP_TOKEN"] = app_token.strip()
1453
+ allow_from = self._get_channel_field(slack, "allowFrom") or []
1454
+ if isinstance(allow_from, list):
1455
+ users = [str(u).strip() for u in allow_from if str(u).strip()]
1456
+ if users:
1457
+ additions["SLACK_ALLOWED_USERS"] = ",".join(users)
1458
+ if additions:
1459
+ self.merge_env_values(additions, "slack-settings", self.source_root / "openclaw.json")
1460
+ else:
1461
+ self.record("slack-settings", self.source_root / "openclaw.json", self.target_root / ".env", "skipped", "No Slack settings found")
1462
+
1463
+ def migrate_whatsapp_settings(self, config: Optional[Dict[str, Any]] = None) -> None:
1464
+ config = config or self.load_openclaw_config()
1465
+ additions: Dict[str, str] = {}
1466
+ whatsapp = config.get("channels", {}).get("whatsapp", {})
1467
+ if isinstance(whatsapp, dict):
1468
+ allow_from = self._get_channel_field(whatsapp, "allowFrom") or []
1469
+ if isinstance(allow_from, list):
1470
+ users = [str(u).strip() for u in allow_from if str(u).strip()]
1471
+ if users:
1472
+ additions["WHATSAPP_ALLOWED_USERS"] = ",".join(users)
1473
+ if additions:
1474
+ self.merge_env_values(additions, "whatsapp-settings", self.source_root / "openclaw.json")
1475
+ else:
1476
+ self.record("whatsapp-settings", self.source_root / "openclaw.json", self.target_root / ".env", "skipped", "No WhatsApp settings found")
1477
+
1478
+ def migrate_signal_settings(self, config: Optional[Dict[str, Any]] = None) -> None:
1479
+ config = config or self.load_openclaw_config()
1480
+ additions: Dict[str, str] = {}
1481
+ signal = config.get("channels", {}).get("signal", {})
1482
+ if isinstance(signal, dict):
1483
+ account = self._get_channel_field(signal, "account")
1484
+ if isinstance(account, str) and account.strip():
1485
+ additions["SIGNAL_ACCOUNT"] = account.strip()
1486
+ http_url = self._get_channel_field(signal, "httpUrl")
1487
+ if isinstance(http_url, str) and http_url.strip():
1488
+ additions["SIGNAL_HTTP_URL"] = http_url.strip()
1489
+ allow_from = self._get_channel_field(signal, "allowFrom") or []
1490
+ if isinstance(allow_from, list):
1491
+ users = [str(u).strip() for u in allow_from if str(u).strip()]
1492
+ if users:
1493
+ additions["SIGNAL_ALLOWED_USERS"] = ",".join(users)
1494
+ if additions:
1495
+ self.merge_env_values(additions, "signal-settings", self.source_root / "openclaw.json")
1496
+ else:
1497
+ self.record("signal-settings", self.source_root / "openclaw.json", self.target_root / ".env", "skipped", "No Signal settings found")
1498
+
1499
+ def handle_provider_keys(self, config: Optional[Dict[str, Any]] = None) -> None:
1500
+ config = config or self.load_openclaw_config()
1501
+ if not self.migrate_secrets:
1502
+ config_path = self.source_root / "openclaw.json"
1503
+ self.record(
1504
+ "provider-keys",
1505
+ config_path,
1506
+ self.target_root / ".env",
1507
+ "skipped",
1508
+ "Secret migration disabled. Re-run with --migrate-secrets to import provider API keys.",
1509
+ supported_targets=sorted(SUPPORTED_SECRET_TARGETS),
1510
+ )
1511
+ return
1512
+ self.migrate_provider_keys(config)
1513
+
1514
+ def migrate_provider_keys(self, config: Dict[str, Any]) -> None:
1515
+ secret_additions: Dict[str, str] = {}
1516
+
1517
+ # Extract provider API keys from models.providers
1518
+ # Note: apiKey values can be strings, env templates, or SecretRef objects
1519
+ openclaw_env = self.load_openclaw_env()
1520
+ providers = config.get("models", {}).get("providers", {})
1521
+ if isinstance(providers, dict):
1522
+ for provider_name, provider_cfg in providers.items():
1523
+ if not isinstance(provider_cfg, dict):
1524
+ continue
1525
+ raw_key = provider_cfg.get("apiKey")
1526
+ api_key = resolve_secret_input(raw_key, openclaw_env)
1527
+ if not api_key:
1528
+ # Warn if a SecretRef with file/exec source was silently unresolvable
1529
+ if isinstance(raw_key, dict) and raw_key.get("source") in ("file", "exec"):
1530
+ self.record(
1531
+ "provider-keys",
1532
+ self.source_root / "openclaw.json",
1533
+ None,
1534
+ "skipped",
1535
+ f"Provider '{provider_name}' uses a {raw_key['source']}-backed SecretRef "
1536
+ f"that cannot be auto-migrated. Add this key manually via: hermes config set",
1537
+ )
1538
+ continue
1539
+
1540
+ base_url = provider_cfg.get("baseUrl", "")
1541
+ api_type = provider_cfg.get("api", "")
1542
+ env_var = None
1543
+
1544
+ # Match by baseUrl first
1545
+ if isinstance(base_url, str):
1546
+ if "openrouter" in base_url.lower():
1547
+ env_var = "OPENROUTER_API_KEY"
1548
+ elif "openai.com" in base_url.lower():
1549
+ env_var = "OPENAI_API_KEY"
1550
+ elif "anthropic" in base_url.lower():
1551
+ env_var = "ANTHROPIC_API_KEY"
1552
+
1553
+ # Match by api type
1554
+ if not env_var and isinstance(api_type, str) and api_type == "anthropic-messages":
1555
+ env_var = "ANTHROPIC_API_KEY"
1556
+
1557
+ # Match by provider name
1558
+ if not env_var:
1559
+ name_lower = provider_name.lower()
1560
+ if name_lower == "openrouter":
1561
+ env_var = "OPENROUTER_API_KEY"
1562
+ elif "openai" in name_lower:
1563
+ env_var = "OPENAI_API_KEY"
1564
+
1565
+ if env_var:
1566
+ secret_additions[env_var] = api_key
1567
+
1568
+ # Extract TTS API keys
1569
+ tts = config.get("messages", {}).get("tts", {})
1570
+ if isinstance(tts, dict):
1571
+ elevenlabs = tts.get("elevenlabs", {})
1572
+ if isinstance(elevenlabs, dict):
1573
+ el_key = elevenlabs.get("apiKey")
1574
+ if isinstance(el_key, str) and el_key.strip():
1575
+ secret_additions["ELEVENLABS_API_KEY"] = el_key.strip()
1576
+ openai_tts = tts.get("openai", {})
1577
+ if isinstance(openai_tts, dict):
1578
+ oai_key = openai_tts.get("apiKey")
1579
+ if isinstance(oai_key, str) and oai_key.strip():
1580
+ secret_additions["VOICE_TOOLS_OPENAI_KEY"] = oai_key.strip()
1581
+
1582
+ # Also check the OpenClaw .env file — many users store keys there
1583
+ # instead of inline in openclaw.json
1584
+ openclaw_env = self.load_openclaw_env()
1585
+ env_key_mapping = {
1586
+ "OPENROUTER_API_KEY": "OPENROUTER_API_KEY",
1587
+ "OPENAI_API_KEY": "OPENAI_API_KEY",
1588
+ "ANTHROPIC_API_KEY": "ANTHROPIC_API_KEY",
1589
+ "ELEVENLABS_API_KEY": "ELEVENLABS_API_KEY",
1590
+ "TELEGRAM_BOT_TOKEN": "TELEGRAM_BOT_TOKEN",
1591
+ "DEEPSEEK_API_KEY": "DEEPSEEK_API_KEY",
1592
+ "GEMINI_API_KEY": "GEMINI_API_KEY",
1593
+ "ZAI_API_KEY": "ZAI_API_KEY",
1594
+ "MINIMAX_API_KEY": "MINIMAX_API_KEY",
1595
+ }
1596
+ for oc_key, hermes_key in env_key_mapping.items():
1597
+ val = openclaw_env.get(oc_key, "").strip()
1598
+ if val and hermes_key not in secret_additions:
1599
+ secret_additions[hermes_key] = val
1600
+
1601
+ # Check the openclaw.json "env" sub-object — some OpenClaw setups
1602
+ # store API keys here instead of in a separate .env file.
1603
+ # Keys can be at env.<KEY> or env.vars.<KEY>.
1604
+ json_env = config.get("env")
1605
+ if isinstance(json_env, dict):
1606
+ env_vars = json_env.get("vars")
1607
+ sources = [json_env]
1608
+ if isinstance(env_vars, dict):
1609
+ sources.append(env_vars)
1610
+ for src in sources:
1611
+ for oc_key, hermes_key in env_key_mapping.items():
1612
+ val = src.get(oc_key)
1613
+ if isinstance(val, str) and val.strip() and hermes_key not in secret_additions:
1614
+ secret_additions[hermes_key] = val.strip()
1615
+
1616
+ # Check per-agent auth-profiles.json for additional credentials
1617
+ auth_profiles_path = self.source_root / "agents" / "main" / "agent" / "auth-profiles.json"
1618
+ if auth_profiles_path.exists():
1619
+ try:
1620
+ profiles = json.loads(auth_profiles_path.read_text(encoding="utf-8"))
1621
+ if isinstance(profiles, dict):
1622
+ # auth-profiles.json wraps profiles in a "profiles" key
1623
+ profile_entries = profiles.get("profiles", profiles) if isinstance(profiles.get("profiles"), dict) else profiles
1624
+ for profile_name, profile_data in profile_entries.items():
1625
+ if not isinstance(profile_data, dict):
1626
+ continue
1627
+ # Canonical field is "key", "apiKey" is accepted as alias
1628
+ api_key = profile_data.get("key", "") or profile_data.get("apiKey", "")
1629
+ if not isinstance(api_key, str) or not api_key.strip():
1630
+ continue
1631
+ name_lower = profile_name.lower()
1632
+ if "openrouter" in name_lower and "OPENROUTER_API_KEY" not in secret_additions:
1633
+ secret_additions["OPENROUTER_API_KEY"] = api_key.strip()
1634
+ elif "openai" in name_lower and "OPENAI_API_KEY" not in secret_additions:
1635
+ secret_additions["OPENAI_API_KEY"] = api_key.strip()
1636
+ elif "anthropic" in name_lower and "ANTHROPIC_API_KEY" not in secret_additions:
1637
+ secret_additions["ANTHROPIC_API_KEY"] = api_key.strip()
1638
+ except (json.JSONDecodeError, OSError):
1639
+ pass
1640
+
1641
+ if secret_additions:
1642
+ self.merge_env_values(secret_additions, "provider-keys", self.source_root / "openclaw.json")
1643
+ else:
1644
+ self.record(
1645
+ "provider-keys",
1646
+ self.source_root / "openclaw.json",
1647
+ self.target_root / ".env",
1648
+ "skipped",
1649
+ "No provider API keys found",
1650
+ supported_targets=sorted(SUPPORTED_SECRET_TARGETS),
1651
+ )
1652
+
1653
+ def migrate_model_config(self, config: Optional[Dict[str, Any]] = None) -> None:
1654
+ config = config or self.load_openclaw_config()
1655
+ destination = self.target_root / "config.yaml"
1656
+ source_path = self.source_root / "openclaw.json"
1657
+
1658
+ model_value = config.get("agents", {}).get("defaults", {}).get("model")
1659
+ if model_value is None:
1660
+ self.record("model-config", source_path, destination, "skipped", "No default model found in OpenClaw config")
1661
+ return
1662
+
1663
+ if isinstance(model_value, dict):
1664
+ model_str = model_value.get("primary")
1665
+ else:
1666
+ model_str = model_value
1667
+
1668
+ if not isinstance(model_str, str) or not model_str.strip():
1669
+ self.record("model-config", source_path, destination, "skipped", "Default model value is empty or invalid")
1670
+ return
1671
+
1672
+ model_str = model_str.strip()
1673
+
1674
+ # Resolve a model alias against the OpenClaw model catalog.
1675
+ # OpenClaw stores agents.defaults.model as either a bare string or
1676
+ # {"primary": "<value>"}, and that value can be either:
1677
+ # - a full provider/model API ID (e.g. "anthropic/claude-opus-4-6"), or
1678
+ # - a display alias (e.g. "Claude Opus 4.6") that maps to one.
1679
+ # The catalog at agents.defaults.models is keyed by the full
1680
+ # provider/model API ID with an "alias" field on the value, e.g.:
1681
+ # {"anthropic/claude-opus-4-6": {"alias": "Claude Opus 4.6"}}
1682
+ # If model_str matches an alias in the catalog, rewrite it to the
1683
+ # catalog key (the real API ID). If it's already an API ID or has
1684
+ # no catalog match, leave it alone and let downstream pass it through.
1685
+ model_catalog = config.get("agents", {}).get("defaults", {}).get("models", {})
1686
+ if isinstance(model_catalog, dict) and model_str not in model_catalog:
1687
+ for api_id, entry in model_catalog.items():
1688
+ if not isinstance(api_id, str):
1689
+ continue
1690
+ if isinstance(entry, dict) and entry.get("alias") == model_str:
1691
+ model_str = api_id
1692
+ break
1693
+ if isinstance(entry, str) and entry == model_str:
1694
+ model_str = api_id
1695
+ break
1696
+
1697
+ if yaml is None:
1698
+ self.record("model-config", source_path, destination, "error", "PyYAML is not available")
1699
+ return
1700
+
1701
+ hermes_config = load_yaml_file(destination)
1702
+ current_model = hermes_config.get("model")
1703
+ if current_model == model_str:
1704
+ self.record("model-config", source_path, destination, "skipped", "Model already set to the same value")
1705
+ return
1706
+ if current_model and not self.overwrite:
1707
+ self.record("model-config", source_path, destination, "conflict", "Model already set and overwrite is disabled", current=current_model, incoming=model_str)
1708
+ return
1709
+
1710
+ if self.execute:
1711
+ backup_path = self.maybe_backup(destination)
1712
+ existing_model = hermes_config.get("model")
1713
+ if isinstance(existing_model, dict):
1714
+ existing_model["default"] = model_str
1715
+ else:
1716
+ hermes_config["model"] = {"default": model_str}
1717
+ dump_yaml_file(destination, hermes_config)
1718
+ self.record("model-config", source_path, destination, "migrated", backup=str(backup_path) if backup_path else "", model=model_str)
1719
+ else:
1720
+ self.record("model-config", source_path, destination, "migrated", "Would set model", model=model_str)
1721
+
1722
+ def migrate_tts_config(self, config: Optional[Dict[str, Any]] = None) -> None:
1723
+ config = config or self.load_openclaw_config()
1724
+ destination = self.target_root / "config.yaml"
1725
+ source_path = self.source_root / "openclaw.json"
1726
+
1727
+ tts = config.get("messages", {}).get("tts", {})
1728
+ if not isinstance(tts, dict) or not tts:
1729
+ self.record("tts-config", source_path, destination, "skipped", "No TTS configuration found in OpenClaw config")
1730
+ return
1731
+
1732
+ if yaml is None:
1733
+ self.record("tts-config", source_path, destination, "error", "PyYAML is not available")
1734
+ return
1735
+
1736
+ tts_data: Dict[str, Any] = {}
1737
+
1738
+ provider = tts.get("provider")
1739
+ if isinstance(provider, str) and provider in ("elevenlabs", "openai", "edge", "microsoft"):
1740
+ # OpenClaw renamed "edge" to "microsoft"; Hermes still uses "edge"
1741
+ tts_data["provider"] = "edge" if provider == "microsoft" else provider
1742
+
1743
+ # TTS provider settings live under messages.tts.providers.{provider}
1744
+ # in OpenClaw (not messages.tts.elevenlabs directly)
1745
+ providers = tts.get("providers") or {}
1746
+
1747
+ # Also check the top-level "talk" config which has provider settings too
1748
+ talk_cfg = (config or self.load_openclaw_config()).get("talk") or {}
1749
+ talk_providers = talk_cfg.get("providers") or {}
1750
+
1751
+ # Merge: messages.tts.providers takes priority, then talk.providers,
1752
+ # then legacy flat keys (messages.tts.elevenlabs, etc.)
1753
+ elevenlabs = (
1754
+ (providers.get("elevenlabs") or {})
1755
+ if isinstance(providers.get("elevenlabs"), dict) else
1756
+ (talk_providers.get("elevenlabs") or {})
1757
+ if isinstance(talk_providers.get("elevenlabs"), dict) else
1758
+ (tts.get("elevenlabs") or {})
1759
+ )
1760
+ if isinstance(elevenlabs, dict):
1761
+ el_settings: Dict[str, str] = {}
1762
+ voice_id = elevenlabs.get("voiceId") or talk_cfg.get("voiceId")
1763
+ if isinstance(voice_id, str) and voice_id.strip():
1764
+ el_settings["voice_id"] = voice_id.strip()
1765
+ model_id = elevenlabs.get("modelId") or talk_cfg.get("modelId")
1766
+ if isinstance(model_id, str) and model_id.strip():
1767
+ el_settings["model_id"] = model_id.strip()
1768
+ if el_settings:
1769
+ tts_data["elevenlabs"] = el_settings
1770
+
1771
+ openai_tts = (
1772
+ (providers.get("openai") or {})
1773
+ if isinstance(providers.get("openai"), dict) else
1774
+ (talk_providers.get("openai") or {})
1775
+ if isinstance(talk_providers.get("openai"), dict) else
1776
+ (tts.get("openai") or {})
1777
+ )
1778
+ if isinstance(openai_tts, dict):
1779
+ oai_settings: Dict[str, str] = {}
1780
+ oai_model = openai_tts.get("model") or openai_tts.get("modelId")
1781
+ if isinstance(oai_model, str) and oai_model.strip():
1782
+ oai_settings["model"] = oai_model.strip()
1783
+ oai_voice = openai_tts.get("voice")
1784
+ if isinstance(oai_voice, str) and oai_voice.strip():
1785
+ oai_settings["voice"] = oai_voice.strip()
1786
+ if oai_settings:
1787
+ tts_data["openai"] = oai_settings
1788
+
1789
+ edge_tts = (
1790
+ (providers.get("edge") or providers.get("microsoft") or {})
1791
+ if isinstance(providers.get("edge"), dict) or isinstance(providers.get("microsoft"), dict) else
1792
+ (tts.get("edge") or tts.get("microsoft") or {})
1793
+ )
1794
+ if isinstance(edge_tts, dict):
1795
+ edge_voice = edge_tts.get("voice")
1796
+ if isinstance(edge_voice, str) and edge_voice.strip():
1797
+ tts_data["edge"] = {"voice": edge_voice.strip()}
1798
+
1799
+ if not tts_data:
1800
+ self.record("tts-config", source_path, destination, "skipped", "No compatible TTS settings found")
1801
+ return
1802
+
1803
+ hermes_config = load_yaml_file(destination)
1804
+ existing_tts = hermes_config.get("tts", {})
1805
+ if not isinstance(existing_tts, dict):
1806
+ existing_tts = {}
1807
+
1808
+ if self.execute:
1809
+ backup_path = self.maybe_backup(destination)
1810
+ merged_tts = dict(existing_tts)
1811
+ for key, value in tts_data.items():
1812
+ if isinstance(value, dict) and isinstance(merged_tts.get(key), dict):
1813
+ merged_tts[key] = {**merged_tts[key], **value}
1814
+ else:
1815
+ merged_tts[key] = value
1816
+ hermes_config["tts"] = merged_tts
1817
+ dump_yaml_file(destination, hermes_config)
1818
+ self.record("tts-config", source_path, destination, "migrated", backup=str(backup_path) if backup_path else "", settings=list(tts_data.keys()))
1819
+ else:
1820
+ self.record("tts-config", source_path, destination, "migrated", "Would set TTS config", settings=list(tts_data.keys()))
1821
+
1822
+ def migrate_shared_skills(self) -> None:
1823
+ # Check all OpenClaw skill sources: managed, personal, project-level
1824
+ skill_sources = [
1825
+ (self.source_root / "skills", "shared-skills", "managed skills"),
1826
+ (Path.home() / ".agents" / "skills", "personal-skills", "personal cross-project skills"),
1827
+ (self.source_root / "workspace" / ".agents" / "skills", "project-skills", "project-level shared skills"),
1828
+ (self.source_root / "workspace.default" / ".agents" / "skills", "project-skills", "project-level shared skills"),
1829
+ ]
1830
+ found_any = False
1831
+ for source_root, kind_label, desc in skill_sources:
1832
+ if source_root.exists():
1833
+ found_any = True
1834
+ self._import_skill_directory(source_root, kind_label, desc)
1835
+ if not found_any:
1836
+ destination_root = self.target_root / "skills" / SKILL_CATEGORY_DIRNAME
1837
+ self.record("shared-skills", None, destination_root, "skipped", "No shared OpenClaw skills directories found")
1838
+
1839
+ def _import_skill_directory(self, source_root: Path, kind_label: str, desc: str) -> None:
1840
+ """Import skills from a single source directory into openclaw-imports."""
1841
+ destination_root = self.target_root / "skills" / SKILL_CATEGORY_DIRNAME
1842
+
1843
+ skill_dirs = [p for p in sorted(source_root.iterdir()) if p.is_dir() and (p / "SKILL.md").exists()]
1844
+ if not skill_dirs:
1845
+ self.record(kind_label, source_root, destination_root, "skipped", f"No skills with SKILL.md found in {desc}")
1846
+ return
1847
+
1848
+ for skill_dir in skill_dirs:
1849
+ destination = destination_root / skill_dir.name
1850
+ final_destination = destination
1851
+ if destination.exists():
1852
+ if self.skill_conflict_mode == "skip":
1853
+ self.record(kind_label, skill_dir, destination, "conflict", "Destination skill already exists")
1854
+ continue
1855
+ if self.skill_conflict_mode == "rename":
1856
+ final_destination = self.resolve_skill_destination(destination)
1857
+ if self.execute:
1858
+ backup_path = None
1859
+ if final_destination == destination and destination.exists():
1860
+ backup_path = self.maybe_backup(destination)
1861
+ final_destination.parent.mkdir(parents=True, exist_ok=True)
1862
+ if final_destination == destination and destination.exists():
1863
+ shutil.rmtree(destination)
1864
+ shutil.copytree(skill_dir, final_destination)
1865
+ details: Dict[str, Any] = {"backup": str(backup_path) if backup_path else ""}
1866
+ if final_destination != destination:
1867
+ details["renamed_from"] = str(destination)
1868
+ self.record(kind_label, skill_dir, final_destination, "migrated", **details)
1869
+ else:
1870
+ if final_destination != destination:
1871
+ self.record(
1872
+ kind_label,
1873
+ skill_dir,
1874
+ final_destination,
1875
+ "migrated",
1876
+ f"Would copy {desc} directory under a renamed folder",
1877
+ renamed_from=str(destination),
1878
+ )
1879
+ else:
1880
+ self.record(kind_label, skill_dir, final_destination, "migrated", f"Would copy {desc} directory")
1881
+
1882
+ desc_path = destination_root / "DESCRIPTION.md"
1883
+ if self.execute:
1884
+ desc_path.parent.mkdir(parents=True, exist_ok=True)
1885
+ if not desc_path.exists():
1886
+ desc_path.write_text(SKILL_CATEGORY_DESCRIPTION + "\n", encoding="utf-8")
1887
+ elif not desc_path.exists():
1888
+ self.record("shared-skill-category", None, desc_path, "migrated", "Would create category description")
1889
+
1890
+ def migrate_daily_memory(self) -> None:
1891
+ source_dir = self.source_candidate("workspace/memory")
1892
+ destination = self.target_root / "memories" / "MEMORY.md"
1893
+ if not source_dir or not source_dir.is_dir():
1894
+ self.record("daily-memory", None, destination, "skipped", "No workspace/memory/ directory found")
1895
+ return
1896
+
1897
+ md_files = sorted(p for p in source_dir.iterdir() if p.is_file() and p.suffix == ".md")
1898
+ if not md_files:
1899
+ self.record("daily-memory", source_dir, destination, "skipped", "No .md files found in workspace/memory/")
1900
+ return
1901
+
1902
+ all_incoming: List[str] = []
1903
+ for md_file in md_files:
1904
+ entries = extract_markdown_entries(read_text(md_file))
1905
+ all_incoming.extend(entries)
1906
+
1907
+ if not all_incoming:
1908
+ self.record("daily-memory", source_dir, destination, "skipped", "No importable entries found in daily memory files")
1909
+ return
1910
+ all_incoming = [rebrand_text(entry) for entry in all_incoming]
1911
+
1912
+ existing = parse_existing_memory_entries(destination)
1913
+ merged, stats, overflowed = merge_entries(existing, all_incoming, self.memory_limit)
1914
+ details = {
1915
+ "source_files": len(md_files),
1916
+ "existing_entries": stats["existing"],
1917
+ "added_entries": stats["added"],
1918
+ "duplicate_entries": stats["duplicates"],
1919
+ "overflowed_entries": stats["overflowed"],
1920
+ "char_limit": self.memory_limit,
1921
+ "final_char_count": len(ENTRY_DELIMITER.join(merged)) if merged else 0,
1922
+ }
1923
+ overflow_file = self.write_overflow_entries("daily-memory", overflowed)
1924
+ if overflow_file is not None:
1925
+ details["overflow_file"] = str(overflow_file)
1926
+
1927
+ if self.execute:
1928
+ if stats["added"] == 0 and not overflowed:
1929
+ self.record("daily-memory", source_dir, destination, "skipped", "No new entries to import", **details)
1930
+ return
1931
+ backup_path = self.maybe_backup(destination)
1932
+ ensure_parent(destination)
1933
+ destination.write_text(ENTRY_DELIMITER.join(merged) + ("\n" if merged else ""), encoding="utf-8")
1934
+ self.record(
1935
+ "daily-memory",
1936
+ source_dir,
1937
+ destination,
1938
+ "migrated",
1939
+ backup=str(backup_path) if backup_path else "",
1940
+ overflow_preview=overflowed[:5],
1941
+ **details,
1942
+ )
1943
+ else:
1944
+ self.record("daily-memory", source_dir, destination, "migrated", "Would merge daily memory entries", overflow_preview=overflowed[:5], **details)
1945
+
1946
+ def migrate_skills(self) -> None:
1947
+ source_root = self.source_candidate("workspace/skills")
1948
+ destination_root = self.target_root / "skills" / SKILL_CATEGORY_DIRNAME
1949
+ if not source_root or not source_root.exists():
1950
+ self.record("skills", None, destination_root, "skipped", "No OpenClaw skills directory found")
1951
+ return
1952
+
1953
+ skill_dirs = [p for p in sorted(source_root.iterdir()) if p.is_dir() and (p / "SKILL.md").exists()]
1954
+ if not skill_dirs:
1955
+ self.record("skills", source_root, destination_root, "skipped", "No skills with SKILL.md found")
1956
+ return
1957
+
1958
+ for skill_dir in skill_dirs:
1959
+ destination = destination_root / skill_dir.name
1960
+ final_destination = destination
1961
+ if destination.exists():
1962
+ if self.skill_conflict_mode == "skip":
1963
+ self.record("skill", skill_dir, destination, "conflict", "Destination skill already exists")
1964
+ continue
1965
+ if self.skill_conflict_mode == "rename":
1966
+ final_destination = self.resolve_skill_destination(destination)
1967
+ if self.execute:
1968
+ backup_path = None
1969
+ if final_destination == destination and destination.exists():
1970
+ backup_path = self.maybe_backup(destination)
1971
+ final_destination.parent.mkdir(parents=True, exist_ok=True)
1972
+ if final_destination == destination and destination.exists():
1973
+ shutil.rmtree(destination)
1974
+ shutil.copytree(skill_dir, final_destination)
1975
+ details: Dict[str, Any] = {"backup": str(backup_path) if backup_path else ""}
1976
+ if final_destination != destination:
1977
+ details["renamed_from"] = str(destination)
1978
+ self.record("skill", skill_dir, final_destination, "migrated", **details)
1979
+ else:
1980
+ if final_destination != destination:
1981
+ self.record(
1982
+ "skill",
1983
+ skill_dir,
1984
+ final_destination,
1985
+ "migrated",
1986
+ "Would copy skill directory under a renamed folder",
1987
+ renamed_from=str(destination),
1988
+ )
1989
+ else:
1990
+ self.record("skill", skill_dir, final_destination, "migrated", "Would copy skill directory")
1991
+
1992
+ desc_path = destination_root / "DESCRIPTION.md"
1993
+ if self.execute:
1994
+ desc_path.parent.mkdir(parents=True, exist_ok=True)
1995
+ if not desc_path.exists():
1996
+ desc_path.write_text(SKILL_CATEGORY_DESCRIPTION + "\n", encoding="utf-8")
1997
+ elif not desc_path.exists():
1998
+ self.record("skill-category", None, desc_path, "migrated", "Would create category description")
1999
+
2000
+ def copy_tree_non_destructive(
2001
+ self,
2002
+ source_root: Optional[Path],
2003
+ destination_root: Path,
2004
+ kind: str,
2005
+ ignore_dir_names: Optional[set[str]] = None,
2006
+ ) -> None:
2007
+ if not source_root or not source_root.exists():
2008
+ self.record(kind, None, destination_root, "skipped", "Source directory not found")
2009
+ return
2010
+
2011
+ ignore_dir_names = ignore_dir_names or set()
2012
+ files = [
2013
+ p
2014
+ for p in source_root.rglob("*")
2015
+ if p.is_file() and not any(part in ignore_dir_names for part in p.relative_to(source_root).parts[:-1])
2016
+ ]
2017
+ if not files:
2018
+ self.record(kind, source_root, destination_root, "skipped", "No files found")
2019
+ return
2020
+
2021
+ copied = 0
2022
+ skipped = 0
2023
+ conflicts = 0
2024
+
2025
+ for source in files:
2026
+ rel = source.relative_to(source_root)
2027
+ destination = destination_root / rel
2028
+ if destination.exists():
2029
+ if sha256_file(source) == sha256_file(destination):
2030
+ skipped += 1
2031
+ continue
2032
+ if not self.overwrite:
2033
+ conflicts += 1
2034
+ self.record(kind, source, destination, "conflict", "Destination file already exists")
2035
+ continue
2036
+
2037
+ if self.execute:
2038
+ self.maybe_backup(destination)
2039
+ ensure_parent(destination)
2040
+ shutil.copy2(source, destination)
2041
+ copied += 1
2042
+
2043
+ status = "migrated" if copied else "skipped"
2044
+ reason = ""
2045
+ if not copied and conflicts:
2046
+ status = "conflict"
2047
+ reason = "All candidate files conflicted with existing destination files"
2048
+ elif not copied:
2049
+ reason = "No new files to copy"
2050
+
2051
+ self.record(kind, source_root, destination_root, status, reason, copied_files=copied, unchanged_files=skipped, conflicts=conflicts)
2052
+
2053
+ def archive_docs(self) -> None:
2054
+ candidates = [
2055
+ self.source_candidate("workspace/IDENTITY.md", "workspace.default/IDENTITY.md"),
2056
+ self.source_candidate("workspace/TOOLS.md", "workspace.default/TOOLS.md"),
2057
+ self.source_candidate("workspace/HEARTBEAT.md", "workspace.default/HEARTBEAT.md"),
2058
+ self.source_candidate("workspace/BOOTSTRAP.md", "workspace.default/BOOTSTRAP.md"),
2059
+ ]
2060
+ for candidate in candidates:
2061
+ if candidate:
2062
+ self.archive_path(candidate, reason="No direct Hermes destination; archived for manual review")
2063
+
2064
+ for rel in ("workspace/.learnings", "workspace/memory"):
2065
+ candidate = self.source_root / rel
2066
+ if candidate.exists():
2067
+ self.archive_path(candidate, reason="No direct Hermes destination; archived for manual review")
2068
+
2069
+ partially_extracted = [
2070
+ ("openclaw.json", "Selected Hermes-compatible values were extracted; raw OpenClaw config was not copied."),
2071
+ ("credentials/telegram-default-allowFrom.json", "Selected Hermes-compatible values were extracted; raw credentials file was not copied."),
2072
+ ]
2073
+ for rel, reason in partially_extracted:
2074
+ candidate = self.source_root / rel
2075
+ if candidate.exists():
2076
+ self.record("raw-config-skip", candidate, None, "skipped", reason)
2077
+
2078
+ skipped_sensitive = [
2079
+ "memory/main.sqlite",
2080
+ "credentials",
2081
+ "devices",
2082
+ "identity",
2083
+ "workspace.zip",
2084
+ ]
2085
+ for rel in skipped_sensitive:
2086
+ candidate = self.source_root / rel
2087
+ if candidate.exists():
2088
+ self.record("sensitive-skip", candidate, None, "skipped", "Contains secrets, binary state, or product-specific runtime data")
2089
+
2090
+ def archive_path(self, source: Path, reason: str) -> None:
2091
+ destination = self.archive_dir / relative_label(source, self.source_root) if self.archive_dir else None
2092
+ if self.execute and destination is not None:
2093
+ ensure_parent(destination)
2094
+ if source.is_dir():
2095
+ shutil.copytree(source, destination, dirs_exist_ok=True)
2096
+ else:
2097
+ shutil.copy2(source, destination)
2098
+ self.record("archive", source, destination, "archived", reason)
2099
+ else:
2100
+ self.record("archive", source, destination, "archived", reason)
2101
+
2102
+ # ── MCP servers ─────────────────────────────────────────────
2103
+ def migrate_mcp_servers(self, config: Optional[Dict[str, Any]] = None) -> None:
2104
+ config = config or self.load_openclaw_config()
2105
+ mcp_raw = (config.get("mcp") or {}).get("servers") or {}
2106
+ if not mcp_raw:
2107
+ self.record("mcp-servers", None, None, "skipped", "No MCP servers found in OpenClaw config")
2108
+ return
2109
+
2110
+ hermes_cfg_path = self.target_root / "config.yaml"
2111
+ hermes_cfg = load_yaml_file(hermes_cfg_path)
2112
+ existing_mcp = hermes_cfg.get("mcp_servers") or {}
2113
+ added = 0
2114
+
2115
+ for name, srv in mcp_raw.items():
2116
+ if not isinstance(srv, dict):
2117
+ continue
2118
+ if name in existing_mcp and not self.overwrite:
2119
+ self.record("mcp-servers", f"mcp.servers.{name}", f"mcp_servers.{name}", "conflict",
2120
+ "MCP server already exists in Hermes config")
2121
+ continue
2122
+
2123
+ hermes_srv: Dict[str, Any] = {}
2124
+ # STDIO transport
2125
+ if srv.get("command"):
2126
+ hermes_srv["command"] = srv["command"]
2127
+ if srv.get("args"):
2128
+ hermes_srv["args"] = srv["args"]
2129
+ if srv.get("env"):
2130
+ hermes_srv["env"] = srv["env"]
2131
+ if srv.get("cwd"):
2132
+ hermes_srv["cwd"] = srv["cwd"]
2133
+ # HTTP/SSE transport
2134
+ if srv.get("url"):
2135
+ hermes_srv["url"] = srv["url"]
2136
+ if srv.get("headers"):
2137
+ hermes_srv["headers"] = srv["headers"]
2138
+ if srv.get("auth"):
2139
+ hermes_srv["auth"] = srv["auth"]
2140
+ # Common fields
2141
+ if srv.get("enabled") is False:
2142
+ hermes_srv["enabled"] = False
2143
+ if srv.get("timeout"):
2144
+ hermes_srv["timeout"] = srv["timeout"]
2145
+ if srv.get("connectTimeout"):
2146
+ hermes_srv["connect_timeout"] = srv["connectTimeout"]
2147
+ # Tool filtering
2148
+ tools_cfg = srv.get("tools") or {}
2149
+ if tools_cfg.get("include") or tools_cfg.get("exclude"):
2150
+ hermes_srv["tools"] = {}
2151
+ if tools_cfg.get("include"):
2152
+ hermes_srv["tools"]["include"] = tools_cfg["include"]
2153
+ if tools_cfg.get("exclude"):
2154
+ hermes_srv["tools"]["exclude"] = tools_cfg["exclude"]
2155
+ # Sampling
2156
+ sampling = srv.get("sampling")
2157
+ if sampling and isinstance(sampling, dict):
2158
+ hermes_srv["sampling"] = {
2159
+ k: v for k, v in {
2160
+ "enabled": sampling.get("enabled"),
2161
+ "model": sampling.get("model"),
2162
+ "max_tokens_cap": sampling.get("maxTokensCap") or sampling.get("max_tokens_cap"),
2163
+ "timeout": sampling.get("timeout"),
2164
+ "max_rpm": sampling.get("maxRpm") or sampling.get("max_rpm"),
2165
+ }.items() if v is not None
2166
+ }
2167
+
2168
+ existing_mcp[name] = hermes_srv
2169
+ added += 1
2170
+ self.record("mcp-servers", f"mcp.servers.{name}", f"config.yaml mcp_servers.{name}",
2171
+ "migrated", servers_added=added)
2172
+
2173
+ if added > 0 and self.execute:
2174
+ self.maybe_backup(hermes_cfg_path)
2175
+ hermes_cfg["mcp_servers"] = existing_mcp
2176
+ dump_yaml_file(hermes_cfg_path, hermes_cfg)
2177
+
2178
+ # ── Plugins ───────────────────────────────────────────────
2179
+ def migrate_plugins_config(self, config: Optional[Dict[str, Any]] = None) -> None:
2180
+ config = config or self.load_openclaw_config()
2181
+ plugins = config.get("plugins") or {}
2182
+ if not plugins:
2183
+ self.record("plugins-config", None, None, "skipped", "No plugins configuration found")
2184
+ return
2185
+
2186
+ # Archive the full plugins config
2187
+ if self.archive_dir and self.execute:
2188
+ self.archive_dir.mkdir(parents=True, exist_ok=True)
2189
+ dest = self.archive_dir / "plugins-config.json"
2190
+ dest.write_text(json.dumps(plugins, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
2191
+ self.record("plugins-config", "openclaw.json plugins.*", str(dest), "archived",
2192
+ "Plugins config archived for manual review")
2193
+ else:
2194
+ self.record("plugins-config", "openclaw.json plugins.*", "archive/plugins-config.json",
2195
+ "archived" if not self.execute else "migrated", "Would archive plugins config")
2196
+
2197
+ # Copy extensions directory if it exists
2198
+ ext_dir = self.source_root / "extensions"
2199
+ if ext_dir.is_dir() and self.archive_dir:
2200
+ dest_ext = self.archive_dir / "extensions"
2201
+ if self.execute:
2202
+ shutil.copytree(ext_dir, dest_ext, dirs_exist_ok=True)
2203
+ self.record("plugins-config", str(ext_dir), str(dest_ext), "archived",
2204
+ "Extensions directory archived")
2205
+
2206
+ # Extract any plugin env vars
2207
+ entries = plugins.get("entries") or {}
2208
+ for plugin_name, plugin_cfg in entries.items():
2209
+ if isinstance(plugin_cfg, dict):
2210
+ env_vars = plugin_cfg.get("env") or {}
2211
+ api_key = plugin_cfg.get("apiKey")
2212
+ if api_key and self.migrate_secrets:
2213
+ env_key = f"PLUGIN_{plugin_name.upper().replace('-', '_')}_API_KEY"
2214
+ self._set_env_var(env_key, api_key, f"plugins.entries.{plugin_name}.apiKey")
2215
+
2216
+ # ── Cron jobs ─────────────────────────────────────────────
2217
+ def migrate_cron_jobs(self, config: Optional[Dict[str, Any]] = None) -> None:
2218
+ config = config or self.load_openclaw_config()
2219
+ cron = config.get("cron") or {}
2220
+ cron_store = self.source_root / "cron"
2221
+ found_any = False
2222
+
2223
+ # Archive the full cron config when present
2224
+ if cron:
2225
+ found_any = True
2226
+ if self.archive_dir and self.execute:
2227
+ self.archive_dir.mkdir(parents=True, exist_ok=True)
2228
+ dest = self.archive_dir / "cron-config.json"
2229
+ dest.write_text(json.dumps(cron, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
2230
+ self.record("cron-jobs", "openclaw.json cron.*", str(dest), "archived",
2231
+ "Cron config archived. Use 'hermes cron' to recreate jobs manually.")
2232
+ else:
2233
+ self.record("cron-jobs", "openclaw.json cron.*", "archive/cron-config.json",
2234
+ "archived", "Would archive cron config")
2235
+
2236
+ # Also check for cron store files even when config.cron is missing
2237
+ if cron_store.is_dir() and self.archive_dir:
2238
+ found_any = True
2239
+ dest_cron = self.archive_dir / "cron-store"
2240
+ if self.execute:
2241
+ shutil.copytree(cron_store, dest_cron, dirs_exist_ok=True)
2242
+ self.record("cron-jobs", str(cron_store), str(dest_cron), "archived",
2243
+ "Cron job store archived")
2244
+
2245
+ if not found_any:
2246
+ self.record("cron-jobs", None, None, "skipped", "No cron configuration found")
2247
+
2248
+ # ── Hooks ─────────────────────────────────────────────────
2249
+ def migrate_hooks_config(self, config: Optional[Dict[str, Any]] = None) -> None:
2250
+ config = config or self.load_openclaw_config()
2251
+ hooks = config.get("hooks") or {}
2252
+ if not hooks:
2253
+ self.record("hooks-config", None, None, "skipped", "No hooks configuration found")
2254
+ return
2255
+
2256
+ # Archive the full hooks config
2257
+ if self.archive_dir and self.execute:
2258
+ self.archive_dir.mkdir(parents=True, exist_ok=True)
2259
+ dest = self.archive_dir / "hooks-config.json"
2260
+ dest.write_text(json.dumps(hooks, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
2261
+ self.record("hooks-config", "openclaw.json hooks.*", str(dest), "archived",
2262
+ "Hooks config archived for manual review")
2263
+ else:
2264
+ self.record("hooks-config", "openclaw.json hooks.*", "archive/hooks-config.json",
2265
+ "archived", "Would archive hooks config")
2266
+
2267
+ # Copy workspace hooks directory
2268
+ for ws_name in ("workspace", "workspace.default"):
2269
+ hooks_dir = self.source_root / ws_name / "hooks"
2270
+ if hooks_dir.is_dir() and self.archive_dir:
2271
+ dest_hooks = self.archive_dir / "workspace-hooks"
2272
+ if self.execute:
2273
+ shutil.copytree(hooks_dir, dest_hooks, dirs_exist_ok=True)
2274
+ self.record("hooks-config", str(hooks_dir), str(dest_hooks), "archived",
2275
+ "Workspace hooks directory archived")
2276
+ break
2277
+
2278
+ # ── Agent config ──────────────────────────────────────────
2279
+ def migrate_agent_config(self, config: Optional[Dict[str, Any]] = None) -> None:
2280
+ config = config or self.load_openclaw_config()
2281
+ agents = config.get("agents") or {}
2282
+ defaults = agents.get("defaults") or {}
2283
+ agent_list = agents.get("list") or []
2284
+
2285
+ if not defaults and not agent_list:
2286
+ self.record("agent-config", None, None, "skipped", "No agent configuration found")
2287
+ return
2288
+
2289
+ hermes_cfg_path = self.target_root / "config.yaml"
2290
+ hermes_cfg = load_yaml_file(hermes_cfg_path)
2291
+ changes = False
2292
+
2293
+ # Map agent defaults
2294
+ agent_cfg = hermes_cfg.get("agent") or {}
2295
+ if defaults.get("contextTokens"):
2296
+ # No direct mapping but useful context
2297
+ pass
2298
+ if defaults.get("timeoutSeconds"):
2299
+ agent_cfg["max_turns"] = min(defaults["timeoutSeconds"] // 10, 200)
2300
+ changes = True
2301
+ if defaults.get("verboseDefault"):
2302
+ agent_cfg["verbose"] = defaults["verboseDefault"]
2303
+ changes = True
2304
+ if defaults.get("thinkingDefault"):
2305
+ # Map OpenClaw thinking -> Hermes reasoning_effort
2306
+ thinking = defaults["thinkingDefault"]
2307
+ if thinking in ("always", "high", "xhigh"):
2308
+ agent_cfg["reasoning_effort"] = "high"
2309
+ elif thinking in ("auto", "medium", "adaptive"):
2310
+ agent_cfg["reasoning_effort"] = "medium"
2311
+ elif thinking in ("off", "low", "none", "minimal"):
2312
+ agent_cfg["reasoning_effort"] = "low"
2313
+ changes = True
2314
+
2315
+ # Map compaction -> compression
2316
+ compaction = defaults.get("compaction") or {}
2317
+ if compaction:
2318
+ compression = hermes_cfg.get("compression") or {}
2319
+ if compaction.get("mode") == "off":
2320
+ compression["enabled"] = False
2321
+ else:
2322
+ compression["enabled"] = True
2323
+ if compaction.get("timeout"):
2324
+ pass # No direct mapping
2325
+ if compaction.get("model"):
2326
+ aux = hermes_cfg.setdefault("auxiliary", {})
2327
+ aux_comp = aux.setdefault("compression", {})
2328
+ aux_comp["model"] = compaction["model"]
2329
+ hermes_cfg["compression"] = compression
2330
+ changes = True
2331
+
2332
+ # Map humanDelay
2333
+ human_delay = defaults.get("humanDelay") or {}
2334
+ if human_delay:
2335
+ hd = hermes_cfg.get("human_delay") or {}
2336
+ hd_mode = human_delay.get("mode") or ("natural" if human_delay.get("enabled") else None)
2337
+ if hd_mode and hd_mode != "off":
2338
+ hd["mode"] = hd_mode
2339
+ if human_delay.get("minMs"):
2340
+ hd["min_ms"] = human_delay["minMs"]
2341
+ if human_delay.get("maxMs"):
2342
+ hd["max_ms"] = human_delay["maxMs"]
2343
+ hermes_cfg["human_delay"] = hd
2344
+ changes = True
2345
+
2346
+ # Map userTimezone
2347
+ if defaults.get("userTimezone"):
2348
+ hermes_cfg["timezone"] = defaults["userTimezone"]
2349
+ changes = True
2350
+
2351
+ # Map terminal/exec settings
2352
+ exec_cfg = (config.get("tools") or {}).get("exec") or {}
2353
+ if exec_cfg:
2354
+ terminal_cfg = hermes_cfg.get("terminal") or {}
2355
+ if exec_cfg.get("timeoutSec") or exec_cfg.get("timeout"):
2356
+ terminal_cfg["timeout"] = exec_cfg.get("timeoutSec") or exec_cfg.get("timeout")
2357
+ changes = True
2358
+ hermes_cfg["terminal"] = terminal_cfg
2359
+
2360
+ # Map sandbox -> terminal docker settings
2361
+ sandbox = defaults.get("sandbox") or {}
2362
+ if sandbox and sandbox.get("backend") == "docker":
2363
+ terminal_cfg = hermes_cfg.get("terminal") or {}
2364
+ terminal_cfg["backend"] = "docker"
2365
+ if sandbox.get("docker", {}).get("image"):
2366
+ terminal_cfg["docker_image"] = sandbox["docker"]["image"]
2367
+ hermes_cfg["terminal"] = terminal_cfg
2368
+ changes = True
2369
+
2370
+ if changes:
2371
+ hermes_cfg["agent"] = agent_cfg
2372
+ if self.execute:
2373
+ self.maybe_backup(hermes_cfg_path)
2374
+ dump_yaml_file(hermes_cfg_path, hermes_cfg)
2375
+ self.record("agent-config", "openclaw.json agents.defaults", "config.yaml agent/compression/terminal",
2376
+ "migrated", "Agent defaults mapped to Hermes config")
2377
+
2378
+ # Archive multi-agent list
2379
+ if agent_list:
2380
+ if self.archive_dir and self.execute:
2381
+ self.archive_dir.mkdir(parents=True, exist_ok=True)
2382
+ dest = self.archive_dir / "agents-list.json"
2383
+ dest.write_text(json.dumps(agent_list, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
2384
+ self.record("agent-config", "openclaw.json agents.list", "archive/agents-list.json",
2385
+ "archived", f"Multi-agent setup ({len(agent_list)} agents) archived for manual recreation")
2386
+
2387
+ # Archive bindings
2388
+ bindings = config.get("bindings") or []
2389
+ if bindings:
2390
+ if self.archive_dir and self.execute:
2391
+ self.archive_dir.mkdir(parents=True, exist_ok=True)
2392
+ dest = self.archive_dir / "bindings.json"
2393
+ dest.write_text(json.dumps(bindings, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
2394
+ self.record("agent-config", "openclaw.json bindings", "archive/bindings.json",
2395
+ "archived", f"Agent routing bindings ({len(bindings)} rules) archived")
2396
+
2397
+ # ── Gateway config ────────────────────────────────────────
2398
+ def migrate_gateway_config(self, config: Optional[Dict[str, Any]] = None) -> None:
2399
+ config = config or self.load_openclaw_config()
2400
+ gateway = config.get("gateway") or {}
2401
+ if not gateway:
2402
+ self.record("gateway-config", None, None, "skipped", "No gateway configuration found")
2403
+ return
2404
+
2405
+ # Archive the full gateway config (complex, many settings)
2406
+ if self.archive_dir and self.execute:
2407
+ self.archive_dir.mkdir(parents=True, exist_ok=True)
2408
+ dest = self.archive_dir / "gateway-config.json"
2409
+ dest.write_text(json.dumps(gateway, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
2410
+ self.record("gateway-config", "openclaw.json gateway.*", "archive/gateway-config.json",
2411
+ "archived", "Gateway config archived. Use 'hermes gateway' to configure.")
2412
+
2413
+ # Extract gateway auth token to .env if present
2414
+ auth = gateway.get("auth") or {}
2415
+ if auth.get("token") and self.migrate_secrets:
2416
+ self._set_env_var("HERMES_GATEWAY_TOKEN", auth["token"], "gateway.auth.token")
2417
+
2418
+ # ── Session config ────────────────────────────────────────
2419
+ def migrate_session_config(self, config: Optional[Dict[str, Any]] = None) -> None:
2420
+ config = config or self.load_openclaw_config()
2421
+ session = config.get("session") or {}
2422
+ if not session:
2423
+ self.record("session-config", None, None, "skipped", "No session configuration found")
2424
+ return
2425
+
2426
+ hermes_cfg_path = self.target_root / "config.yaml"
2427
+ hermes_cfg = load_yaml_file(hermes_cfg_path)
2428
+ sr = hermes_cfg.get("session_reset") or {}
2429
+ changes = False
2430
+
2431
+ # OpenClaw uses session.reset (structured) and session.resetTriggers (string array)
2432
+ reset = session.get("reset") or {}
2433
+ reset_triggers = session.get("resetTriggers") or session.get("reset_triggers") or []
2434
+
2435
+ if reset:
2436
+ # Structured reset config: has mode, atHour, idleMinutes
2437
+ mode = reset.get("mode", "")
2438
+ if mode == "daily":
2439
+ sr["mode"] = "daily"
2440
+ elif mode == "idle":
2441
+ sr["mode"] = "idle"
2442
+ else:
2443
+ sr["mode"] = mode or "none"
2444
+ if reset.get("atHour") is not None:
2445
+ sr["at_hour"] = reset["atHour"]
2446
+ if reset.get("idleMinutes"):
2447
+ sr["idle_minutes"] = reset["idleMinutes"]
2448
+ changes = True
2449
+ elif isinstance(reset_triggers, list) and reset_triggers:
2450
+ # Simple string triggers: ["daily", "idle"]
2451
+ has_daily = "daily" in reset_triggers
2452
+ has_idle = "idle" in reset_triggers
2453
+ if has_daily and has_idle:
2454
+ sr["mode"] = "both"
2455
+ elif has_daily:
2456
+ sr["mode"] = "daily"
2457
+ elif has_idle:
2458
+ sr["mode"] = "idle"
2459
+ changes = True
2460
+
2461
+ if changes:
2462
+ hermes_cfg["session_reset"] = sr
2463
+ if self.execute:
2464
+ self.maybe_backup(hermes_cfg_path)
2465
+ dump_yaml_file(hermes_cfg_path, hermes_cfg)
2466
+ self.record("session-config", "openclaw.json session.resetTriggers",
2467
+ "config.yaml session_reset", "migrated")
2468
+
2469
+ # Archive full session config (identity links, thread bindings, etc.)
2470
+ complex_keys = {"identityLinks", "threadBindings", "maintenance", "scope", "sendPolicy"}
2471
+ complex_session = {k: v for k, v in session.items() if k in complex_keys and v}
2472
+ if complex_session and self.archive_dir:
2473
+ if self.execute:
2474
+ self.archive_dir.mkdir(parents=True, exist_ok=True)
2475
+ dest = self.archive_dir / "session-config.json"
2476
+ dest.write_text(json.dumps(complex_session, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
2477
+ self.record("session-config", "openclaw.json session (advanced)",
2478
+ "archive/session-config.json", "archived",
2479
+ "Advanced session settings archived (identity links, thread bindings, etc.)")
2480
+
2481
+ # ── Full model providers ──────────────────────────────────
2482
+ def migrate_full_providers(self, config: Optional[Dict[str, Any]] = None) -> None:
2483
+ config = config or self.load_openclaw_config()
2484
+ models = config.get("models") or {}
2485
+ providers = models.get("providers") or {}
2486
+ if not providers:
2487
+ self.record("full-providers", None, None, "skipped", "No model providers found")
2488
+ return
2489
+
2490
+ hermes_cfg_path = self.target_root / "config.yaml"
2491
+ hermes_cfg = load_yaml_file(hermes_cfg_path)
2492
+ custom_providers = hermes_cfg.get("custom_providers") or []
2493
+ added = 0
2494
+
2495
+ # Well-known providers: just extract API keys
2496
+ WELL_KNOWN = {"openrouter", "openai", "anthropic", "deepseek", "google", "groq"}
2497
+
2498
+ for prov_name, prov_cfg in providers.items():
2499
+ if not isinstance(prov_cfg, dict):
2500
+ continue
2501
+
2502
+ # Extract API key to .env
2503
+ api_key = prov_cfg.get("apiKey") or prov_cfg.get("api_key")
2504
+ if api_key and self.migrate_secrets:
2505
+ env_key = f"{prov_name.upper().replace('-', '_')}_API_KEY"
2506
+ self._set_env_var(env_key, api_key, f"models.providers.{prov_name}.apiKey")
2507
+
2508
+ # For non-well-known providers, create custom_providers entry
2509
+ if prov_name.lower() not in WELL_KNOWN and prov_cfg.get("baseUrl"):
2510
+ # Check if already exists
2511
+ existing_names = {p.get("name", "").lower() for p in custom_providers}
2512
+ if prov_name.lower() in existing_names and not self.overwrite:
2513
+ self.record("full-providers", f"models.providers.{prov_name}",
2514
+ "config.yaml custom_providers", "conflict",
2515
+ f"Provider '{prov_name}' already exists")
2516
+ continue
2517
+
2518
+ api_type = prov_cfg.get("apiType") or prov_cfg.get("api") or prov_cfg.get("type") or "openai"
2519
+ api_mode_map = {
2520
+ "openai": "chat_completions",
2521
+ "openai-completions": "chat_completions",
2522
+ "openai-responses": "chat_completions",
2523
+ "anthropic": "anthropic_messages",
2524
+ "anthropic-messages": "anthropic_messages",
2525
+ "google-generative-ai": "chat_completions",
2526
+ "cohere": "chat_completions",
2527
+ }
2528
+ entry = {
2529
+ "name": prov_name,
2530
+ "base_url": prov_cfg["baseUrl"],
2531
+ "api_key": "", # referenced from .env
2532
+ "api_mode": api_mode_map.get(api_type, "chat_completions"),
2533
+ }
2534
+ custom_providers.append(entry)
2535
+ added += 1
2536
+ self.record("full-providers", f"models.providers.{prov_name}",
2537
+ f"config.yaml custom_providers[{prov_name}]", "migrated")
2538
+
2539
+ if added > 0 and self.execute:
2540
+ self.maybe_backup(hermes_cfg_path)
2541
+ hermes_cfg["custom_providers"] = custom_providers
2542
+ dump_yaml_file(hermes_cfg_path, hermes_cfg)
2543
+
2544
+ # Archive model aliases/catalog
2545
+ agent_defaults = (config.get("agents") or {}).get("defaults") or {}
2546
+ model_aliases = agent_defaults.get("models") or {}
2547
+ if model_aliases:
2548
+ if self.archive_dir and self.execute:
2549
+ self.archive_dir.mkdir(parents=True, exist_ok=True)
2550
+ dest = self.archive_dir / "model-aliases.json"
2551
+ dest.write_text(json.dumps(model_aliases, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
2552
+ self.record("full-providers", "agents.defaults.models", "archive/model-aliases.json",
2553
+ "archived", f"Model aliases/catalog ({len(model_aliases)} entries) archived")
2554
+
2555
+ # ── Deep channel config ───────────────────────────────────
2556
+ def migrate_deep_channels(self, config: Optional[Dict[str, Any]] = None) -> None:
2557
+ config = config or self.load_openclaw_config()
2558
+ channels = config.get("channels") or {}
2559
+ if not channels:
2560
+ self.record("deep-channels", None, None, "skipped", "No channel configuration found")
2561
+ return
2562
+
2563
+ # Extended channel token/allowlist mapping
2564
+ CHANNEL_ENV_MAP = {
2565
+ "matrix": {"token": "MATRIX...OKEN", "tokenField": "accessToken", "allowFrom": "MATRIX_ALLOWED_USERS",
2566
+ "extras": {"homeserverUrl": "MATRIX_HOMESERVER_URL", "userId": "MATRIX_USER_ID"}},
2567
+ "mattermost": {"token": "MATTERMOST_BOT_TOKEN", "allowFrom": "MATTERMOST_ALLOWED_USERS",
2568
+ "extras": {"url": "MATTERMOST_URL", "teamId": "MATTERMOST_TEAM_ID"}},
2569
+ "irc": {"extras": {"server": "IRC_SERVER", "nick": "IRC_NICK", "channels": "IRC_CHANNELS"}},
2570
+ "googlechat": {"extras": {"serviceAccountKeyPath": "GOOGLE_CHAT_SA_KEY_PATH"}},
2571
+ "imessage": {},
2572
+ "bluebubbles": {"extras": {"server": "BLUEBUBBLES_SERVER", "password": "BLUEBUBBLES_PASSWORD"}},
2573
+ "msteams": {"token": "MSTEAMS_BOT_TOKEN", "allowFrom": "MSTEAMS_ALLOWED_USERS"},
2574
+ "nostr": {"extras": {"nsec": "NOSTR_NSEC", "relays": "NOSTR_RELAYS"}},
2575
+ "twitch": {"token": "TWITCH_BOT_TOKEN", "extras": {"channels": "TWITCH_CHANNELS"}},
2576
+ }
2577
+
2578
+ for ch_name, ch_mapping in CHANNEL_ENV_MAP.items():
2579
+ ch_cfg = channels.get(ch_name) or {}
2580
+ if not ch_cfg:
2581
+ continue
2582
+
2583
+ # Extract tokens (check flat path, then accounts.default)
2584
+ token_field = ch_mapping.get("tokenField", "botToken")
2585
+ bot_token = self._get_channel_field(ch_cfg, token_field)
2586
+ if ch_mapping.get("token") and bot_token and self.migrate_secrets:
2587
+ self._set_env_var(ch_mapping["token"], str(bot_token),
2588
+ f"channels.{ch_name}.{token_field}")
2589
+ allow_val = self._get_channel_field(ch_cfg, "allowFrom")
2590
+ if ch_mapping.get("allowFrom") and allow_val:
2591
+ if isinstance(allow_val, list):
2592
+ allow_val = ",".join(str(x) for x in allow_val)
2593
+ self._set_env_var(ch_mapping["allowFrom"], str(allow_val),
2594
+ f"channels.{ch_name}.allowFrom")
2595
+ # Extra fields
2596
+ for oc_key, env_key in (ch_mapping.get("extras") or {}).items():
2597
+ val = self._get_channel_field(ch_cfg, oc_key)
2598
+ if val:
2599
+ if isinstance(val, list):
2600
+ val = ",".join(str(x) for x in val)
2601
+ is_secret = "password" in oc_key.lower() or "token" in oc_key.lower() or "nsec" in oc_key.lower()
2602
+ if is_secret and not self.migrate_secrets:
2603
+ continue
2604
+ self._set_env_var(env_key, str(val), f"channels.{ch_name}.{oc_key}")
2605
+
2606
+ # Map Discord-specific settings to Hermes config
2607
+ discord_cfg = channels.get("discord") or {}
2608
+ if discord_cfg:
2609
+ hermes_cfg_path = self.target_root / "config.yaml"
2610
+ hermes_cfg = load_yaml_file(hermes_cfg_path)
2611
+ discord_hermes = hermes_cfg.get("discord") or {}
2612
+ changed = False
2613
+ if "requireMention" in discord_cfg:
2614
+ discord_hermes["require_mention"] = discord_cfg["requireMention"]
2615
+ changed = True
2616
+ if discord_cfg.get("autoThread") is not None:
2617
+ discord_hermes["auto_thread"] = discord_cfg["autoThread"]
2618
+ changed = True
2619
+ if changed and self.execute:
2620
+ hermes_cfg["discord"] = discord_hermes
2621
+ dump_yaml_file(hermes_cfg_path, hermes_cfg)
2622
+
2623
+ # Archive complex channel configs (group settings, thread bindings, etc.)
2624
+ complex_archive = {}
2625
+ for ch_name, ch_cfg in channels.items():
2626
+ if not isinstance(ch_cfg, dict):
2627
+ continue
2628
+ complex_keys = {k: v for k, v in ch_cfg.items()
2629
+ if k not in ("botToken", "appToken", "allowFrom", "enabled")
2630
+ and v and k not in ("requireMention", "autoThread")}
2631
+ if complex_keys:
2632
+ complex_archive[ch_name] = complex_keys
2633
+
2634
+ if complex_archive and self.archive_dir:
2635
+ if self.execute:
2636
+ self.archive_dir.mkdir(parents=True, exist_ok=True)
2637
+ dest = self.archive_dir / "channels-deep-config.json"
2638
+ dest.write_text(json.dumps(complex_archive, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
2639
+ self.record("deep-channels", "openclaw.json channels (advanced settings)",
2640
+ "archive/channels-deep-config.json", "archived",
2641
+ f"Deep channel config for {len(complex_archive)} channels archived")
2642
+
2643
+ # ── Browser config ────────────────────────────────────────
2644
+ def migrate_browser_config(self, config: Optional[Dict[str, Any]] = None) -> None:
2645
+ config = config or self.load_openclaw_config()
2646
+ browser = config.get("browser") or {}
2647
+ if not browser:
2648
+ self.record("browser-config", None, None, "skipped", "No browser configuration found")
2649
+ return
2650
+
2651
+ hermes_cfg_path = self.target_root / "config.yaml"
2652
+ hermes_cfg = load_yaml_file(hermes_cfg_path)
2653
+ browser_hermes = hermes_cfg.get("browser") or {}
2654
+ changed = False
2655
+
2656
+ # Map fields that have Hermes equivalents
2657
+ if browser.get("cdpUrl"):
2658
+ browser_hermes["cdp_url"] = browser["cdpUrl"]
2659
+ changed = True
2660
+ if browser.get("headless") is not None:
2661
+ browser_hermes["headless"] = browser["headless"]
2662
+ changed = True
2663
+
2664
+ if changed:
2665
+ hermes_cfg["browser"] = browser_hermes
2666
+ if self.execute:
2667
+ self.maybe_backup(hermes_cfg_path)
2668
+ dump_yaml_file(hermes_cfg_path, hermes_cfg)
2669
+ self.record("browser-config", "openclaw.json browser.*", "config.yaml browser",
2670
+ "migrated")
2671
+
2672
+ # Archive remaining browser settings
2673
+ advanced = {k: v for k, v in browser.items()
2674
+ if k not in ("cdpUrl", "headless") and v}
2675
+ if advanced and self.archive_dir:
2676
+ if self.execute:
2677
+ self.archive_dir.mkdir(parents=True, exist_ok=True)
2678
+ dest = self.archive_dir / "browser-config.json"
2679
+ dest.write_text(json.dumps(advanced, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
2680
+ self.record("browser-config", "openclaw.json browser (advanced)",
2681
+ "archive/browser-config.json", "archived")
2682
+
2683
+ # ── Tools config ──────────────────────────────────────────
2684
+ def migrate_tools_config(self, config: Optional[Dict[str, Any]] = None) -> None:
2685
+ config = config or self.load_openclaw_config()
2686
+ tools = config.get("tools") or {}
2687
+ if not tools:
2688
+ self.record("tools-config", None, None, "skipped", "No tools configuration found")
2689
+ return
2690
+
2691
+ hermes_cfg_path = self.target_root / "config.yaml"
2692
+ hermes_cfg = load_yaml_file(hermes_cfg_path)
2693
+ changed = False
2694
+
2695
+ # Map exec timeout -> terminal timeout (field is timeoutSec in OpenClaw)
2696
+ exec_cfg = tools.get("exec") or {}
2697
+ timeout_val = exec_cfg.get("timeoutSec") or exec_cfg.get("timeout")
2698
+ if timeout_val:
2699
+ terminal_cfg = hermes_cfg.get("terminal") or {}
2700
+ terminal_cfg["timeout"] = timeout_val
2701
+ hermes_cfg["terminal"] = terminal_cfg
2702
+ changed = True
2703
+
2704
+ # Map web search API key (path: tools.web.search.brave.apiKey in OpenClaw)
2705
+ web_cfg = tools.get("web") or tools.get("webSearch") or {}
2706
+ search_cfg = web_cfg.get("search") or web_cfg if not web_cfg.get("search") else web_cfg["search"]
2707
+ brave_cfg = search_cfg.get("brave") or {}
2708
+ brave_key = brave_cfg.get("apiKey") or search_cfg.get("braveApiKey") or web_cfg.get("braveApiKey")
2709
+ if brave_key and isinstance(brave_key, str) and self.migrate_secrets:
2710
+ self._set_env_var("BRAVE_API_KEY", brave_key, "tools.web.search.brave.apiKey")
2711
+
2712
+ if changed and self.execute:
2713
+ self.maybe_backup(hermes_cfg_path)
2714
+ dump_yaml_file(hermes_cfg_path, hermes_cfg)
2715
+ self.record("tools-config", "openclaw.json tools.*", "config.yaml terminal",
2716
+ "migrated")
2717
+
2718
+ # Archive full tools config
2719
+ if self.archive_dir:
2720
+ if self.execute:
2721
+ self.archive_dir.mkdir(parents=True, exist_ok=True)
2722
+ dest = self.archive_dir / "tools-config.json"
2723
+ dest.write_text(json.dumps(tools, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
2724
+ self.record("tools-config", "openclaw.json tools (full)", "archive/tools-config.json",
2725
+ "archived", "Full tools config archived for reference")
2726
+
2727
+ # ── Approvals config ──────────────────────────────────────
2728
+ def migrate_approvals_config(self, config: Optional[Dict[str, Any]] = None) -> None:
2729
+ config = config or self.load_openclaw_config()
2730
+ approvals = config.get("approvals") or {}
2731
+ if not approvals:
2732
+ self.record("approvals-config", None, None, "skipped", "No approvals configuration found")
2733
+ return
2734
+
2735
+ hermes_cfg_path = self.target_root / "config.yaml"
2736
+ hermes_cfg = load_yaml_file(hermes_cfg_path)
2737
+
2738
+ # Map approval mode (nested under approvals.exec.mode in OpenClaw)
2739
+ exec_approvals = approvals.get("exec") or {}
2740
+ mode = (exec_approvals.get("mode") if isinstance(exec_approvals, dict) else None) or approvals.get("mode") or approvals.get("defaultMode")
2741
+ if mode:
2742
+ mode_map = {"auto": "off", "always": "manual", "smart": "smart", "manual": "manual"}
2743
+ hermes_mode = mode_map.get(mode, "manual")
2744
+ hermes_cfg.setdefault("approvals", {})["mode"] = hermes_mode
2745
+ if self.execute:
2746
+ self.maybe_backup(hermes_cfg_path)
2747
+ dump_yaml_file(hermes_cfg_path, hermes_cfg)
2748
+ self.record("approvals-config", "openclaw.json approvals.mode",
2749
+ "config.yaml approvals.mode", "migrated", f"Mapped '{mode}' -> '{hermes_mode}'")
2750
+
2751
+ # Archive full approvals config
2752
+ if len(approvals) > 1 and self.archive_dir:
2753
+ if self.execute:
2754
+ self.archive_dir.mkdir(parents=True, exist_ok=True)
2755
+ dest = self.archive_dir / "approvals-config.json"
2756
+ dest.write_text(json.dumps(approvals, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
2757
+ self.record("approvals-config", "openclaw.json approvals (rules)",
2758
+ "archive/approvals-config.json", "archived")
2759
+
2760
+ # ── Memory backend ────────────────────────────────────────
2761
+ def migrate_memory_backend(self, config: Optional[Dict[str, Any]] = None) -> None:
2762
+ config = config or self.load_openclaw_config()
2763
+ memory = config.get("memory") or {}
2764
+ if not memory:
2765
+ self.record("memory-backend", None, None, "skipped", "No memory backend configuration found")
2766
+ return
2767
+
2768
+ if self.archive_dir and self.execute:
2769
+ self.archive_dir.mkdir(parents=True, exist_ok=True)
2770
+ dest = self.archive_dir / "memory-backend-config.json"
2771
+ dest.write_text(json.dumps(memory, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
2772
+ self.record("memory-backend", "openclaw.json memory.*", "archive/memory-backend-config.json",
2773
+ "archived", "Memory backend config (QMD, vector search, citations) archived for manual review")
2774
+
2775
+ # ── Skills config ─────────────────────────────────────────
2776
+ def migrate_skills_config(self, config: Optional[Dict[str, Any]] = None) -> None:
2777
+ config = config or self.load_openclaw_config()
2778
+ skills = config.get("skills") or {}
2779
+ entries = skills.get("entries") or {}
2780
+ if not entries and not skills:
2781
+ self.record("skills-config", None, None, "skipped", "No skills registry configuration found")
2782
+ return
2783
+
2784
+ if self.archive_dir and self.execute:
2785
+ self.archive_dir.mkdir(parents=True, exist_ok=True)
2786
+ dest = self.archive_dir / "skills-registry-config.json"
2787
+ dest.write_text(json.dumps(skills, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
2788
+ self.record("skills-config", "openclaw.json skills.*", "archive/skills-registry-config.json",
2789
+ "archived", f"Skills registry config ({len(entries)} entries) archived")
2790
+
2791
+ # ── UI / Identity ─────────────────────────────────────────
2792
+ def migrate_ui_identity(self, config: Optional[Dict[str, Any]] = None) -> None:
2793
+ config = config or self.load_openclaw_config()
2794
+ ui = config.get("ui") or {}
2795
+ if not ui:
2796
+ self.record("ui-identity", None, None, "skipped", "No UI/identity configuration found")
2797
+ return
2798
+
2799
+ if self.archive_dir and self.execute:
2800
+ self.archive_dir.mkdir(parents=True, exist_ok=True)
2801
+ dest = self.archive_dir / "ui-identity-config.json"
2802
+ dest.write_text(json.dumps(ui, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
2803
+ self.record("ui-identity", "openclaw.json ui.*", "archive/ui-identity-config.json",
2804
+ "archived", "UI theme and identity settings archived")
2805
+
2806
+ # ── Logging / Diagnostics ─────────────────────────────────
2807
+ def migrate_logging_config(self, config: Optional[Dict[str, Any]] = None) -> None:
2808
+ config = config or self.load_openclaw_config()
2809
+ logging_cfg = config.get("logging") or {}
2810
+ diagnostics = config.get("diagnostics") or {}
2811
+ combined = {}
2812
+ if logging_cfg:
2813
+ combined["logging"] = logging_cfg
2814
+ if diagnostics:
2815
+ combined["diagnostics"] = diagnostics
2816
+ if not combined:
2817
+ self.record("logging-config", None, None, "skipped", "No logging/diagnostics configuration found")
2818
+ return
2819
+
2820
+ if self.archive_dir and self.execute:
2821
+ self.archive_dir.mkdir(parents=True, exist_ok=True)
2822
+ dest = self.archive_dir / "logging-diagnostics-config.json"
2823
+ dest.write_text(json.dumps(combined, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
2824
+ self.record("logging-config", "openclaw.json logging/diagnostics",
2825
+ "archive/logging-diagnostics-config.json", "archived")
2826
+
2827
+ # ── Helper: set env var ───────────────────────────────────
2828
+ def _set_env_var(self, key: str, value: str, source_label: str) -> None:
2829
+ env_path = self.target_root / ".env"
2830
+ if self.execute:
2831
+ env_data = parse_env_file(env_path)
2832
+ if key in env_data and not self.overwrite:
2833
+ self.record("env-var", source_label, f".env {key}", "conflict",
2834
+ f"Env var {key} already set")
2835
+ return
2836
+ env_data[key] = value
2837
+ save_env_file(env_path, env_data)
2838
+ self.record("env-var", source_label, f".env {key}", "migrated")
2839
+
2840
+ # ── Generate migration notes ──────────────────────────────
2841
+ def generate_migration_notes(self) -> None:
2842
+ if not self.output_dir:
2843
+ return
2844
+ notes = [
2845
+ "# OpenClaw -> Hermes Migration Notes",
2846
+ "",
2847
+ "This document lists items that require manual attention after migration.",
2848
+ "",
2849
+ "## PM2 / External Processes",
2850
+ "",
2851
+ "Your PM2 processes (Discord bots, Telegram bots, etc.) are NOT affected",
2852
+ "by this migration. They run independently and will continue working.",
2853
+ "No action needed for PM2-managed processes.",
2854
+ "",
2855
+ ]
2856
+
2857
+ archived = [i for i in self.items if i.status == "archived"]
2858
+ if archived:
2859
+ notes.extend([
2860
+ "## Archived Items (Manual Review Needed)",
2861
+ "",
2862
+ "These OpenClaw configurations were archived because they don't have a",
2863
+ "direct 1:1 mapping in Hermes. Review each file and recreate manually:",
2864
+ "",
2865
+ ])
2866
+ for item in archived:
2867
+ notes.append(f"- **{item.kind}**: `{item.destination}` -- {item.reason}")
2868
+ notes.append("")
2869
+
2870
+ conflicts = [i for i in self.items if i.status == "conflict"]
2871
+ if conflicts:
2872
+ notes.extend([
2873
+ "## Conflicts (Existing Hermes Config Not Overwritten)",
2874
+ "",
2875
+ "These items already existed in your Hermes config. Re-run with",
2876
+ "`--overwrite` to force, or merge manually:",
2877
+ "",
2878
+ ])
2879
+ for item in conflicts:
2880
+ notes.append(f"- **{item.kind}**: {item.reason}")
2881
+ notes.append("")
2882
+
2883
+ has_cron_config_archive = any(
2884
+ i.kind == "cron-jobs" and i.status == "archived" and i.destination and i.destination.endswith("cron-config.json")
2885
+ for i in self.items
2886
+ )
2887
+ has_cron_store_archive = any(
2888
+ i.kind == "cron-jobs" and i.status == "archived" and i.destination and i.destination.endswith("cron-store")
2889
+ for i in self.items
2890
+ )
2891
+
2892
+ notes.extend([
2893
+ "## IMPORTANT: Archive the OpenClaw Directory",
2894
+ "",
2895
+ "After migration, your OpenClaw directory still exists on disk with workspace",
2896
+ "state files (todo.json, sessions, logs). If the Hermes agent discovers these",
2897
+ "directories, it may read/write to them instead of the Hermes state, causing",
2898
+ "confusion (e.g., cron jobs reading a different todo list than interactive sessions).",
2899
+ "",
2900
+ "**Strongly recommended:** Run `hermes claw cleanup` to rename the OpenClaw",
2901
+ "directory to `.openclaw.pre-migration`. This prevents the agent from finding it.",
2902
+ "The directory is renamed, not deleted — you can undo this at any time.",
2903
+ "",
2904
+ "If you skip this step and notice the agent getting confused about workspaces",
2905
+ "or todo lists, run `hermes claw cleanup` to fix it.",
2906
+ "",
2907
+ "## Hermes-Specific Setup",
2908
+ "",
2909
+ "After migration, you may want to:",
2910
+ "- Run `hermes claw cleanup` to archive the OpenClaw directory (prevents state confusion)",
2911
+ "- Run `hermes setup` to configure any remaining settings",
2912
+ "- Run `hermes mcp list` to verify MCP servers were imported correctly",
2913
+ ])
2914
+
2915
+ if has_cron_config_archive:
2916
+ notes.append("- Run `hermes cron` to recreate scheduled tasks (see archive/cron-config.json)")
2917
+ elif has_cron_store_archive:
2918
+ notes.append("- Run `hermes cron` to recreate scheduled tasks (see archived cron-store)")
2919
+
2920
+ # Check if skills were imported
2921
+ has_skills = any(i.kind == "skills" and i.status == "migrated" for i in self.items)
2922
+ if has_skills:
2923
+ notes.extend([
2924
+ "",
2925
+ "## Imported Skills",
2926
+ "",
2927
+ "Imported skills require a new session to take effect. After migration,",
2928
+ "restart your agent or start a new chat session, then run `/skills`",
2929
+ "to verify they loaded correctly.",
2930
+ "",
2931
+ ])
2932
+
2933
+ # Check if WhatsApp was detected
2934
+ has_whatsapp = any(i.kind == "whatsapp-settings" and i.status == "migrated" for i in self.items)
2935
+ if has_whatsapp:
2936
+ notes.extend([
2937
+ "",
2938
+ "## WhatsApp Requires Re-Pairing",
2939
+ "",
2940
+ "WhatsApp uses QR-code pairing, not token-based auth. Your allowlist",
2941
+ "was migrated, but you must re-pair the device by running:",
2942
+ "",
2943
+ " hermes whatsapp",
2944
+ "",
2945
+ ])
2946
+
2947
+ notes.extend([
2948
+ "- Run `hermes gateway install` if you need the gateway service",
2949
+ "- Review `~/.hermes/config.yaml` for any adjustments",
2950
+ "",
2951
+ ])
2952
+
2953
+ if self.execute:
2954
+ self.output_dir.mkdir(parents=True, exist_ok=True)
2955
+ (self.output_dir / "MIGRATION_NOTES.md").write_text(
2956
+ "\n".join(notes) + "\n", encoding="utf-8"
2957
+ )
2958
+
2959
+
2960
+ def parse_args() -> argparse.Namespace:
2961
+ parser = argparse.ArgumentParser(description="Migrate OpenClaw user state into Hermes Agent.")
2962
+ parser.add_argument("--source", default=str(Path.home() / ".openclaw"), help="OpenClaw home directory")
2963
+ parser.add_argument("--target", default=os.environ.get("HERMES_HOME") or str(Path.home() / ".hermes"), help="Hermes home directory")
2964
+ parser.add_argument(
2965
+ "--workspace-target",
2966
+ help="Optional workspace root where the workspace instructions file should be copied",
2967
+ )
2968
+ parser.add_argument("--execute", action="store_true", help="Apply changes instead of reporting a dry run")
2969
+ parser.add_argument("--overwrite", action="store_true", help="Overwrite existing Hermes targets after backing them up")
2970
+ parser.add_argument(
2971
+ "--migrate-secrets",
2972
+ action="store_true",
2973
+ help="Import a narrow allowlist of Hermes-compatible secrets into the target env file",
2974
+ )
2975
+ parser.add_argument(
2976
+ "--skill-conflict",
2977
+ choices=sorted(SKILL_CONFLICT_MODES),
2978
+ default="skip",
2979
+ help="How to handle imported skill directory conflicts: skip, overwrite, or rename the imported copy.",
2980
+ )
2981
+ parser.add_argument(
2982
+ "--preset",
2983
+ choices=sorted(MIGRATION_PRESETS),
2984
+ help="Apply a named migration preset. 'user-data' excludes allowlisted secrets; 'full' includes all compatible groups.",
2985
+ )
2986
+ parser.add_argument(
2987
+ "--include",
2988
+ action="append",
2989
+ default=[],
2990
+ help="Comma-separated migration option ids to include (default: all). "
2991
+ f"Valid ids: {', '.join(sorted(MIGRATION_OPTION_METADATA))}",
2992
+ )
2993
+ parser.add_argument(
2994
+ "--exclude",
2995
+ action="append",
2996
+ default=[],
2997
+ help="Comma-separated migration option ids to skip. "
2998
+ f"Valid ids: {', '.join(sorted(MIGRATION_OPTION_METADATA))}",
2999
+ )
3000
+ parser.add_argument("--output-dir", help="Where to write report, backups, and archived docs")
3001
+ parser.add_argument(
3002
+ "--json",
3003
+ action="store_true",
3004
+ dest="json_output",
3005
+ help="Print the migration report as JSON on stdout (redacted). "
3006
+ "Combine with no --execute for a safe plan-only machine-readable preview.",
3007
+ )
3008
+ return parser.parse_args()
3009
+
3010
+
3011
+ def main() -> int:
3012
+ args = parse_args()
3013
+ try:
3014
+ selected_options = resolve_selected_options(args.include, args.exclude, preset=args.preset)
3015
+ except ValueError as exc:
3016
+ print(json.dumps({"error": str(exc)}, indent=2, ensure_ascii=False))
3017
+ return 2
3018
+ migrator = Migrator(
3019
+ source_root=Path(os.path.expanduser(args.source)).resolve(),
3020
+ target_root=Path(os.path.expanduser(args.target)).resolve(),
3021
+ execute=bool(args.execute),
3022
+ workspace_target=Path(os.path.expanduser(args.workspace_target)).resolve() if args.workspace_target else None,
3023
+ overwrite=bool(args.overwrite),
3024
+ migrate_secrets=bool(args.migrate_secrets),
3025
+ output_dir=Path(os.path.expanduser(args.output_dir)).resolve() if args.output_dir else None,
3026
+ selected_options=selected_options,
3027
+ preset_name=args.preset or "",
3028
+ skill_conflict_mode=args.skill_conflict,
3029
+ )
3030
+ report = migrator.migrate()
3031
+
3032
+ # ── Machine-readable JSON mode ────────────────────────────
3033
+ # When --json is set, print the redacted report to stdout and skip the
3034
+ # human-readable terminal recap. Useful for CI and scripted wrappers.
3035
+ if getattr(args, "json_output", False):
3036
+ print(json.dumps(redact_migration_value(report), indent=2, ensure_ascii=False))
3037
+ return 0
3038
+
3039
+ # ── Human-readable terminal recap ─────────────────────────
3040
+ s = report["summary"]
3041
+ items = report["items"]
3042
+ mode_label = "DRY RUN" if not args.execute else "EXECUTED"
3043
+ total = sum(s.values())
3044
+
3045
+ print()
3046
+ print(f" ╔══════════════════════════════════════════════════════╗")
3047
+ print(f" ║ OpenClaw -> Hermes Migration [{mode_label:>8s}] ║")
3048
+ print(f" ╠══════════════════════════════════════════════════════╣")
3049
+ print(f" ║ Source: {str(report['source_root'])[:42]:<42s} ║")
3050
+ print(f" ║ Target: {str(report['target_root'])[:42]:<42s} ║")
3051
+ print(f" ╠══════════════════════════════════════════════════════╣")
3052
+ print(f" ║ ✔ Migrated: {s.get('migrated', 0):>3d} ◆ Archived: {s.get('archived', 0):>3d} ║")
3053
+ print(f" ║ ⊘ Skipped: {s.get('skipped', 0):>3d} ⚠ Conflicts: {s.get('conflict', 0):>3d} ║")
3054
+ print(f" ║ ✖ Errors: {s.get('error', 0):>3d} Total: {total:>3d} ║")
3055
+ print(f" ╚══════════════════════════════════════════════════════╝")
3056
+
3057
+ # Show what was migrated
3058
+ migrated = [i for i in items if i["status"] == "migrated"]
3059
+ if migrated:
3060
+ print()
3061
+ print(" Migrated:")
3062
+ seen_kinds = set()
3063
+ for item in migrated:
3064
+ label = item["kind"]
3065
+ if label in seen_kinds:
3066
+ continue
3067
+ seen_kinds.add(label)
3068
+ dest = item.get("destination") or ""
3069
+ if dest.startswith(str(report["target_root"])):
3070
+ dest = "~/.hermes/" + dest[len(str(report["target_root"])) + 1:]
3071
+ meta = MIGRATION_OPTION_METADATA.get(label, {})
3072
+ display = meta.get("label", label)
3073
+ print(f" ✔ {display:<35s} -> {dest}")
3074
+
3075
+ # Show what was archived
3076
+ archived = [i for i in items if i["status"] == "archived"]
3077
+ if archived:
3078
+ print()
3079
+ print(" Archived (manual review needed):")
3080
+ seen_kinds = set()
3081
+ for item in archived:
3082
+ label = item["kind"]
3083
+ if label in seen_kinds:
3084
+ continue
3085
+ seen_kinds.add(label)
3086
+ reason = item.get("reason", "")
3087
+ meta = MIGRATION_OPTION_METADATA.get(label, {})
3088
+ display = meta.get("label", label)
3089
+ short_reason = reason[:50] + "..." if len(reason) > 50 else reason
3090
+ print(f" ◆ {display:<35s} {short_reason}")
3091
+
3092
+ # Show conflicts
3093
+ conflicts = [i for i in items if i["status"] == "conflict"]
3094
+ if conflicts:
3095
+ print()
3096
+ print(" Conflicts (use --overwrite to force):")
3097
+ for item in conflicts:
3098
+ print(f" ⚠ {item['kind']}: {item.get('reason', '')}")
3099
+
3100
+ # Show errors
3101
+ errors = [i for i in items if i["status"] == "error"]
3102
+ if errors:
3103
+ print()
3104
+ print(" Errors:")
3105
+ for item in errors:
3106
+ print(f" ✖ {item['kind']}: {item.get('reason', '')}")
3107
+
3108
+ # PM2 reassurance
3109
+ print()
3110
+ print(" ℹ PM2 processes (Discord/Telegram bots) are NOT affected.")
3111
+
3112
+ # Next steps
3113
+ if args.execute:
3114
+ print()
3115
+ print(" Next steps:")
3116
+ print(" 1. Review ~/.hermes/config.yaml")
3117
+ print(" 2. Run: hermes mcp list")
3118
+ if any(i["kind"] == "cron-jobs" and i["status"] == "archived" for i in items):
3119
+ print(" 3. Recreate cron jobs: hermes cron")
3120
+ if report.get("output_dir"):
3121
+ print(f" → Full report: {report['output_dir']}/MIGRATION_NOTES.md")
3122
+ elif not args.execute:
3123
+ print()
3124
+ print(" This was a dry run. Add --execute to apply changes.")
3125
+
3126
+ print()
3127
+
3128
+ # Also dump JSON for programmatic use
3129
+ if os.environ.get("MIGRATION_JSON_OUTPUT"):
3130
+ print(json.dumps(report, indent=2, ensure_ascii=False))
3131
+
3132
+ return 0 if s.get("error", 0) == 0 else 1
3133
+
3134
+
3135
+ if __name__ == "__main__":
3136
+ raise SystemExit(main())