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,1518 @@
1
+ """Signal messenger platform adapter.
2
+
3
+ Connects to a signal-cli daemon running in HTTP mode.
4
+ Inbound messages arrive via SSE (Server-Sent Events) streaming.
5
+ Outbound messages and actions use JSON-RPC 2.0 over HTTP.
6
+
7
+ Based on PR #268 by ibhagwan, rebuilt with bug fixes.
8
+
9
+ Requires:
10
+ - signal-cli installed and running: signal-cli daemon --http 127.0.0.1:8080
11
+ - SIGNAL_HTTP_URL and SIGNAL_ACCOUNT environment variables set
12
+ """
13
+
14
+ import asyncio
15
+ import base64
16
+ import json
17
+ import logging
18
+ import os
19
+ import random
20
+ import time
21
+ import uuid
22
+ from datetime import datetime, timezone
23
+ from pathlib import Path
24
+ from typing import Any, Dict, List, Optional, Tuple
25
+ from urllib.parse import quote, unquote
26
+
27
+ import httpx
28
+
29
+ from gateway.config import Platform, PlatformConfig
30
+ from gateway.platforms.base import (
31
+ BasePlatformAdapter,
32
+ MessageEvent,
33
+ MessageType,
34
+ ProcessingOutcome,
35
+ SendResult,
36
+ cache_image_from_bytes,
37
+ cache_audio_from_bytes,
38
+ cache_document_from_bytes,
39
+ cache_image_from_url,
40
+ )
41
+ from gateway.platforms.helpers import redact_phone
42
+ from gateway.platforms.signal_rate_limit import (
43
+ SIGNAL_BATCH_PACING_NOTICE_THRESHOLD,
44
+ SIGNAL_MAX_ATTACHMENTS_PER_MSG,
45
+ SIGNAL_RATE_LIMIT_MAX_ATTEMPTS,
46
+ SignalRateLimitError,
47
+ _extract_retry_after_seconds,
48
+ _format_wait,
49
+ _is_signal_rate_limit_error,
50
+ _signal_send_timeout,
51
+ get_scheduler,
52
+ )
53
+
54
+ logger = logging.getLogger(__name__)
55
+
56
+ # ---------------------------------------------------------------------------
57
+ # Constants
58
+ # ---------------------------------------------------------------------------
59
+ SIGNAL_MAX_ATTACHMENT_SIZE = 100 * 1024 * 1024 # 100 MB
60
+ MAX_MESSAGE_LENGTH = 8000 # Signal message size limit
61
+ TYPING_INTERVAL = 8.0 # seconds between typing indicator refreshes
62
+ SSE_RETRY_DELAY_INITIAL = 2.0
63
+ SSE_RETRY_DELAY_MAX = 60.0
64
+ HEALTH_CHECK_INTERVAL = 30.0 # seconds between health checks
65
+ HEALTH_CHECK_STALE_THRESHOLD = 120.0 # seconds without SSE activity before concern
66
+
67
+
68
+ # ---------------------------------------------------------------------------
69
+ # Helpers
70
+ # ---------------------------------------------------------------------------
71
+
72
+
73
+ def _parse_comma_list(value: str) -> List[str]:
74
+ """Split a comma-separated string into a list, stripping whitespace."""
75
+ return [v.strip() for v in value.split(",") if v.strip()]
76
+
77
+
78
+ def _guess_extension(data: bytes) -> str:
79
+ """Guess file extension from magic bytes."""
80
+ if data[:4] == b"\x89PNG":
81
+ return ".png"
82
+ if data[:2] == b"\xff\xd8":
83
+ return ".jpg"
84
+ if data[:4] == b"GIF8":
85
+ return ".gif"
86
+ if len(data) >= 12 and data[:4] == b"RIFF" and data[8:12] == b"WEBP":
87
+ return ".webp"
88
+ if data[:4] == b"%PDF":
89
+ return ".pdf"
90
+ if len(data) >= 8 and data[4:8] == b"ftyp":
91
+ return ".mp4"
92
+ if data[:4] == b"OggS":
93
+ return ".ogg"
94
+ if len(data) >= 2 and data[0] == 0xFF and (data[1] & 0xE0) == 0xE0:
95
+ return ".mp3"
96
+ if data[:2] == b"PK":
97
+ return ".zip"
98
+ return ".bin"
99
+
100
+
101
+ def _is_image_ext(ext: str) -> bool:
102
+ return ext.lower() in {".jpg", ".jpeg", ".png", ".gif", ".webp"}
103
+
104
+
105
+ def _is_audio_ext(ext: str) -> bool:
106
+ return ext.lower() in {".mp3", ".wav", ".ogg", ".m4a", ".aac"}
107
+
108
+
109
+ _EXT_TO_MIME = {
110
+ ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png",
111
+ ".gif": "image/gif", ".webp": "image/webp",
112
+ ".ogg": "audio/ogg", ".mp3": "audio/mpeg", ".wav": "audio/wav",
113
+ ".m4a": "audio/mp4", ".aac": "audio/aac",
114
+ ".mp4": "video/mp4", ".pdf": "application/pdf", ".zip": "application/zip",
115
+ }
116
+
117
+
118
+ def _ext_to_mime(ext: str) -> str:
119
+ """Map file extension to MIME type."""
120
+ return _EXT_TO_MIME.get(ext.lower(), "application/octet-stream")
121
+
122
+
123
+ def _render_mentions(text: str, mentions: list) -> str:
124
+ """Replace Signal mention placeholders (\\uFFFC) with readable @identifiers.
125
+
126
+ Signal encodes @mentions as the Unicode object replacement character
127
+ with out-of-band metadata containing the mentioned user's UUID/number.
128
+ """
129
+ if not mentions or "\uFFFC" not in text:
130
+ return text
131
+ # Sort mentions by start position (reverse) to replace from end to start
132
+ # so indices don't shift as we replace
133
+ sorted_mentions = sorted(mentions, key=lambda m: m.get("start", 0), reverse=True)
134
+ for mention in sorted_mentions:
135
+ start = mention.get("start", 0)
136
+ length = mention.get("length", 1)
137
+ # Use the mention's number or UUID as the replacement
138
+ identifier = mention.get("number") or mention.get("uuid") or "user"
139
+ replacement = f"@{identifier}"
140
+ text = text[:start] + replacement + text[start + length:]
141
+ return text
142
+
143
+
144
+ def _is_signal_service_id(value: str) -> bool:
145
+ """Return True if *value* already looks like a Signal service identifier."""
146
+ if not value:
147
+ return False
148
+ if value.startswith("PNI:") or value.startswith("u:"):
149
+ return True
150
+ try:
151
+ uuid.UUID(value)
152
+ return True
153
+ except (ValueError, AttributeError, TypeError):
154
+ return False
155
+
156
+
157
+ def _looks_like_e164_number(value: str) -> bool:
158
+ """Return True for a plausible E.164 phone number."""
159
+ if not value or not value.startswith("+"):
160
+ return False
161
+ digits = value[1:]
162
+ return digits.isdigit() and 7 <= len(digits) <= 15
163
+
164
+
165
+ def check_signal_requirements() -> bool:
166
+ """Check if Signal is configured (has URL and account)."""
167
+ return bool(os.getenv("SIGNAL_HTTP_URL") and os.getenv("SIGNAL_ACCOUNT"))
168
+
169
+
170
+ # ---------------------------------------------------------------------------
171
+ # Signal Adapter
172
+ # ---------------------------------------------------------------------------
173
+
174
+ class SignalAdapter(BasePlatformAdapter):
175
+ """Signal messenger adapter using signal-cli HTTP daemon."""
176
+
177
+ platform = Platform.SIGNAL
178
+ # Signal has no real edit API for already-sent messages. Mark it explicitly
179
+ # so streaming suppresses the visible cursor instead of leaving a stale tofu
180
+ # square behind in chat clients when edit attempts fail.
181
+ SUPPORTS_MESSAGE_EDITING = False
182
+
183
+ def __init__(self, config: PlatformConfig):
184
+ super().__init__(config, Platform.SIGNAL)
185
+
186
+ extra = config.extra or {}
187
+ self.http_url = extra.get("http_url", "http://127.0.0.1:8080").rstrip("/")
188
+ self.account = extra.get("account", "")
189
+ self.ignore_stories = extra.get("ignore_stories", True)
190
+
191
+ # Parse allowlists — group policy is derived from presence of group allowlist
192
+ group_allowed_str = os.getenv("SIGNAL_GROUP_ALLOWED_USERS", "")
193
+ self.group_allow_from = set(_parse_comma_list(group_allowed_str))
194
+
195
+ # DM allowlist — mirrors SIGNAL_ALLOWED_USERS checked by run.py.
196
+ # Stored here so the reaction hooks can skip unauthorized senders
197
+ # (reactions fire before run.py's auth gate, so without this check
198
+ # every inbound DM from any contact gets a 👀 reaction).
199
+ # "*" means all users allowed (open mode); empty means no restriction
200
+ # recorded at adapter level (run.py still enforces auth separately).
201
+ dm_allowed_str = os.getenv("SIGNAL_ALLOWED_USERS", "*")
202
+ self.dm_allow_from = set(_parse_comma_list(dm_allowed_str))
203
+
204
+ # HTTP client
205
+ self.client: Optional[httpx.AsyncClient] = None
206
+
207
+ # Background tasks
208
+ self._sse_task: Optional[asyncio.Task] = None
209
+ self._health_monitor_task: Optional[asyncio.Task] = None
210
+ self._typing_tasks: Dict[str, asyncio.Task] = {}
211
+ # Per-chat typing-indicator backoff. When signal-cli reports
212
+ # NETWORK_FAILURE (recipient offline / unroutable), base.py's
213
+ # _keep_typing refresh loop would otherwise hammer sendTyping every
214
+ # ~2s indefinitely, producing WARNING-level log spam and pointless
215
+ # RPC traffic. We track consecutive failures per chat and skip the
216
+ # RPC during a cooldown window instead.
217
+ self._typing_failures: Dict[str, int] = {}
218
+ self._typing_skip_until: Dict[str, float] = {}
219
+ self._running = False
220
+ self._last_sse_activity = 0.0
221
+ self._sse_response: Optional[httpx.Response] = None
222
+
223
+ # Normalize account for self-message filtering
224
+ self._account_normalized = self.account.strip()
225
+
226
+ # Track recently sent message timestamps to prevent echo-back loops
227
+ # in Note to Self / self-chat mode (mirrors WhatsApp recentlySentIds)
228
+ self._recent_sent_timestamps: set = set()
229
+ self._max_recent_timestamps = 50
230
+ # Signal increasingly exposes ACI/PNI UUIDs as stable recipient IDs.
231
+ # Keep a best-effort mapping so outbound sends can upgrade from a
232
+ # phone number to the corresponding UUID when signal-cli prefers it.
233
+ self._recipient_uuid_by_number: Dict[str, str] = {}
234
+ self._recipient_number_by_uuid: Dict[str, str] = {}
235
+ self._recipient_cache_lock = asyncio.Lock()
236
+
237
+ logger.info("Signal adapter initialized: url=%s account=%s groups=%s",
238
+ self.http_url, redact_phone(self.account),
239
+ "enabled" if self.group_allow_from else "disabled")
240
+
241
+ # ------------------------------------------------------------------
242
+ # Lifecycle
243
+ # ------------------------------------------------------------------
244
+
245
+ async def connect(self) -> bool:
246
+ """Connect to signal-cli daemon and start SSE listener."""
247
+ if not self.http_url or not self.account:
248
+ logger.error("Signal: SIGNAL_HTTP_URL and SIGNAL_ACCOUNT are required")
249
+ return False
250
+
251
+ # Acquire scoped lock to prevent duplicate Signal listeners for the same phone
252
+ lock_acquired = False
253
+ try:
254
+ if not self._acquire_platform_lock('signal-phone', self.account, 'Signal account'):
255
+ return False
256
+ lock_acquired = True
257
+ except Exception as e:
258
+ logger.warning("Signal: Could not acquire phone lock (non-fatal): %s", e)
259
+
260
+ # Tighter keepalive so idle CLOSE_WAIT drains promptly (#18451).
261
+ from gateway.platforms._http_client_limits import platform_httpx_limits
262
+ self.client = httpx.AsyncClient(timeout=30.0, limits=platform_httpx_limits())
263
+ try:
264
+ # Health check — verify signal-cli daemon is reachable
265
+ try:
266
+ resp = await self.client.get(f"{self.http_url}/api/v1/check", timeout=10.0)
267
+ if resp.status_code != 200:
268
+ logger.error("Signal: health check failed (status %d)", resp.status_code)
269
+ return False
270
+ except Exception as e:
271
+ logger.error("Signal: cannot reach signal-cli at %s: %s", self.http_url, e)
272
+ return False
273
+
274
+ self._running = True
275
+ self._last_sse_activity = time.time()
276
+ self._sse_task = asyncio.create_task(self._sse_listener())
277
+ self._health_monitor_task = asyncio.create_task(self._health_monitor())
278
+
279
+ logger.info("Signal: connected to %s", self.http_url)
280
+ return True
281
+ finally:
282
+ if not self._running:
283
+ if self.client:
284
+ await self.client.aclose()
285
+ self.client = None
286
+ if lock_acquired:
287
+ self._release_platform_lock()
288
+
289
+ async def disconnect(self) -> None:
290
+ """Stop SSE listener and clean up."""
291
+ self._running = False
292
+
293
+ if self._sse_task:
294
+ self._sse_task.cancel()
295
+ try:
296
+ await self._sse_task
297
+ except asyncio.CancelledError:
298
+ pass
299
+
300
+ if self._health_monitor_task:
301
+ self._health_monitor_task.cancel()
302
+ try:
303
+ await self._health_monitor_task
304
+ except asyncio.CancelledError:
305
+ pass
306
+
307
+ # Cancel all typing tasks
308
+ for task in self._typing_tasks.values():
309
+ task.cancel()
310
+ self._typing_tasks.clear()
311
+
312
+ if self.client:
313
+ await self.client.aclose()
314
+ self.client = None
315
+
316
+ self._release_platform_lock()
317
+
318
+ logger.info("Signal: disconnected")
319
+
320
+ # ------------------------------------------------------------------
321
+ # SSE Streaming (inbound messages)
322
+ # ------------------------------------------------------------------
323
+
324
+ async def _sse_listener(self) -> None:
325
+ """Listen for SSE events from signal-cli daemon."""
326
+ url = f"{self.http_url}/api/v1/events?account={quote(self.account, safe='')}"
327
+ backoff = SSE_RETRY_DELAY_INITIAL
328
+
329
+ while self._running:
330
+ try:
331
+ logger.debug("Signal SSE: connecting to %s", url)
332
+ async with self.client.stream(
333
+ "GET", url,
334
+ headers={"Accept": "text/event-stream"},
335
+ timeout=None,
336
+ ) as response:
337
+ self._sse_response = response
338
+ backoff = SSE_RETRY_DELAY_INITIAL # Reset on successful connection
339
+ self._last_sse_activity = time.time()
340
+ logger.info("Signal SSE: connected")
341
+
342
+ buffer = ""
343
+ async for chunk in response.aiter_text():
344
+ if not self._running:
345
+ break
346
+ buffer += chunk
347
+ while "\n" in buffer:
348
+ line, buffer = buffer.split("\n", 1)
349
+ line = line.strip()
350
+ if not line:
351
+ continue
352
+ # SSE keepalive comments (":") prove the connection
353
+ # is alive — update activity so the health monitor
354
+ # doesn't report false idle warnings.
355
+ if line.startswith(":"):
356
+ self._last_sse_activity = time.time()
357
+ continue
358
+ # Parse SSE data lines
359
+ if line.startswith("data:"):
360
+ data_str = line[5:].strip()
361
+ if not data_str:
362
+ continue
363
+ self._last_sse_activity = time.time()
364
+ try:
365
+ data = json.loads(data_str)
366
+ await self._handle_envelope(data)
367
+ except json.JSONDecodeError:
368
+ logger.debug("Signal SSE: invalid JSON: %s", data_str[:100])
369
+ except Exception:
370
+ logger.exception("Signal SSE: error handling event")
371
+
372
+ except asyncio.CancelledError:
373
+ break
374
+ except httpx.HTTPError as e:
375
+ if self._running:
376
+ logger.warning("Signal SSE: HTTP error: %s (reconnecting in %.0fs)", e, backoff)
377
+ except Exception as e:
378
+ if self._running:
379
+ logger.warning("Signal SSE: error: %s (reconnecting in %.0fs)", e, backoff)
380
+
381
+ if self._running:
382
+ # Add 20% jitter to prevent thundering herd on reconnection
383
+ jitter = backoff * 0.2 * random.random()
384
+ await asyncio.sleep(backoff + jitter)
385
+ backoff = min(backoff * 2, SSE_RETRY_DELAY_MAX)
386
+
387
+ self._sse_response = None
388
+
389
+ # ------------------------------------------------------------------
390
+ # Health Monitor
391
+ # ------------------------------------------------------------------
392
+
393
+ async def _health_monitor(self) -> None:
394
+ """Monitor SSE connection health and force reconnect if stale."""
395
+ while self._running:
396
+ await asyncio.sleep(HEALTH_CHECK_INTERVAL)
397
+ if not self._running:
398
+ break
399
+
400
+ elapsed = time.time() - self._last_sse_activity
401
+ if elapsed > HEALTH_CHECK_STALE_THRESHOLD:
402
+ logger.warning("Signal: SSE idle for %.0fs, checking daemon health", elapsed)
403
+ try:
404
+ resp = await self.client.get(
405
+ f"{self.http_url}/api/v1/check", timeout=10.0
406
+ )
407
+ if resp.status_code == 200:
408
+ # Daemon is alive but SSE is idle — update activity to
409
+ # avoid repeated warnings (connection may just be quiet)
410
+ self._last_sse_activity = time.time()
411
+ logger.debug("Signal: daemon healthy, SSE idle")
412
+ else:
413
+ logger.warning("Signal: health check failed (%d), forcing reconnect", resp.status_code)
414
+ self._force_reconnect()
415
+ except Exception as e:
416
+ logger.warning("Signal: health check error: %s, forcing reconnect", e)
417
+ self._force_reconnect()
418
+
419
+ def _force_reconnect(self) -> None:
420
+ """Force SSE reconnection by closing the current response."""
421
+ if self._sse_response and not self._sse_response.is_stream_consumed:
422
+ try:
423
+ task = asyncio.create_task(self._sse_response.aclose())
424
+ self._background_tasks.add(task)
425
+ task.add_done_callback(self._background_tasks.discard)
426
+ except Exception:
427
+ pass
428
+ self._sse_response = None
429
+
430
+ # ------------------------------------------------------------------
431
+ # Message Handling
432
+ # ------------------------------------------------------------------
433
+
434
+ async def _handle_envelope(self, envelope: dict) -> None:
435
+ """Process an incoming signal-cli envelope."""
436
+ # Unwrap nested envelope if present
437
+ envelope_data = envelope.get("envelope", envelope)
438
+
439
+ # Handle syncMessage: extract "Note to Self" messages (sent to own account)
440
+ # while still filtering other sync events (read receipts, typing, etc.)
441
+ is_note_to_self = False
442
+ if "syncMessage" in envelope_data:
443
+ sync_msg = envelope_data.get("syncMessage")
444
+ if sync_msg and isinstance(sync_msg, dict):
445
+ sent_msg = sync_msg.get("sentMessage")
446
+ if sent_msg and isinstance(sent_msg, dict):
447
+ dest = sent_msg.get("destinationNumber") or sent_msg.get("destination")
448
+ sent_ts = sent_msg.get("timestamp")
449
+ sent_msg_group_info = sent_msg.get("groupInfo") or {}
450
+ sent_msg_group_id = sent_msg_group_info.get("groupId") if sent_msg_group_info else None
451
+ if dest == self._account_normalized or sent_msg_group_id:
452
+ # Check if this is an echo of our own outbound reply
453
+ if sent_ts and sent_ts in self._recent_sent_timestamps:
454
+ self._recent_sent_timestamps.discard(sent_ts)
455
+ return
456
+ # Genuine user Note to Self — promote to dataMessage
457
+ is_note_to_self = True
458
+ envelope_data = {**envelope_data, "dataMessage": sent_msg}
459
+ if not is_note_to_self:
460
+ return
461
+
462
+ # Extract sender info
463
+ sender = (
464
+ envelope_data.get("sourceNumber")
465
+ or envelope_data.get("sourceUuid")
466
+ or envelope_data.get("source")
467
+ )
468
+ sender_name = envelope_data.get("sourceName", "")
469
+ sender_uuid = envelope_data.get("sourceUuid", "")
470
+ self._remember_recipient_identifiers(sender, sender_uuid)
471
+
472
+ if not sender:
473
+ logger.debug("Signal: ignoring envelope with no sender")
474
+ return
475
+
476
+ # Self-message filtering — prevent reply loops (but allow Note to Self)
477
+ if self._account_normalized and sender == self._account_normalized and not is_note_to_self:
478
+ return
479
+
480
+ # Filter stories
481
+ if self.ignore_stories and envelope_data.get("storyMessage"):
482
+ return
483
+
484
+ # Get data message — also check editMessage (edited messages contain
485
+ # their updated dataMessage inside editMessage.dataMessage)
486
+ data_message = (
487
+ envelope_data.get("dataMessage")
488
+ or (envelope_data.get("editMessage") or {}).get("dataMessage")
489
+ )
490
+ if not data_message:
491
+ return
492
+
493
+ # Check for group message
494
+ group_info = data_message.get("groupInfo")
495
+ group_id = group_info.get("groupId") if group_info else None
496
+ is_group = bool(group_id)
497
+
498
+ # Group message filtering — derived from SIGNAL_GROUP_ALLOWED_USERS:
499
+ # - No env var set → groups disabled (default safe behavior)
500
+ # - Env var set with group IDs → only those groups allowed
501
+ # - Env var set with "*" → all groups allowed
502
+ # DM auth is fully handled by run.py (_is_user_authorized)
503
+ if is_group:
504
+ if not self.group_allow_from:
505
+ logger.debug("Signal: ignoring group message (no SIGNAL_GROUP_ALLOWED_USERS)")
506
+ return
507
+ if "*" not in self.group_allow_from and group_id not in self.group_allow_from:
508
+ logger.debug("Signal: group %s not in allowlist", group_id[:8] if group_id else "?")
509
+ return
510
+
511
+ # Build chat info
512
+ chat_id = sender if not is_group else f"group:{group_id}"
513
+ chat_type = "group" if is_group else "dm"
514
+
515
+ # Extract text and render mentions
516
+ text = data_message.get("message", "")
517
+ mentions = data_message.get("mentions", [])
518
+ if text and mentions:
519
+ text = _render_mentions(text, mentions)
520
+
521
+ # Extract quote (reply-to) context from Signal dataMessage
522
+ quote_data = data_message.get("quote") or {}
523
+ reply_to_id = str(quote_data.get("id")) if quote_data.get("id") else None
524
+ reply_to_text = quote_data.get("text")
525
+
526
+ # Process attachments
527
+ attachments_data = data_message.get("attachments", [])
528
+ media_urls = []
529
+ media_types = []
530
+
531
+ if attachments_data and not getattr(self, "ignore_attachments", False):
532
+ for att in attachments_data:
533
+ att_id = att.get("id")
534
+ att_size = att.get("size", 0)
535
+ if not att_id:
536
+ continue
537
+ if att_size > SIGNAL_MAX_ATTACHMENT_SIZE:
538
+ logger.warning("Signal: attachment too large (%d bytes), skipping", att_size)
539
+ continue
540
+ try:
541
+ cached_path, ext = await self._fetch_attachment(att_id)
542
+ if cached_path:
543
+ # Use contentType from Signal if available, else map from extension
544
+ content_type = att.get("contentType") or _ext_to_mime(ext)
545
+ media_urls.append(cached_path)
546
+ media_types.append(content_type)
547
+ except Exception:
548
+ logger.exception("Signal: failed to fetch attachment %s", att_id)
549
+
550
+ # Skip envelopes with no meaningful content (no text, no attachments).
551
+ # Catches profile key updates, empty messages, and other metadata-only
552
+ # envelopes that still carry a dataMessage wrapper but have nothing
553
+ # worth processing. See issue: signal-cli logs "Profile key update" +
554
+ # Hermes receives msg='' triggering a full agent turn for nothing.
555
+ if (not text or not text.strip()) and not media_urls:
556
+ logger.debug(
557
+ "Signal: skipping contentless envelope from %s (%d attachments)",
558
+ redact_phone(sender), len(media_urls) if media_urls else 0,
559
+ )
560
+ return
561
+
562
+ # Build session source
563
+ source = self.build_source(
564
+ chat_id=chat_id,
565
+ chat_name=group_info.get("groupName") if group_info else sender_name,
566
+ chat_type=chat_type,
567
+ user_id=sender,
568
+ user_name=sender_name or sender,
569
+ user_id_alt=sender_uuid if sender_uuid else None,
570
+ chat_id_alt=group_id if is_group else None,
571
+ )
572
+
573
+ # Determine message type from media
574
+ msg_type = MessageType.TEXT
575
+ if media_types:
576
+ if any(mt.startswith("audio/") for mt in media_types):
577
+ msg_type = MessageType.VOICE
578
+ elif any(mt.startswith("image/") for mt in media_types):
579
+ msg_type = MessageType.PHOTO
580
+
581
+ # Parse timestamp from envelope data (milliseconds since epoch)
582
+ ts_ms = envelope_data.get("timestamp", 0)
583
+ if ts_ms:
584
+ try:
585
+ timestamp = datetime.fromtimestamp(ts_ms / 1000, tz=timezone.utc)
586
+ except (ValueError, OSError):
587
+ timestamp = datetime.now(tz=timezone.utc)
588
+ else:
589
+ timestamp = datetime.now(tz=timezone.utc)
590
+
591
+ # Build and dispatch event.
592
+ # Store raw envelope data in raw_message so on_processing_start/complete
593
+ # can extract targetAuthor + targetTimestamp for sendReaction.
594
+ event = MessageEvent(
595
+ source=source,
596
+ text=text or "",
597
+ message_type=msg_type,
598
+ media_urls=media_urls,
599
+ media_types=media_types,
600
+ timestamp=timestamp,
601
+ raw_message={"sender": sender, "timestamp_ms": ts_ms},
602
+ reply_to_message_id=reply_to_id,
603
+ reply_to_text=reply_to_text,
604
+ )
605
+
606
+ logger.debug("Signal: message from %s in %s: %s",
607
+ redact_phone(sender), chat_id[:20], (text or "")[:50])
608
+
609
+ await self.handle_message(event)
610
+
611
+ def _remember_recipient_identifiers(self, number: Optional[str], service_id: Optional[str]) -> None:
612
+ """Cache any number↔UUID mapping observed from Signal envelopes."""
613
+ if not number or not service_id or not _is_signal_service_id(service_id):
614
+ return
615
+ self._recipient_uuid_by_number[number] = service_id
616
+ self._recipient_number_by_uuid[service_id] = number
617
+
618
+ def _extract_contact_uuid(self, contact: Any, phone_number: str) -> Optional[str]:
619
+ """Best-effort extraction of a Signal service ID from listContacts output."""
620
+ if not isinstance(contact, dict):
621
+ return None
622
+
623
+ number = contact.get("number")
624
+ recipient = contact.get("recipient")
625
+ service_id = contact.get("uuid") or contact.get("serviceId")
626
+ if not service_id:
627
+ profile = contact.get("profile")
628
+ if isinstance(profile, dict):
629
+ service_id = profile.get("serviceId") or profile.get("uuid")
630
+
631
+ if service_id and _is_signal_service_id(service_id):
632
+ matches_number = number == phone_number or recipient == phone_number
633
+ if matches_number:
634
+ return service_id
635
+ return None
636
+
637
+ async def _resolve_recipient(self, chat_id: str) -> str:
638
+ """Return the preferred Signal recipient identifier for a direct chat."""
639
+ if (
640
+ not chat_id
641
+ or chat_id.startswith("group:")
642
+ or _is_signal_service_id(chat_id)
643
+ or not _looks_like_e164_number(chat_id)
644
+ ):
645
+ return chat_id
646
+
647
+ cached = self._recipient_uuid_by_number.get(chat_id)
648
+ if cached:
649
+ return cached
650
+
651
+ async with self._recipient_cache_lock:
652
+ cached = self._recipient_uuid_by_number.get(chat_id)
653
+ if cached:
654
+ return cached
655
+
656
+ contacts = await self._rpc("listContacts", {
657
+ "account": self.account,
658
+ "allRecipients": True,
659
+ })
660
+ if isinstance(contacts, list):
661
+ for contact in contacts:
662
+ number = contact.get("number") if isinstance(contact, dict) else None
663
+ service_id = self._extract_contact_uuid(contact, chat_id)
664
+ if number and service_id:
665
+ self._remember_recipient_identifiers(number, service_id)
666
+
667
+ return self._recipient_uuid_by_number.get(chat_id, chat_id)
668
+
669
+ # ------------------------------------------------------------------
670
+ # Attachment Handling
671
+ # ------------------------------------------------------------------
672
+
673
+ async def _fetch_attachment(self, attachment_id: str) -> tuple:
674
+ """Fetch an attachment via JSON-RPC and cache it. Returns (path, ext)."""
675
+ result = await self._rpc("getAttachment", {
676
+ "account": self.account,
677
+ "id": attachment_id,
678
+ })
679
+
680
+ if not result:
681
+ return None, ""
682
+
683
+ # Handle dict response (signal-cli returns {"data": "base64..."})
684
+ if isinstance(result, dict):
685
+ result = result.get("data")
686
+ if not result:
687
+ logger.warning("Signal: attachment response missing 'data' key")
688
+ return None, ""
689
+
690
+ # Result is base64-encoded file content
691
+ raw_data = base64.b64decode(result)
692
+ ext = _guess_extension(raw_data)
693
+
694
+ if _is_image_ext(ext):
695
+ path = cache_image_from_bytes(raw_data, ext)
696
+ elif _is_audio_ext(ext):
697
+ path = cache_audio_from_bytes(raw_data, ext)
698
+ else:
699
+ path = cache_document_from_bytes(raw_data, ext)
700
+
701
+ return path, ext
702
+
703
+ # ------------------------------------------------------------------
704
+ # JSON-RPC Communication
705
+ # ------------------------------------------------------------------
706
+
707
+ async def _rpc(
708
+ self,
709
+ method: str,
710
+ params: dict,
711
+ rpc_id: str = None,
712
+ *,
713
+ log_failures: bool = True,
714
+ raise_on_rate_limit: bool = False,
715
+ timeout: float = 30.0,
716
+ ) -> Any:
717
+ """Send a JSON-RPC 2.0 request to signal-cli daemon.
718
+
719
+ When ``log_failures=False``, error and exception paths log at DEBUG
720
+ instead of WARNING — used by the typing-indicator path to silence
721
+ repeated NETWORK_FAILURE spam for unreachable recipients while
722
+ still preserving visibility for the first occurrence and for
723
+ unrelated RPCs.
724
+
725
+ When ``raise_on_rate_limit=True``, a Signal ``[429]`` /
726
+ ``RateLimitException`` response raises ``SignalRateLimitError``
727
+ instead of being swallowed — lets callers (multi-attachment send)
728
+ opt into backoff-retry without changing default behaviour.
729
+ """
730
+ if not self.client:
731
+ logger.warning("Signal: RPC called but client not connected")
732
+ return None
733
+
734
+ if rpc_id is None:
735
+ rpc_id = f"{method}_{int(time.time() * 1000)}"
736
+
737
+ payload = {
738
+ "jsonrpc": "2.0",
739
+ "method": method,
740
+ "params": params,
741
+ "id": rpc_id,
742
+ }
743
+
744
+ try:
745
+ resp = await self.client.post(
746
+ f"{self.http_url}/api/v1/rpc",
747
+ json=payload,
748
+ timeout=timeout,
749
+ )
750
+ resp.raise_for_status()
751
+ data = resp.json()
752
+
753
+ if "error" in data:
754
+ err = data["error"]
755
+ if raise_on_rate_limit:
756
+ if _is_signal_rate_limit_error(err):
757
+ err_msg = str(err.get("message", "")) if isinstance(err, dict) else str(err)
758
+ retry_after = _extract_retry_after_seconds(err)
759
+ raise SignalRateLimitError(err_msg, retry_after=retry_after)
760
+ if log_failures:
761
+ logger.warning("Signal RPC error (%s): %s", method, err)
762
+ else:
763
+ logger.debug("Signal RPC error (%s): %s", method, err)
764
+ return None
765
+
766
+ return data.get("result")
767
+
768
+ except SignalRateLimitError:
769
+ raise
770
+ except Exception as e:
771
+ if log_failures:
772
+ logger.warning("Signal RPC %s failed: %s", method, e)
773
+ else:
774
+ logger.debug("Signal RPC %s failed: %s", method, e)
775
+ return None
776
+
777
+ # ------------------------------------------------------------------
778
+ # Formatting — markdown → Signal body ranges
779
+ # ------------------------------------------------------------------
780
+
781
+ @staticmethod
782
+ def _markdown_to_signal(text: str) -> tuple:
783
+ """Convert markdown to plain text + Signal textStyles list.
784
+
785
+ Signal doesn't render markdown. Instead it uses ``bodyRanges``
786
+ (exposed by signal-cli as ``textStyle`` / ``textStyles`` params)
787
+ with the format ``start:length:STYLE``.
788
+
789
+ Positions are measured in **UTF-16 code units** (not Python code
790
+ points) because that's what the Signal protocol uses.
791
+
792
+ Supported styles: BOLD, ITALIC, STRIKETHROUGH, MONOSPACE.
793
+ (Signal's SPOILER style is not currently mapped — no standard
794
+ markdown syntax for it; would need ``||spoiler||`` parsing.)
795
+
796
+ Returns ``(plain_text, styles_list)`` where *styles_list* may be
797
+ empty if there's nothing to format.
798
+ """
799
+ import re
800
+
801
+ def _utf16_len(s: str) -> int:
802
+ """Length of *s* in UTF-16 code units."""
803
+ return len(s.encode("utf-16-le")) // 2
804
+
805
+ # Pre-process: normalize whitespace before any position tracking
806
+ # so later operations don't invalidate recorded offsets.
807
+ text = re.sub(r"\n{3,}", "\n\n", text)
808
+ text = text.strip()
809
+
810
+ styles: list = []
811
+
812
+ # --- Phase 1: fenced code blocks ```...``` → MONOSPACE ---
813
+ _CB = re.compile(r"```[a-zA-Z0-9_+-]*\n?(.*?)```", re.DOTALL)
814
+ while m := _CB.search(text):
815
+ inner = m.group(1).rstrip("\n")
816
+ start = m.start()
817
+ text = text[: m.start()] + inner + text[m.end() :]
818
+ styles.append((start, len(inner), "MONOSPACE"))
819
+
820
+ # --- Phase 2: heading markers # Foo → Foo (BOLD) ---
821
+ _HEADING = re.compile(r"^#{1,6}\s+", re.MULTILINE)
822
+ new_text = ""
823
+ last_end = 0
824
+ for m in _HEADING.finditer(text):
825
+ new_text += text[last_end : m.start()]
826
+ last_end = m.end()
827
+ eol = text.find("\n", m.end())
828
+ if eol == -1:
829
+ eol = len(text)
830
+ heading_text = text[m.end() : eol]
831
+ start = len(new_text)
832
+ new_text += heading_text
833
+ styles.append((start, len(heading_text), "BOLD"))
834
+ last_end = eol
835
+ new_text += text[last_end:]
836
+ text = new_text
837
+
838
+ # --- Phase 3: inline patterns (single-pass to avoid offset drift) ---
839
+ # The old code processed each pattern sequentially, stripping markers
840
+ # and recording positions per-pass. Later passes shifted text without
841
+ # adjusting earlier positions → bold/italic landed mid-word.
842
+ #
843
+ # Fix: collect ALL non-overlapping matches first, then strip every
844
+ # marker in one pass so positions are computed against the final text.
845
+ _PATTERNS = [
846
+ (re.compile(r"\*\*(.+?)\*\*", re.DOTALL), "BOLD"),
847
+ (re.compile(r"__(.+?)__", re.DOTALL), "BOLD"),
848
+ (re.compile(r"~~(.+?)~~", re.DOTALL), "STRIKETHROUGH"),
849
+ (re.compile(r"`(.+?)`"), "MONOSPACE"),
850
+ (re.compile(r"(?<!\*)\*(?!\*| )(.+?)(?<!\*)\*(?!\*)"), "ITALIC"),
851
+ (re.compile(r"(?<!\w)_(?!_)(.+?)(?<!_)_(?!\w)"), "ITALIC"),
852
+ ]
853
+
854
+ # Collect all non-overlapping matches (earlier patterns win ties).
855
+ all_matches: list = [] # (start, end, g1_start, g1_end, style)
856
+ occupied: list = [] # (start, end) intervals already claimed
857
+ for pat, style in _PATTERNS:
858
+ for m in pat.finditer(text):
859
+ ms, me = m.start(), m.end()
860
+ if not any(ms < oe and me > os for os, oe in occupied):
861
+ all_matches.append((ms, me, m.start(1), m.end(1), style))
862
+ occupied.append((ms, me))
863
+ all_matches.sort()
864
+
865
+ # Build removal list so we can adjust Phase 1/2 styles.
866
+ # Each match removes its prefix markers (start..g1_start) and
867
+ # suffix markers (g1_end..end).
868
+ removals: list = [] # (position, length) sorted
869
+ for ms, me, g1s, g1e, _ in all_matches:
870
+ if g1s > ms:
871
+ removals.append((ms, g1s - ms))
872
+ if me > g1e:
873
+ removals.append((g1e, me - g1e))
874
+ removals.sort()
875
+
876
+ # Adjust Phase 1/2 styles for characters about to be removed.
877
+ def _adj(pos: int) -> int:
878
+ shift = 0
879
+ for rp, rl in removals:
880
+ if rp < pos:
881
+ shift += min(rl, pos - rp)
882
+ else:
883
+ break
884
+ return pos - shift
885
+
886
+ adjusted_prior: list = []
887
+ for s, l, st in styles:
888
+ ns = _adj(s)
889
+ ne = _adj(s + l)
890
+ if ne > ns:
891
+ adjusted_prior.append((ns, ne - ns, st))
892
+
893
+ # Strip all inline markers in one pass → positions are correct.
894
+ result = ""
895
+ last_end = 0
896
+ inline_styles: list = []
897
+ for ms, me, g1s, g1e, sty in all_matches:
898
+ result += text[last_end:ms]
899
+ pos = len(result)
900
+ inner = text[g1s:g1e]
901
+ result += inner
902
+ inline_styles.append((pos, len(inner), sty))
903
+ last_end = me
904
+ result += text[last_end:]
905
+ text = result
906
+
907
+ styles = adjusted_prior + inline_styles
908
+
909
+ # Convert code-point offsets → UTF-16 code-unit offsets
910
+ style_strings = []
911
+ for cp_start, cp_len, stype in sorted(styles):
912
+ # Safety: skip any out-of-bounds styles
913
+ if cp_start < 0 or cp_start + cp_len > len(text):
914
+ continue
915
+ u16_start = _utf16_len(text[:cp_start])
916
+ u16_len = _utf16_len(text[cp_start : cp_start + cp_len])
917
+ style_strings.append(f"{u16_start}:{u16_len}:{stype}")
918
+
919
+ return text, style_strings
920
+
921
+ def format_message(self, content: str) -> str:
922
+ """Strip markdown for plain-text fallback (used by base class).
923
+
924
+ The actual rich formatting happens in send() via _markdown_to_signal().
925
+ """
926
+ # This is only called if someone uses the base-class send path.
927
+ # Our send() override bypasses this entirely.
928
+ return content
929
+
930
+ # ------------------------------------------------------------------
931
+ # Sending
932
+ # ------------------------------------------------------------------
933
+
934
+ async def send(
935
+ self,
936
+ chat_id: str,
937
+ content: str,
938
+ reply_to: Optional[str] = None,
939
+ metadata: Optional[Dict[str, Any]] = None,
940
+ ) -> SendResult:
941
+ """Send a text message with native Signal formatting."""
942
+ await self._stop_typing_indicator(chat_id)
943
+
944
+ plain_text, text_styles = self._markdown_to_signal(content)
945
+
946
+ params: Dict[str, Any] = {
947
+ "account": self.account,
948
+ "message": plain_text,
949
+ }
950
+
951
+ if text_styles:
952
+ if len(text_styles) == 1:
953
+ params["textStyle"] = text_styles[0]
954
+ else:
955
+ params["textStyles"] = text_styles
956
+
957
+ if chat_id.startswith("group:"):
958
+ params["groupId"] = chat_id[6:]
959
+ else:
960
+ params["recipient"] = [await self._resolve_recipient(chat_id)]
961
+
962
+ result = await self._rpc("send", params)
963
+
964
+ if result is not None:
965
+ self._track_sent_timestamp(result)
966
+ # Signal has no editable message identifier. Returning None keeps the
967
+ # stream consumer on the non-edit fallback path instead of pretending
968
+ # future edits can remove an in-progress cursor from the chat thread.
969
+ return SendResult(success=True, message_id=None)
970
+ return SendResult(success=False, error="RPC send failed")
971
+
972
+ def _track_sent_timestamp(self, rpc_result) -> None:
973
+ """Record outbound message timestamp for echo-back filtering."""
974
+ ts = rpc_result.get("timestamp") if isinstance(rpc_result, dict) else None
975
+ if ts:
976
+ self._recent_sent_timestamps.add(ts)
977
+ if len(self._recent_sent_timestamps) > self._max_recent_timestamps:
978
+ self._recent_sent_timestamps.pop()
979
+
980
+ async def send_typing(self, chat_id: str, metadata=None) -> None:
981
+ """Send a typing indicator.
982
+
983
+ base.py's ``_keep_typing`` refresh loop calls this every ~2s while
984
+ the agent is processing. If signal-cli returns NETWORK_FAILURE for
985
+ this recipient (offline, unroutable, group membership lost, etc.)
986
+ the unmitigated behaviour is: a WARNING log every 2 seconds for as
987
+ long as the agent keeps running. Instead we:
988
+
989
+ - silence the WARNING after the first consecutive failure (subsequent
990
+ attempts log at DEBUG) so transport issues are still visible once
991
+ but don't flood the log,
992
+ - skip the RPC entirely during an exponential cooldown window once
993
+ three consecutive failures have happened, so we stop hammering
994
+ signal-cli with requests it can't deliver.
995
+
996
+ A successful sendTyping clears the counters.
997
+ """
998
+ now = time.monotonic()
999
+ skip_until = self._typing_skip_until.get(chat_id, 0.0)
1000
+ if now < skip_until:
1001
+ return
1002
+
1003
+ params: Dict[str, Any] = {
1004
+ "account": self.account,
1005
+ }
1006
+
1007
+ if chat_id.startswith("group:"):
1008
+ params["groupId"] = chat_id[6:]
1009
+ else:
1010
+ params["recipient"] = [await self._resolve_recipient(chat_id)]
1011
+
1012
+ fails = self._typing_failures.get(chat_id, 0)
1013
+ result = await self._rpc(
1014
+ "sendTyping",
1015
+ params,
1016
+ rpc_id="typing",
1017
+ log_failures=(fails == 0),
1018
+ )
1019
+
1020
+ if result is None:
1021
+ fails += 1
1022
+ self._typing_failures[chat_id] = fails
1023
+ # After 3 consecutive failures, back off exponentially (16s,
1024
+ # 32s, 60s cap) to stop spamming signal-cli for a recipient
1025
+ # that clearly isn't reachable right now.
1026
+ if fails >= 3:
1027
+ backoff = min(60.0, 16.0 * (2 ** (fails - 3)))
1028
+ self._typing_skip_until[chat_id] = now + backoff
1029
+ else:
1030
+ self._typing_failures.pop(chat_id, None)
1031
+ self._typing_skip_until.pop(chat_id, None)
1032
+
1033
+ async def send_multiple_images(
1034
+ self,
1035
+ chat_id: str,
1036
+ images: List[Tuple[str, str]],
1037
+ metadata: Optional[Dict[str, Any]] = None,
1038
+ human_delay: float = 0.0,
1039
+ ) -> None:
1040
+ """Send a batch of images via chunked Signal RPC calls.
1041
+
1042
+ Per-image alt texts are dropped — Signal's send RPC only carries
1043
+ one shared message body. Bad images (download failure, missing
1044
+ file, oversize) are skipped with a warning so one bad URL
1045
+ doesn't lose the rest of the batch. ``human_delay`` is ignored:
1046
+ the rate-limit scheduler handles inter-batch pacing.
1047
+ """
1048
+ if not images:
1049
+ return
1050
+
1051
+ scheduler = get_scheduler()
1052
+ logger.info(
1053
+ "Signal send_multiple_images: received %d image(s) for %s — "
1054
+ "scheduler state: %s",
1055
+ len(images), chat_id[:30], scheduler.state(),
1056
+ )
1057
+
1058
+ await self._stop_typing_indicator(chat_id)
1059
+
1060
+ attachments: List[str] = []
1061
+ skipped_download = 0
1062
+ skipped_missing = 0
1063
+ skipped_oversize = 0
1064
+ for image_url, _alt_text in images:
1065
+ if image_url.startswith("file://"):
1066
+ file_path = unquote(image_url[7:])
1067
+ else:
1068
+ try:
1069
+ file_path = await cache_image_from_url(image_url)
1070
+ except Exception as e:
1071
+ logger.warning("Signal: failed to download image %s: %s", image_url, e)
1072
+ skipped_download += 1
1073
+ continue
1074
+
1075
+ if not file_path or not Path(file_path).exists():
1076
+ logger.warning("Signal: image file not found for %s", image_url)
1077
+ skipped_missing += 1
1078
+ continue
1079
+
1080
+ file_size = Path(file_path).stat().st_size
1081
+ if file_size > SIGNAL_MAX_ATTACHMENT_SIZE:
1082
+ logger.warning(
1083
+ "Signal: image too large (%d bytes), skipping %s", file_size, image_url
1084
+ )
1085
+ skipped_oversize += 1
1086
+ continue
1087
+
1088
+ attachments.append(file_path)
1089
+
1090
+ if not attachments:
1091
+ logger.error(
1092
+ "Signal: no valid images in batch of %d "
1093
+ "(download=%d missing=%d oversize=%d)",
1094
+ len(images), skipped_download, skipped_missing, skipped_oversize,
1095
+ )
1096
+ return
1097
+
1098
+ logger.info(
1099
+ "Signal send_multiple_images: %d/%d images valid, sending in chunks",
1100
+ len(attachments), len(images),
1101
+ )
1102
+
1103
+ base_params: Dict[str, Any] = {
1104
+ "account": self.account,
1105
+ "message": "",
1106
+ }
1107
+ if chat_id.startswith("group:"):
1108
+ base_params["groupId"] = chat_id[6:]
1109
+ else:
1110
+ base_params["recipient"] = [await self._resolve_recipient(chat_id)]
1111
+
1112
+ att_batches = [
1113
+ attachments[i:i + SIGNAL_MAX_ATTACHMENTS_PER_MSG]
1114
+ for i in range(0, len(attachments), SIGNAL_MAX_ATTACHMENTS_PER_MSG)
1115
+ ]
1116
+
1117
+ for idx, att_batch in enumerate(att_batches):
1118
+ n = len(att_batch)
1119
+ estimated = scheduler.estimate_wait(n)
1120
+ logger.debug(
1121
+ "Signal batch %d/%d: %d attachments, estimated wait=%.1fs",
1122
+ idx + 1, len(att_batches), n, estimated,
1123
+ )
1124
+ if estimated >= SIGNAL_BATCH_PACING_NOTICE_THRESHOLD:
1125
+ await self._notify_batch_pacing(
1126
+ chat_id, idx + 1, len(att_batches), estimated
1127
+ )
1128
+
1129
+ params = dict(base_params, attachments=att_batch)
1130
+ send_timeout = _signal_send_timeout(n)
1131
+
1132
+ for attempt in range(1, SIGNAL_RATE_LIMIT_MAX_ATTEMPTS + 1):
1133
+ await scheduler.acquire(n)
1134
+ try:
1135
+ _rpc_t0 = time.monotonic()
1136
+ result = await self._rpc(
1137
+ "send", params, raise_on_rate_limit=True, timeout=send_timeout,
1138
+ )
1139
+ _rpc_duration = time.monotonic() - _rpc_t0
1140
+ if result is not None:
1141
+ self._track_sent_timestamp(result)
1142
+ await scheduler.report_rpc_duration(_rpc_duration, n)
1143
+ logger.info(
1144
+ "Signal batch %d/%d: %d attachments sent in %.1fs "
1145
+ "(attempt %d/%d)",
1146
+ idx + 1, len(att_batches), n, _rpc_duration,
1147
+ attempt, SIGNAL_RATE_LIMIT_MAX_ATTEMPTS,
1148
+ )
1149
+ else:
1150
+ # Assume the server didn't accept the batch, don't deduce tokens
1151
+ logger.error(
1152
+ "Signal: RPC send failed for batch %d/%d (%d attachments, "
1153
+ "attempt %d/%d, rpc_duration=%.1fs)",
1154
+ idx + 1, len(att_batches), n,
1155
+ attempt, SIGNAL_RATE_LIMIT_MAX_ATTEMPTS,
1156
+ _rpc_duration,
1157
+ )
1158
+ # Retry transient (non-rate-limit) failures once
1159
+ if attempt < SIGNAL_RATE_LIMIT_MAX_ATTEMPTS:
1160
+ backoff = 2.0 ** attempt
1161
+ logger.info(
1162
+ "Signal: retrying batch %d/%d after %.1fs backoff",
1163
+ idx + 1, len(att_batches), backoff,
1164
+ )
1165
+ await asyncio.sleep(backoff)
1166
+ continue
1167
+ break
1168
+ except SignalRateLimitError as e:
1169
+ scheduler.feedback(e.retry_after, n)
1170
+ if attempt >= SIGNAL_RATE_LIMIT_MAX_ATTEMPTS:
1171
+ logger.error(
1172
+ "Signal: rate-limit retries exhausted on batch %d/%d "
1173
+ "(%d attachments lost, server retry_after=%s)",
1174
+ idx + 1, len(att_batches), n,
1175
+ f"{e.retry_after:.0f}s" if e.retry_after else "unknown",
1176
+ )
1177
+ break
1178
+ logger.warning(
1179
+ "Signal: rate-limited on batch %d/%d "
1180
+ "(attempt %d/%d, server retry_after=%s); "
1181
+ "scheduler will pace the retry",
1182
+ idx + 1, len(att_batches),
1183
+ attempt, SIGNAL_RATE_LIMIT_MAX_ATTEMPTS,
1184
+ f"{e.retry_after:.0f}s" if e.retry_after else "unknown",
1185
+ )
1186
+
1187
+ async def _notify_batch_pacing(
1188
+ self,
1189
+ chat_id: str,
1190
+ next_batch_idx: int,
1191
+ total_batches: int,
1192
+ wait_s: float,
1193
+ ) -> None:
1194
+ """Inform the user when an inter-batch pacing wait crosses the
1195
+ notice threshold. Best-effort; logs and continues on failure."""
1196
+ try:
1197
+ await self.send(
1198
+ chat_id,
1199
+ f"(More images coming — pausing ~{_format_wait(wait_s)} "
1200
+ f"for Signal rate limit, batch {next_batch_idx}/{total_batches}.)",
1201
+ )
1202
+ except Exception as e:
1203
+ logger.warning("Signal: failed to send pacing notice: %s", e)
1204
+
1205
+ async def send_image(
1206
+ self,
1207
+ chat_id: str,
1208
+ image_url: str,
1209
+ caption: Optional[str] = None,
1210
+ **kwargs,
1211
+ ) -> SendResult:
1212
+ """Send an image. Supports http(s):// and file:// URLs."""
1213
+ await self._stop_typing_indicator(chat_id)
1214
+
1215
+ # Resolve image to local path
1216
+ if image_url.startswith("file://"):
1217
+ file_path = unquote(image_url[7:])
1218
+ else:
1219
+ # Download remote image to cache
1220
+ try:
1221
+ file_path = await cache_image_from_url(image_url)
1222
+ except Exception as e:
1223
+ logger.warning("Signal: failed to download image: %s", e)
1224
+ return SendResult(success=False, error=str(e))
1225
+
1226
+ if not file_path or not Path(file_path).exists():
1227
+ return SendResult(success=False, error="Image file not found")
1228
+
1229
+ # Validate size
1230
+ file_size = Path(file_path).stat().st_size
1231
+ if file_size > SIGNAL_MAX_ATTACHMENT_SIZE:
1232
+ return SendResult(success=False, error=f"Image too large ({file_size} bytes)")
1233
+
1234
+ params: Dict[str, Any] = {
1235
+ "account": self.account,
1236
+ "message": caption or "",
1237
+ "attachments": [file_path],
1238
+ }
1239
+
1240
+ if chat_id.startswith("group:"):
1241
+ params["groupId"] = chat_id[6:]
1242
+ else:
1243
+ params["recipient"] = [await self._resolve_recipient(chat_id)]
1244
+
1245
+ result = await self._rpc("send", params)
1246
+ if result is not None:
1247
+ self._track_sent_timestamp(result)
1248
+ return SendResult(success=True)
1249
+ return SendResult(success=False, error="RPC send with attachment failed")
1250
+
1251
+ async def _send_attachment(
1252
+ self,
1253
+ chat_id: str,
1254
+ file_path: str,
1255
+ media_label: str,
1256
+ caption: Optional[str] = None,
1257
+ ) -> SendResult:
1258
+ """Send any file as a Signal attachment via RPC.
1259
+
1260
+ Shared implementation for send_document, send_image_file, send_voice,
1261
+ and send_video — avoids duplicating the validation/routing/RPC logic.
1262
+ """
1263
+ await self._stop_typing_indicator(chat_id)
1264
+
1265
+ try:
1266
+ file_size = Path(file_path).stat().st_size
1267
+ except FileNotFoundError:
1268
+ return SendResult(success=False, error=f"{media_label} file not found: {file_path}")
1269
+
1270
+ if file_size > SIGNAL_MAX_ATTACHMENT_SIZE:
1271
+ return SendResult(success=False, error=f"{media_label} too large ({file_size} bytes)")
1272
+
1273
+ params: Dict[str, Any] = {
1274
+ "account": self.account,
1275
+ "message": caption or "",
1276
+ "attachments": [file_path],
1277
+ }
1278
+
1279
+ if chat_id.startswith("group:"):
1280
+ params["groupId"] = chat_id[6:]
1281
+ else:
1282
+ params["recipient"] = [await self._resolve_recipient(chat_id)]
1283
+
1284
+ result = await self._rpc("send", params)
1285
+ if result is not None:
1286
+ self._track_sent_timestamp(result)
1287
+ return SendResult(success=True)
1288
+ return SendResult(success=False, error=f"RPC send {media_label.lower()} failed")
1289
+
1290
+ async def send_document(
1291
+ self,
1292
+ chat_id: str,
1293
+ file_path: str,
1294
+ caption: Optional[str] = None,
1295
+ filename: Optional[str] = None,
1296
+ **kwargs,
1297
+ ) -> SendResult:
1298
+ """Send a document/file attachment."""
1299
+ return await self._send_attachment(chat_id, file_path, "File", caption)
1300
+
1301
+ async def send_image_file(
1302
+ self,
1303
+ chat_id: str,
1304
+ image_path: str,
1305
+ caption: Optional[str] = None,
1306
+ reply_to: Optional[str] = None,
1307
+ **kwargs,
1308
+ ) -> SendResult:
1309
+ """Send a local image file as a native Signal attachment.
1310
+
1311
+ Called by the gateway media delivery flow when MEDIA: tags containing
1312
+ image paths are extracted from agent responses.
1313
+ """
1314
+ return await self._send_attachment(chat_id, image_path, "Image", caption)
1315
+
1316
+ async def send_voice(
1317
+ self,
1318
+ chat_id: str,
1319
+ audio_path: str,
1320
+ caption: Optional[str] = None,
1321
+ reply_to: Optional[str] = None,
1322
+ **kwargs,
1323
+ ) -> SendResult:
1324
+ """Send an audio file as a Signal attachment.
1325
+
1326
+ Signal does not distinguish voice messages from file attachments at
1327
+ the API level, so this routes through the same RPC send path.
1328
+ """
1329
+ return await self._send_attachment(chat_id, audio_path, "Audio", caption)
1330
+
1331
+ async def send_video(
1332
+ self,
1333
+ chat_id: str,
1334
+ video_path: str,
1335
+ caption: Optional[str] = None,
1336
+ reply_to: Optional[str] = None,
1337
+ **kwargs,
1338
+ ) -> SendResult:
1339
+ """Send a video file as a Signal attachment."""
1340
+ return await self._send_attachment(chat_id, video_path, "Video", caption)
1341
+
1342
+ # ------------------------------------------------------------------
1343
+ # Typing Indicators
1344
+ # ------------------------------------------------------------------
1345
+
1346
+ async def _stop_typing_indicator(self, chat_id: str) -> None:
1347
+ """Stop a typing indicator loop for a chat."""
1348
+ task = self._typing_tasks.pop(chat_id, None)
1349
+ if task:
1350
+ task.cancel()
1351
+ try:
1352
+ await task
1353
+ except asyncio.CancelledError:
1354
+ pass
1355
+ # Reset per-chat typing backoff state so the next agent turn starts
1356
+ # fresh rather than inheriting a cooldown from a prior conversation.
1357
+ self._typing_failures.pop(chat_id, None)
1358
+ self._typing_skip_until.pop(chat_id, None)
1359
+
1360
+ async def stop_typing(self, chat_id: str) -> None:
1361
+ """Public interface for stopping typing — called by base adapter's
1362
+ _keep_typing finally block to clean up platform-level typing tasks."""
1363
+ await self._stop_typing_indicator(chat_id)
1364
+
1365
+ # ------------------------------------------------------------------
1366
+ # Reactions
1367
+ # ------------------------------------------------------------------
1368
+
1369
+ async def send_reaction(
1370
+ self,
1371
+ chat_id: str,
1372
+ emoji: str,
1373
+ target_author: str,
1374
+ target_timestamp: int,
1375
+ ) -> bool:
1376
+ """Send a reaction emoji to a specific message via signal-cli RPC.
1377
+
1378
+ Args:
1379
+ chat_id: The chat (phone number or "group:<id>")
1380
+ emoji: Reaction emoji string (e.g. "👀", "✅")
1381
+ target_author: Phone number / UUID of the message author
1382
+ target_timestamp: Signal timestamp (ms) of the message to react to
1383
+ """
1384
+ params: Dict[str, Any] = {
1385
+ "account": self.account,
1386
+ "emoji": emoji,
1387
+ "targetAuthor": target_author,
1388
+ "targetTimestamp": target_timestamp,
1389
+ }
1390
+
1391
+ if chat_id.startswith("group:"):
1392
+ params["groupId"] = chat_id[6:]
1393
+ else:
1394
+ params["recipient"] = [chat_id]
1395
+
1396
+ result = await self._rpc("sendReaction", params)
1397
+ if result is not None:
1398
+ return True
1399
+ logger.debug("Signal: sendReaction failed (chat=%s, emoji=%s)", chat_id[:20], emoji)
1400
+ return False
1401
+
1402
+ async def remove_reaction(
1403
+ self,
1404
+ chat_id: str,
1405
+ target_author: str,
1406
+ target_timestamp: int,
1407
+ ) -> bool:
1408
+ """Remove a reaction by sending an empty-string emoji."""
1409
+ params: Dict[str, Any] = {
1410
+ "account": self.account,
1411
+ "emoji": "",
1412
+ "targetAuthor": target_author,
1413
+ "targetTimestamp": target_timestamp,
1414
+ "remove": True,
1415
+ }
1416
+
1417
+ if chat_id.startswith("group:"):
1418
+ params["groupId"] = chat_id[6:]
1419
+ else:
1420
+ params["recipient"] = [chat_id]
1421
+
1422
+ result = await self._rpc("sendReaction", params)
1423
+ return result is not None
1424
+
1425
+ # ------------------------------------------------------------------
1426
+ # Processing Lifecycle Hooks (reactions as progress indicators)
1427
+ # ------------------------------------------------------------------
1428
+
1429
+ def _extract_reaction_target(self, event: MessageEvent) -> Optional[tuple]:
1430
+ """Extract (target_author, target_timestamp) from a MessageEvent.
1431
+
1432
+ Returns None if the event doesn't carry the raw Signal envelope data
1433
+ needed for sendReaction.
1434
+ """
1435
+ raw = event.raw_message
1436
+ if not isinstance(raw, dict):
1437
+ return None
1438
+ author = raw.get("sender")
1439
+ ts = raw.get("timestamp_ms")
1440
+ if not author or not ts:
1441
+ return None
1442
+ return (author, ts)
1443
+
1444
+ def _reactions_enabled(self, event: "MessageEvent" = None) -> bool:
1445
+ """Check if message reactions are enabled for this event.
1446
+
1447
+ Two gates:
1448
+ 1. SIGNAL_REACTIONS env var — set to false/0/no to disable globally.
1449
+ 2. DM allowlist — if SIGNAL_ALLOWED_USERS is set, only react to
1450
+ messages from senders in that list. This prevents unauthorized
1451
+ contacts from seeing the 👀 reaction (which fires before run.py's
1452
+ auth gate and would otherwise reveal that a bot is listening).
1453
+ """
1454
+ if os.getenv("SIGNAL_REACTIONS", "true").lower() in {"false", "0", "no"}:
1455
+ return False
1456
+ if event is not None:
1457
+ sender = getattr(getattr(event, "source", None), "user_id", None)
1458
+ if sender and "*" not in self.dm_allow_from and sender not in self.dm_allow_from:
1459
+ return False
1460
+ return True
1461
+
1462
+ async def on_processing_start(self, event: MessageEvent) -> None:
1463
+ """React with 👀 when processing begins."""
1464
+ if not self._reactions_enabled(event):
1465
+ return
1466
+ target = self._extract_reaction_target(event)
1467
+ if target:
1468
+ await self.send_reaction(event.source.chat_id, "👀", *target)
1469
+
1470
+ async def on_processing_complete(self, event: MessageEvent, outcome: "ProcessingOutcome") -> None:
1471
+ """Swap the 👀 reaction for ✅ (success) or ❌ (failure).
1472
+
1473
+ On CANCELLED we leave the 👀 in place — no terminal outcome means
1474
+ the reaction should keep reflecting "in progress" (matches Telegram).
1475
+ """
1476
+ if not self._reactions_enabled(event):
1477
+ return
1478
+ if outcome == ProcessingOutcome.CANCELLED:
1479
+ return
1480
+ target = self._extract_reaction_target(event)
1481
+ if not target:
1482
+ return
1483
+ chat_id = event.source.chat_id
1484
+ # Remove the in-progress reaction, then add the final one
1485
+ await self.remove_reaction(chat_id, *target)
1486
+ if outcome == ProcessingOutcome.SUCCESS:
1487
+ await self.send_reaction(chat_id, "✅", *target)
1488
+ elif outcome == ProcessingOutcome.FAILURE:
1489
+ await self.send_reaction(chat_id, "❌", *target)
1490
+
1491
+ # ------------------------------------------------------------------
1492
+ # Chat Info
1493
+ # ------------------------------------------------------------------
1494
+
1495
+ async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
1496
+ """Get information about a chat/contact."""
1497
+ if chat_id.startswith("group:"):
1498
+ return {
1499
+ "name": chat_id,
1500
+ "type": "group",
1501
+ "chat_id": chat_id,
1502
+ }
1503
+
1504
+ # Try to resolve contact name
1505
+ result = await self._rpc("getContact", {
1506
+ "account": self.account,
1507
+ "contactAddress": chat_id,
1508
+ })
1509
+
1510
+ name = chat_id
1511
+ if result and isinstance(result, dict):
1512
+ name = result.get("name") or result.get("profileName") or chat_id
1513
+
1514
+ return {
1515
+ "name": name,
1516
+ "type": "dm",
1517
+ "chat_id": chat_id,
1518
+ }