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,1639 @@
1
+ """
2
+ LINE Messaging API platform adapter for Hermes Agent.
3
+
4
+ A bundled platform plugin that runs an aiohttp webhook server, accepts LINE
5
+ webhook events (signature-verified), and relays messages to/from the agent
6
+ via the standard ``BasePlatformAdapter`` interface.
7
+
8
+ Design highlights
9
+ -----------------
10
+
11
+ **Reply token preferred, Push fallback.** LINE's reply token is single-use
12
+ and expires roughly 60 seconds after the inbound event. We try Reply first
13
+ (it's free) and fall back to the metered Push API when the token is absent,
14
+ expired, or rejected by the API.
15
+
16
+ **Slow-LLM postback button (optional).** When the LLM is still running past
17
+ ``slow_response_threshold`` seconds (default 45, leaving 15s margin on the
18
+ 60s reply-token TTL), we burn the original reply token to send a Template
19
+ Buttons bubble — the user taps it later to receive the cached answer via a
20
+ *fresh* reply token (also free). State machine: PENDING → READY → DELIVERED,
21
+ with ERROR for cancelled runs. Set the threshold to 0 to disable the
22
+ button and always Push-fallback instead.
23
+
24
+ **Three-allowlist gating.** Separate allowlists for users (U-prefixed),
25
+ groups (C-prefixed), and rooms (R-prefixed). ``LINE_ALLOW_ALL_USERS=true``
26
+ is a dev-only escape hatch.
27
+
28
+ **Media via public HTTPS.** LINE's Messaging API does *not* accept
29
+ binary uploads — images, audio, and video must be reachable HTTPS URLs.
30
+ We register registered tempfiles under ``/line/media/<token>/<filename>``
31
+ served by the same aiohttp app, with an allowed-roots traversal guard.
32
+ ``LINE_PUBLIC_URL`` (e.g. ``https://my-tunnel.example.com``) overrides
33
+ the host:port construction so URLs are reachable when bind is 0.0.0.0
34
+ or behind a reverse proxy.
35
+
36
+ **5-message batching.** LINE accepts at most 5 message objects per
37
+ Reply/Push call; longer responses are smart-chunked at 4500 chars
38
+ (LINE per-bubble limit is 5000) and batched.
39
+
40
+ Synthesis credits
41
+ -----------------
42
+
43
+ This file is a synthesis of seven open community PRs adding LINE support
44
+ to Hermes Agent. It deliberately ports the *strongest* idea from each into
45
+ a single plugin-form module that requires zero core edits:
46
+
47
+ * PR #18153 (leepoweii) — Template Buttons postback cache state machine,
48
+ Markdown URL preservation, system-message bypass.
49
+ * PR #8398 (yuga-hashimoto) — media URL serving with traversal guard,
50
+ send_voice / send_video, ``LINE_PUBLIC_URL`` env, macOS ``/tmp`` root.
51
+ * PR #16832 (jethac) — config wiring style, voice/image tests.
52
+ * PR #21023 (perng) — plugin-form skeleton (the only one already
53
+ modeled on ``ADDING_A_PLATFORM.md``), reply→push fallback at 50s TTL,
54
+ loading-animation indicator, source dispatcher.
55
+ * PR #14942 (soichiyo) — Cloudflare-tunnel operating model (docs only).
56
+ * PR #14988 (David-0x221Eight) — text-first scope discipline.
57
+ * PR #6676 (liyoungc) — Push-only mode (used as the ``threshold=0``
58
+ fallback path here).
59
+ """
60
+
61
+ from __future__ import annotations
62
+
63
+ import asyncio
64
+ import base64
65
+ import enum
66
+ import hashlib
67
+ import hmac
68
+ import json
69
+ import logging
70
+ import mimetypes
71
+ import os
72
+ import re
73
+ import secrets
74
+ import tempfile
75
+ import time
76
+ import uuid
77
+ from dataclasses import dataclass, field
78
+ from pathlib import Path
79
+ from typing import Any, Awaitable, Callable, Dict, List, Optional, Set, Tuple
80
+ from urllib.parse import quote as _urlquote
81
+
82
+ logger = logging.getLogger(__name__)
83
+
84
+ # ---------------------------------------------------------------------------
85
+ # Lazy / function-level imports for gateway internals are NOT used here —
86
+ # the plugin discovery flow imports adapter.py late enough that gateway is
87
+ # already loaded.
88
+ # ---------------------------------------------------------------------------
89
+
90
+ from gateway.platforms.base import (
91
+ BasePlatformAdapter,
92
+ MessageEvent,
93
+ MessageType,
94
+ SendResult,
95
+ cache_image_from_bytes,
96
+ )
97
+ from gateway.config import Platform
98
+ from gateway.session import SessionSource
99
+
100
+
101
+ # ---------------------------------------------------------------------------
102
+ # Constants
103
+ # ---------------------------------------------------------------------------
104
+
105
+ LINE_REPLY_URL = "https://api.line.me/v2/bot/message/reply"
106
+ LINE_PUSH_URL = "https://api.line.me/v2/bot/message/push"
107
+ LINE_LOADING_URL = "https://api.line.me/v2/bot/chat/loading/start"
108
+ LINE_CONTENT_URL_FMT = "https://api-data.line.me/v2/bot/message/{message_id}/content"
109
+ LINE_BOT_INFO_URL = "https://api.line.me/v2/bot/info"
110
+
111
+ # LINE Messaging API hard limits
112
+ LINE_PER_BUBBLE_CHARS = 5000 # Hard limit per text message object
113
+ LINE_SAFE_BUBBLE_CHARS = 4500 # Conservative limit for chunking
114
+ LINE_MAX_MESSAGES_PER_CALL = 5 # API rejects >5 messages per Reply/Push
115
+ LINE_REPLY_TOKEN_TTL_SECONDS = 50 # Conservative cap below LINE's ~60s
116
+
117
+ # Webhook hardening
118
+ WEBHOOK_BODY_MAX_BYTES = 1_048_576 # 1 MiB — webhooks are tiny JSON
119
+ DEFAULT_WEBHOOK_PORT = 8646
120
+ DEFAULT_WEBHOOK_PATH = "/line/webhook"
121
+ DEFAULT_MEDIA_PATH_PREFIX = "/line/media"
122
+
123
+ # Slow-LLM postback button defaults
124
+ DEFAULT_SLOW_RESPONSE_THRESHOLD = 45.0 # seconds; 0 disables
125
+ DEFAULT_PENDING_REPLY_TEXT = (
126
+ "🤔 Still thinking. Tap below to fetch the answer when it's ready."
127
+ )
128
+ DEFAULT_BUTTON_LABEL = "Get answer"
129
+ DEFAULT_DELIVERED_TEXT = "Already replied ✅"
130
+ DEFAULT_INTERRUPTED_TEXT = "Run was interrupted before completion."
131
+
132
+ # Media defaults
133
+ MEDIA_TOKEN_TTL_SECONDS = 1800 # 30 minutes; LINE caches the URL aggressively
134
+ LINE_IMAGE_MAX_BYTES = 10 * 1024 * 1024 # 10 MB per LINE docs
135
+ LINE_AV_MAX_BYTES = 200 * 1024 * 1024 # 200 MB for voice/video
136
+
137
+ # A 1×1 transparent PNG used as fallback video preview thumbnail when no
138
+ # explicit preview is supplied — LINE requires ``previewImageUrl`` for
139
+ # video messages. Sourced from the Python stdlib (no Pillow dependency).
140
+ _FALLBACK_PNG_PREVIEW = bytes.fromhex(
141
+ "89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c4"
142
+ "890000000d49444154789c63000100000005000100377a7ff20000000049454e"
143
+ "44ae426082"
144
+ )
145
+
146
+
147
+ # ---------------------------------------------------------------------------
148
+ # Markdown stripping (URL-preserving)
149
+ # ---------------------------------------------------------------------------
150
+
151
+ _MD_LINK_RE = re.compile(r"\[([^\]]+)\]\((https?://[^\s)]+)\)")
152
+ _MD_BOLD_RE = re.compile(r"\*\*(.+?)\*\*")
153
+ _MD_ITAL_RE = re.compile(r"(?<!\*)\*(?!\s)(.+?)(?<!\s)\*(?!\*)")
154
+ _MD_CODE_INLINE_RE = re.compile(r"`([^`]+)`")
155
+ _MD_CODE_BLOCK_RE = re.compile(r"```[a-zA-Z0-9_+-]*\n?(.*?)```", re.DOTALL)
156
+ _MD_HEADING_RE = re.compile(r"^#{1,6}\s+", re.MULTILINE)
157
+ _MD_BULLET_RE = re.compile(r"^[\s]*[-*+]\s+", re.MULTILINE)
158
+
159
+
160
+ def strip_markdown_preserving_urls(text: str) -> str:
161
+ """Strip Markdown that LINE can't render, but keep URLs usable.
162
+
163
+ LINE's text bubble has zero Markdown support — bold, italics, code
164
+ fences, headings, and bullet markers all render as literal characters.
165
+ URLs *are* auto-linked by the client, but only when they appear bare
166
+ (not inside ``[label](url)`` syntax). This converts ``[label](url)``
167
+ to ``label (url)`` so the URL remains tappable, then strips the rest.
168
+
169
+ Source: PR #18153 (leepoweii) — adapted to keep code-block content
170
+ visible (LINE users frequently want command snippets to land as
171
+ plain text, not be eaten by the fence).
172
+ """
173
+ if not text:
174
+ return text
175
+
176
+ # Code blocks first — keep the inner content, drop the fences.
177
+ def _unfence(m: re.Match) -> str:
178
+ return m.group(1).rstrip("\n")
179
+ text = _MD_CODE_BLOCK_RE.sub(_unfence, text)
180
+
181
+ # Inline code: keep content, drop backticks.
182
+ text = _MD_CODE_INLINE_RE.sub(r"\1", text)
183
+
184
+ # Markdown links → "label (url)"
185
+ text = _MD_LINK_RE.sub(lambda m: f"{m.group(1)} ({m.group(2)})", text)
186
+
187
+ # Bold/italic markers — strip.
188
+ text = _MD_BOLD_RE.sub(r"\1", text)
189
+ text = _MD_ITAL_RE.sub(r"\1", text)
190
+
191
+ # Headings (#, ##) and bullet markers — strip the prefix only.
192
+ text = _MD_HEADING_RE.sub("", text)
193
+ text = _MD_BULLET_RE.sub("• ", text)
194
+
195
+ return text
196
+
197
+
198
+ def split_for_line(text: str, max_chars: int = LINE_SAFE_BUBBLE_CHARS) -> List[str]:
199
+ """Split ``text`` into LINE-sized bubbles, preferring paragraph/line breaks.
200
+
201
+ Returns at most ``LINE_MAX_MESSAGES_PER_CALL`` chunks; longer text is
202
+ truncated with an ellipsis on the final chunk to keep the response
203
+ deliverable in a single Reply/Push call.
204
+ """
205
+ if not text:
206
+ return []
207
+ if len(text) <= max_chars:
208
+ return [text]
209
+
210
+ chunks: List[str] = []
211
+ remaining = text
212
+ while remaining and len(chunks) < LINE_MAX_MESSAGES_PER_CALL:
213
+ if len(remaining) <= max_chars:
214
+ chunks.append(remaining)
215
+ remaining = ""
216
+ break
217
+ # Try to break on the latest paragraph or newline within budget.
218
+ cut = remaining.rfind("\n\n", 0, max_chars)
219
+ if cut < int(max_chars * 0.5):
220
+ cut = remaining.rfind("\n", 0, max_chars)
221
+ if cut < int(max_chars * 0.5):
222
+ cut = remaining.rfind(" ", 0, max_chars)
223
+ if cut <= 0:
224
+ cut = max_chars
225
+ chunks.append(remaining[:cut].rstrip())
226
+ remaining = remaining[cut:].lstrip()
227
+
228
+ if remaining:
229
+ # Truncate gracefully — caller already burned its 5-bubble budget.
230
+ if chunks:
231
+ tail = chunks[-1]
232
+ if len(tail) > max_chars - 1:
233
+ tail = tail[: max_chars - 1]
234
+ chunks[-1] = tail.rstrip() + "…"
235
+ else:
236
+ chunks.append(remaining[: max_chars - 1] + "…")
237
+ return chunks
238
+
239
+
240
+ # ---------------------------------------------------------------------------
241
+ # Webhook signature verification
242
+ # ---------------------------------------------------------------------------
243
+
244
+ def verify_line_signature(body: bytes, signature: str, channel_secret: str) -> bool:
245
+ """Verify a LINE webhook's ``X-Line-Signature`` header.
246
+
247
+ LINE signs the *raw* request body with HMAC-SHA256 keyed by the
248
+ channel secret, then base64-encodes the digest. Constant-time
249
+ comparison defends against timing oracles.
250
+ """
251
+ if not signature or not channel_secret or body is None:
252
+ return False
253
+ try:
254
+ digest = hmac.new(
255
+ channel_secret.encode("utf-8"),
256
+ body,
257
+ hashlib.sha256,
258
+ ).digest()
259
+ expected = base64.b64encode(digest).decode("utf-8")
260
+ except Exception:
261
+ return False
262
+ return hmac.compare_digest(expected, signature)
263
+
264
+
265
+ # ---------------------------------------------------------------------------
266
+ # Cache state machine — slow-LLM postback flow
267
+ # ---------------------------------------------------------------------------
268
+
269
+ class State(enum.Enum):
270
+ PENDING = "pending" # button sent, LLM still running
271
+ READY = "ready" # LLM done, response cached, waiting for postback tap
272
+ DELIVERED = "delivered"
273
+ ERROR = "error" # LLM raised / interrupted; cached error text waiting
274
+
275
+
276
+ @dataclass
277
+ class _CacheEntry:
278
+ state: State
279
+ payload: Any = None
280
+ chat_id: str = ""
281
+ created_at: float = field(default_factory=time.time)
282
+ updated_at: float = field(default_factory=time.time)
283
+
284
+
285
+ class RequestCache:
286
+ """In-memory cache for slow-LLM postback retrieval.
287
+
288
+ PRs #18153 originally combined two TTLs — one for PENDING (24h) and
289
+ a shorter one for READY/DELIVERED/ERROR (1h). We keep the same model
290
+ here.
291
+ """
292
+
293
+ def __init__(
294
+ self,
295
+ ttl_seconds: int = 3600,
296
+ pending_ttl_seconds: int = 86400,
297
+ ) -> None:
298
+ self._entries: Dict[str, _CacheEntry] = {}
299
+ self._ttl = ttl_seconds
300
+ self._pending_ttl = pending_ttl_seconds
301
+
302
+ def register_pending(self, chat_id: str) -> str:
303
+ rid = str(uuid.uuid4())
304
+ self._entries[rid] = _CacheEntry(state=State.PENDING, chat_id=chat_id)
305
+ return rid
306
+
307
+ def get(self, request_id: str) -> Optional[_CacheEntry]:
308
+ return self._entries.get(request_id)
309
+
310
+ def set_ready(self, request_id: str, payload: Any) -> None:
311
+ entry = self._entries.get(request_id)
312
+ if entry is None or entry.state is not State.PENDING:
313
+ return
314
+ entry.state = State.READY
315
+ entry.payload = payload
316
+ entry.updated_at = time.time()
317
+
318
+ def set_error(self, request_id: str, message: str) -> None:
319
+ entry = self._entries.get(request_id)
320
+ if entry is None or entry.state is not State.PENDING:
321
+ return
322
+ entry.state = State.ERROR
323
+ entry.payload = message
324
+ entry.updated_at = time.time()
325
+
326
+ def mark_delivered(self, request_id: str) -> None:
327
+ entry = self._entries.get(request_id)
328
+ if entry is None or entry.state not in (State.READY, State.ERROR):
329
+ return
330
+ entry.state = State.DELIVERED
331
+ entry.updated_at = time.time()
332
+
333
+ def find_pending_for_chat(self, chat_id: str) -> Optional[str]:
334
+ for rid, entry in self._entries.items():
335
+ if entry.state is State.PENDING and entry.chat_id == chat_id:
336
+ return rid
337
+ return None
338
+
339
+ def prune(self) -> int:
340
+ now = time.time()
341
+ removed = 0
342
+ for rid in list(self._entries.keys()):
343
+ entry = self._entries[rid]
344
+ if entry.state is State.PENDING:
345
+ if now - entry.created_at > self._pending_ttl:
346
+ del self._entries[rid]
347
+ removed += 1
348
+ else:
349
+ if now - entry.updated_at > self._ttl:
350
+ del self._entries[rid]
351
+ removed += 1
352
+ return removed
353
+
354
+
355
+ # ---------------------------------------------------------------------------
356
+ # Inbound dedup
357
+ # ---------------------------------------------------------------------------
358
+
359
+ class _MessageDeduplicator:
360
+ """Bounded LRU of LINE webhook event IDs to ignore at-least-once retries."""
361
+
362
+ def __init__(self, max_size: int = 1000) -> None:
363
+ self._seen: Dict[str, float] = {}
364
+ self._max = max_size
365
+
366
+ def is_duplicate(self, event_id: str) -> bool:
367
+ if not event_id:
368
+ return False
369
+ if event_id in self._seen:
370
+ return True
371
+ if len(self._seen) >= self._max:
372
+ # Drop the oldest 10% so we don't trim on every insert.
373
+ cutoff = sorted(self._seen.values())[len(self._seen) // 10 or 1]
374
+ self._seen = {k: v for k, v in self._seen.items() if v > cutoff}
375
+ self._seen[event_id] = time.time()
376
+ return False
377
+
378
+
379
+ # ---------------------------------------------------------------------------
380
+ # Source / chat-id resolution
381
+ # ---------------------------------------------------------------------------
382
+
383
+ def _resolve_chat(source: Dict[str, Any]) -> Tuple[str, str]:
384
+ """Return ``(chat_id, chat_type)`` from a LINE event ``source`` block.
385
+
386
+ LINE sources are one of:
387
+ * ``{"type": "user", "userId": "U..."}`` → 1:1 DM
388
+ * ``{"type": "group", "groupId": "C...", "userId": "U..."}`` → group chat
389
+ * ``{"type": "room", "roomId": "R...", "userId": "U..."}`` → multi-user room
390
+
391
+ Source: PR #21023 (perng), unchanged.
392
+ """
393
+ src_type = (source or {}).get("type", "")
394
+ if src_type == "group":
395
+ return source.get("groupId", ""), "group"
396
+ if src_type == "room":
397
+ return source.get("roomId", ""), "room"
398
+ if src_type == "user":
399
+ return source.get("userId", ""), "dm"
400
+ return "", "dm"
401
+
402
+
403
+ def _allowed_for_source(
404
+ source: Dict[str, Any],
405
+ *,
406
+ allow_all: bool,
407
+ user_ids: Set[str],
408
+ group_ids: Set[str],
409
+ room_ids: Set[str],
410
+ ) -> bool:
411
+ """Three-list gate — credit PR #18153."""
412
+ if allow_all:
413
+ return True
414
+ src_type = (source or {}).get("type", "")
415
+ if src_type == "user":
416
+ uid = source.get("userId", "")
417
+ return bool(uid) and uid in user_ids
418
+ if src_type == "group":
419
+ gid = source.get("groupId", "")
420
+ return bool(gid) and gid in group_ids
421
+ if src_type == "room":
422
+ rid = source.get("roomId", "")
423
+ return bool(rid) and rid in room_ids
424
+ return False
425
+
426
+
427
+ # ---------------------------------------------------------------------------
428
+ # LINE Reply / Push HTTP client
429
+ # ---------------------------------------------------------------------------
430
+
431
+ class _LineClient:
432
+ """Thin async wrapper around the LINE Messaging API.
433
+
434
+ We use ``aiohttp`` directly to avoid a ``line-bot-sdk`` dependency
435
+ (the SDK pulls in its own httpx pin and the ergonomic gain is small
436
+ for the four endpoints we actually call).
437
+ """
438
+
439
+ def __init__(self, channel_access_token: str, *, timeout: float = 15.0) -> None:
440
+ self._token = channel_access_token
441
+ self._timeout = timeout
442
+ self._headers = {
443
+ "Authorization": f"Bearer {channel_access_token}",
444
+ "Content-Type": "application/json",
445
+ }
446
+
447
+ async def reply(self, reply_token: str, messages: List[Dict[str, Any]]) -> None:
448
+ import aiohttp
449
+ timeout = aiohttp.ClientTimeout(total=self._timeout)
450
+ async with aiohttp.ClientSession(timeout=timeout) as session:
451
+ async with session.post(
452
+ LINE_REPLY_URL,
453
+ headers=self._headers,
454
+ json={"replyToken": reply_token, "messages": messages},
455
+ ) as resp:
456
+ if resp.status >= 400:
457
+ body = await resp.text()
458
+ raise RuntimeError(f"LINE reply {resp.status}: {body[:200]}")
459
+
460
+ async def push(self, chat_id: str, messages: List[Dict[str, Any]]) -> None:
461
+ import aiohttp
462
+ timeout = aiohttp.ClientTimeout(total=self._timeout)
463
+ async with aiohttp.ClientSession(timeout=timeout) as session:
464
+ async with session.post(
465
+ LINE_PUSH_URL,
466
+ headers=self._headers,
467
+ json={"to": chat_id, "messages": messages},
468
+ ) as resp:
469
+ if resp.status >= 400:
470
+ body = await resp.text()
471
+ raise RuntimeError(f"LINE push {resp.status}: {body[:200]}")
472
+
473
+ async def loading(self, chat_id: str, seconds: int = 60) -> None:
474
+ """Loading indicator (DM only). LINE rejects this for groups/rooms."""
475
+ if not chat_id or not chat_id.startswith("U"):
476
+ return
477
+ import aiohttp
478
+ # LINE caps loadingSeconds in 5-step increments, max 60.
479
+ clamped = max(5, min(60, (seconds // 5) * 5 or 5))
480
+ try:
481
+ timeout = aiohttp.ClientTimeout(total=5.0)
482
+ async with aiohttp.ClientSession(timeout=timeout) as session:
483
+ await session.post(
484
+ LINE_LOADING_URL,
485
+ headers=self._headers,
486
+ json={"chatId": chat_id, "loadingSeconds": clamped},
487
+ )
488
+ except Exception as exc: # best-effort; never raise
489
+ logger.debug("LINE loading indicator failed: %s", exc)
490
+
491
+ async def fetch_content(self, message_id: str) -> bytes:
492
+ """Download an inbound media message's binary content."""
493
+ import aiohttp
494
+ url = LINE_CONTENT_URL_FMT.format(message_id=message_id)
495
+ timeout = aiohttp.ClientTimeout(total=30.0)
496
+ async with aiohttp.ClientSession(timeout=timeout) as session:
497
+ async with session.get(url, headers={"Authorization": f"Bearer {self._token}"}) as resp:
498
+ if resp.status >= 400:
499
+ raise RuntimeError(f"LINE content {resp.status}")
500
+ return await resp.read()
501
+
502
+ async def get_bot_user_id(self) -> Optional[str]:
503
+ """Fetch this channel's own userId so we can filter self-messages."""
504
+ import aiohttp
505
+ timeout = aiohttp.ClientTimeout(total=10.0)
506
+ try:
507
+ async with aiohttp.ClientSession(timeout=timeout) as session:
508
+ async with session.get(LINE_BOT_INFO_URL, headers=self._headers) as resp:
509
+ if resp.status >= 400:
510
+ return None
511
+ data = await resp.json()
512
+ return data.get("userId")
513
+ except Exception:
514
+ return None
515
+
516
+
517
+ # ---------------------------------------------------------------------------
518
+ # Message builders
519
+ # ---------------------------------------------------------------------------
520
+
521
+ def _text_message(text: str) -> Dict[str, Any]:
522
+ """Build a LINE text message object, capped to per-bubble max."""
523
+ if len(text) > LINE_PER_BUBBLE_CHARS:
524
+ text = text[: LINE_PER_BUBBLE_CHARS - 1] + "…"
525
+ return {"type": "text", "text": text}
526
+
527
+
528
+ def _image_message(original_url: str, preview_url: Optional[str] = None) -> Dict[str, Any]:
529
+ return {
530
+ "type": "image",
531
+ "originalContentUrl": original_url,
532
+ "previewImageUrl": preview_url or original_url,
533
+ }
534
+
535
+
536
+ def _audio_message(url: str, duration_ms: int = 1000) -> Dict[str, Any]:
537
+ return {
538
+ "type": "audio",
539
+ "originalContentUrl": url,
540
+ "duration": int(duration_ms),
541
+ }
542
+
543
+
544
+ def _video_message(url: str, preview_url: str) -> Dict[str, Any]:
545
+ return {
546
+ "type": "video",
547
+ "originalContentUrl": url,
548
+ "previewImageUrl": preview_url,
549
+ }
550
+
551
+
552
+ def build_postback_button_message(
553
+ text: str, button_label: str, request_id: str
554
+ ) -> Dict[str, Any]:
555
+ """Template Buttons message — the slow-LLM postback bubble.
556
+
557
+ From PR #18153 (leepoweii). Template Buttons stay tappable from chat
558
+ history, unlike Quick Reply chips which are dismissed the moment any
559
+ new message arrives in the chat.
560
+
561
+ LINE limits: ``text`` ≤ 160 chars, ``altText`` ≤ 400 chars.
562
+ """
563
+ truncated = text if len(text) <= 160 else text[:157] + "..."
564
+ alt = text if len(text) <= 400 else text[:397] + "..."
565
+ return {
566
+ "type": "template",
567
+ "altText": alt,
568
+ "template": {
569
+ "type": "buttons",
570
+ "text": truncated,
571
+ "actions": [
572
+ {
573
+ "type": "postback",
574
+ "label": button_label[:20] or "Get answer",
575
+ "data": json.dumps(
576
+ {"action": "show_response", "request_id": request_id}
577
+ ),
578
+ "displayText": button_label[:300] or "Get answer",
579
+ }
580
+ ],
581
+ },
582
+ }
583
+
584
+
585
+ # Prefixes the gateway uses for system busy-acks (interrupting / queued /
586
+ # steered). When the postback cache has a PENDING entry we *bypass* the
587
+ # cache for these so they reach the user as visible bubbles instead of
588
+ # being silently swallowed. From PR #18153.
589
+ _SYSTEM_BYPASS_PREFIXES: Tuple[str, ...] = (
590
+ "⚡ Interrupting",
591
+ "⏳ Queued",
592
+ "⏩ Steered",
593
+ "💾", # background-review summary
594
+ )
595
+
596
+
597
+ def _is_system_bypass(content: str) -> bool:
598
+ if not content:
599
+ return False
600
+ return any(content.startswith(p) for p in _SYSTEM_BYPASS_PREFIXES)
601
+
602
+
603
+ # ---------------------------------------------------------------------------
604
+ # Configuration helpers
605
+ # ---------------------------------------------------------------------------
606
+
607
+ def _csv_set(value: str) -> Set[str]:
608
+ if not value:
609
+ return set()
610
+ return {x.strip() for x in value.split(",") if x.strip()}
611
+
612
+
613
+ def _truthy_env(name: str, default: bool = False) -> bool:
614
+ v = os.getenv(name)
615
+ if v is None:
616
+ return default
617
+ return v.strip().lower() in ("1", "true", "yes", "on")
618
+
619
+
620
+ # ---------------------------------------------------------------------------
621
+ # Adapter
622
+ # ---------------------------------------------------------------------------
623
+
624
+ class LineAdapter(BasePlatformAdapter):
625
+ """LINE Messaging API gateway adapter."""
626
+
627
+ # LINE has its own message-edit story (none) — we always send fresh
628
+ # bubbles, never edit, so REQUIRES_EDIT_FINALIZE stays False.
629
+
630
+ def __init__(self, config, **kwargs):
631
+ platform = Platform("line")
632
+ super().__init__(config=config, platform=platform)
633
+
634
+ extra = getattr(config, "extra", {}) or {}
635
+
636
+ # Credentials
637
+ self.channel_access_token = (
638
+ os.getenv("LINE_CHANNEL_ACCESS_TOKEN")
639
+ or extra.get("channel_access_token", "")
640
+ )
641
+ self.channel_secret = (
642
+ os.getenv("LINE_CHANNEL_SECRET")
643
+ or extra.get("channel_secret", "")
644
+ )
645
+
646
+ # Webhook server
647
+ self.webhook_host = os.getenv("LINE_HOST") or extra.get("host", "0.0.0.0")
648
+ try:
649
+ self.webhook_port = int(
650
+ os.getenv("LINE_PORT") or extra.get("port", DEFAULT_WEBHOOK_PORT)
651
+ )
652
+ except (TypeError, ValueError):
653
+ self.webhook_port = DEFAULT_WEBHOOK_PORT
654
+ self.webhook_path = extra.get("webhook_path", DEFAULT_WEBHOOK_PATH)
655
+
656
+ # Public base URL — required for media sending when bind isn't
657
+ # publicly reachable.
658
+ self.public_base_url = (
659
+ os.getenv("LINE_PUBLIC_URL")
660
+ or extra.get("public_url", "")
661
+ or ""
662
+ ).rstrip("/")
663
+
664
+ # Three-allowlist gating
665
+ self.allow_all = _truthy_env(
666
+ "LINE_ALLOW_ALL_USERS", bool(extra.get("allow_all_users", False))
667
+ )
668
+ self.allowed_users = _csv_set(
669
+ os.getenv("LINE_ALLOWED_USERS", "")
670
+ ) | set(extra.get("allowed_users", []))
671
+ self.allowed_groups = _csv_set(
672
+ os.getenv("LINE_ALLOWED_GROUPS", "")
673
+ ) | set(extra.get("allowed_groups", []))
674
+ self.allowed_rooms = _csv_set(
675
+ os.getenv("LINE_ALLOWED_ROOMS", "")
676
+ ) | set(extra.get("allowed_rooms", []))
677
+
678
+ # Slow-LLM postback button threshold
679
+ try:
680
+ self.slow_response_threshold = float(
681
+ os.getenv("LINE_SLOW_RESPONSE_THRESHOLD")
682
+ or extra.get("slow_response_threshold", DEFAULT_SLOW_RESPONSE_THRESHOLD)
683
+ )
684
+ except (TypeError, ValueError):
685
+ self.slow_response_threshold = DEFAULT_SLOW_RESPONSE_THRESHOLD
686
+
687
+ # User-overridable copy
688
+ self.pending_text = (
689
+ os.getenv("LINE_PENDING_TEXT")
690
+ or extra.get("pending_text", DEFAULT_PENDING_REPLY_TEXT)
691
+ )
692
+ self.button_label = (
693
+ os.getenv("LINE_BUTTON_LABEL")
694
+ or extra.get("button_label", DEFAULT_BUTTON_LABEL)
695
+ )
696
+ self.delivered_text = (
697
+ os.getenv("LINE_DELIVERED_TEXT")
698
+ or extra.get("delivered_text", DEFAULT_DELIVERED_TEXT)
699
+ )
700
+ self.interrupted_text = (
701
+ os.getenv("LINE_INTERRUPTED_TEXT")
702
+ or extra.get("interrupted_text", DEFAULT_INTERRUPTED_TEXT)
703
+ )
704
+
705
+ # Runtime state
706
+ self._client: Optional[_LineClient] = None
707
+ self._app = None # aiohttp.web.Application
708
+ self._runner = None # aiohttp.web.AppRunner
709
+ self._site = None # aiohttp.web.TCPSite
710
+ self._reply_tokens: Dict[str, Tuple[str, float]] = {} # chat_id → (token, expiry)
711
+ self._cache = RequestCache()
712
+ self._dedup = _MessageDeduplicator()
713
+ self._bot_user_id: Optional[str] = None
714
+ self._lock_key: Optional[str] = None
715
+
716
+ # Media state
717
+ self._media_tokens: Dict[str, Tuple[str, float]] = {} # token → (path, expiry)
718
+ self._media_temp_paths: Set[str] = set()
719
+ self._media_ttl = MEDIA_TOKEN_TTL_SECONDS
720
+
721
+ # Pending-button slot per chat — ensures one outstanding postback
722
+ # button per chat at a time. Postback cache request_id keyed by chat_id.
723
+ self._pending_buttons: Dict[str, str] = {}
724
+
725
+ # ------------------------------------------------------------------
726
+ # Connection lifecycle
727
+ # ------------------------------------------------------------------
728
+
729
+ async def connect(self) -> bool:
730
+ if not self.channel_access_token or not self.channel_secret:
731
+ self._set_fatal_error(
732
+ "config_missing",
733
+ "LINE_CHANNEL_ACCESS_TOKEN and LINE_CHANNEL_SECRET must be set",
734
+ retryable=False,
735
+ )
736
+ return False
737
+
738
+ # Prevent two profiles from running on the same channel access token.
739
+ try:
740
+ from gateway.status import acquire_scoped_lock
741
+ # Use a hash of the token so we don't write the secret to disk.
742
+ tok_hash = hashlib.sha256(self.channel_access_token.encode()).hexdigest()[:16]
743
+ if not acquire_scoped_lock("line", tok_hash):
744
+ self._set_fatal_error(
745
+ "lock_conflict",
746
+ "LINE channel already in use by another profile",
747
+ retryable=False,
748
+ )
749
+ return False
750
+ self._lock_key = tok_hash
751
+ except ImportError:
752
+ self._lock_key = None
753
+
754
+ self._client = _LineClient(self.channel_access_token)
755
+
756
+ # Best-effort: fetch our own bot userId for self-message filtering.
757
+ # If the call fails (offline tests, transient 5xx) we fall back to
758
+ # not filtering self-events; the cost is minor (LINE doesn't
759
+ # actually echo our own messages back).
760
+ try:
761
+ self._bot_user_id = await self._client.get_bot_user_id()
762
+ except Exception as exc:
763
+ logger.debug("LINE: get_bot_user_id failed: %s", exc)
764
+ self._bot_user_id = None
765
+
766
+ # Spin up the aiohttp webhook server.
767
+ try:
768
+ from aiohttp import web
769
+ except ImportError:
770
+ self._set_fatal_error(
771
+ "missing_dep",
772
+ "aiohttp is required for the LINE adapter — install with `pip install aiohttp`",
773
+ retryable=False,
774
+ )
775
+ return False
776
+
777
+ self._app = web.Application(client_max_size=WEBHOOK_BODY_MAX_BYTES)
778
+ self._app.router.add_post(self.webhook_path, self._handle_webhook)
779
+ # Public health probe — useful for tunnel/proxy verification.
780
+ self._app.router.add_get(f"{self.webhook_path}/health", self._handle_health)
781
+ # Media serving endpoint.
782
+ self._app.router.add_get(
783
+ f"{DEFAULT_MEDIA_PATH_PREFIX}/{{token}}/{{filename}}",
784
+ self._handle_media,
785
+ )
786
+
787
+ self._runner = web.AppRunner(self._app)
788
+ try:
789
+ await self._runner.setup()
790
+ self._site = web.TCPSite(self._runner, self.webhook_host, self.webhook_port)
791
+ await self._site.start()
792
+ except OSError as exc:
793
+ self._set_fatal_error(
794
+ "bind_failed",
795
+ f"Could not bind LINE webhook on {self.webhook_host}:{self.webhook_port}: {exc}",
796
+ retryable=True,
797
+ )
798
+ return False
799
+
800
+ self._mark_connected()
801
+ logger.info(
802
+ "LINE: webhook listening on %s:%s%s%s",
803
+ self.webhook_host,
804
+ self.webhook_port,
805
+ self.webhook_path,
806
+ f" (public: {self.public_base_url})" if self.public_base_url else "",
807
+ )
808
+ return True
809
+
810
+ async def disconnect(self) -> None:
811
+ self._mark_disconnected()
812
+
813
+ if self._site is not None:
814
+ try:
815
+ await self._site.stop()
816
+ except Exception:
817
+ pass
818
+ self._site = None
819
+ if self._runner is not None:
820
+ try:
821
+ await self._runner.cleanup()
822
+ except Exception:
823
+ pass
824
+ self._runner = None
825
+ self._app = None
826
+
827
+ # Cleanup any tracked tempfiles.
828
+ for path in list(self._media_temp_paths):
829
+ try:
830
+ os.unlink(path)
831
+ except OSError:
832
+ pass
833
+ self._media_temp_paths.clear()
834
+ self._media_tokens.clear()
835
+
836
+ if self._lock_key:
837
+ try:
838
+ from gateway.status import release_scoped_lock
839
+ release_scoped_lock("line", self._lock_key)
840
+ except Exception:
841
+ pass
842
+ self._lock_key = None
843
+
844
+ # ------------------------------------------------------------------
845
+ # Webhook handlers
846
+ # ------------------------------------------------------------------
847
+
848
+ async def _handle_health(self, request) -> Any:
849
+ from aiohttp import web
850
+ return web.json_response({"status": "ok", "platform": "line"})
851
+
852
+ async def _handle_webhook(self, request) -> Any:
853
+ from aiohttp import web
854
+
855
+ # Body cap defends against memory-exhaustion via crafted Content-Length
856
+ # (aiohttp's client_max_size only applies to certain body modes).
857
+ try:
858
+ body = await request.read()
859
+ except Exception as exc:
860
+ logger.debug("LINE: read failed: %s", exc)
861
+ return web.Response(status=400, text="bad request")
862
+ if len(body) > WEBHOOK_BODY_MAX_BYTES:
863
+ return web.Response(status=413, text="payload too large")
864
+
865
+ signature = request.headers.get("X-Line-Signature", "")
866
+ if not verify_line_signature(body, signature, self.channel_secret):
867
+ return web.Response(status=401, text="invalid signature")
868
+
869
+ try:
870
+ payload = json.loads(body.decode("utf-8"))
871
+ except (UnicodeDecodeError, json.JSONDecodeError):
872
+ return web.Response(status=400, text="bad json")
873
+
874
+ events = payload.get("events", []) or []
875
+ for event in events:
876
+ try:
877
+ await self._dispatch_event(event)
878
+ except Exception:
879
+ logger.exception("LINE: dispatch_event failed")
880
+
881
+ return web.Response(status=200, text="ok")
882
+
883
+ async def _dispatch_event(self, event: Dict[str, Any]) -> None:
884
+ event_type = event.get("type")
885
+ source = event.get("source") or {}
886
+ webhook_event_id = event.get("webhookEventId", "") or ""
887
+
888
+ # Dedup retries (LINE webhooks may be re-delivered).
889
+ if webhook_event_id and self._dedup.is_duplicate(webhook_event_id):
890
+ logger.debug("LINE: ignoring duplicate webhook event %s", webhook_event_id)
891
+ return
892
+
893
+ # Filter our own messages (self-echo).
894
+ sender_user_id = source.get("userId", "")
895
+ if self._bot_user_id and sender_user_id == self._bot_user_id:
896
+ return
897
+
898
+ # Allowlist gate.
899
+ if not _allowed_for_source(
900
+ source,
901
+ allow_all=self.allow_all,
902
+ user_ids=self.allowed_users,
903
+ group_ids=self.allowed_groups,
904
+ room_ids=self.allowed_rooms,
905
+ ):
906
+ logger.info("LINE: rejecting unauthorized source %s", source)
907
+ return
908
+
909
+ if event_type == "message":
910
+ await self._handle_message_event(event)
911
+ elif event_type == "postback":
912
+ await self._handle_postback_event(event)
913
+ elif event_type in ("follow", "unfollow", "join", "leave"):
914
+ logger.info("LINE: lifecycle event %s from %s", event_type, source)
915
+ else:
916
+ logger.debug("LINE: ignoring event type %r", event_type)
917
+
918
+ async def _handle_message_event(self, event: Dict[str, Any]) -> None:
919
+ msg = event.get("message") or {}
920
+ msg_type = msg.get("type", "")
921
+ message_id = msg.get("id", "")
922
+ reply_token = event.get("replyToken", "")
923
+ source = event.get("source") or {}
924
+ chat_id, chat_type = _resolve_chat(source)
925
+ user_id = source.get("userId", "") or chat_id
926
+
927
+ # Stash the reply token for outbound use.
928
+ if chat_id and reply_token:
929
+ self._reply_tokens[chat_id] = (
930
+ reply_token,
931
+ time.time() + LINE_REPLY_TOKEN_TTL_SECONDS,
932
+ )
933
+
934
+ # Handle media inbound — fetch the binary, cache it, and surface a
935
+ # vision-tool-friendly local path on the MessageEvent.
936
+ media_urls: List[str] = []
937
+ media_types: List[str] = []
938
+ text = ""
939
+
940
+ if msg_type == "text":
941
+ text = msg.get("text", "") or ""
942
+ elif msg_type in ("image", "audio", "video", "file"):
943
+ local_path = await self._download_media(message_id, msg_type)
944
+ if local_path:
945
+ media_urls.append(local_path)
946
+ media_types.append(msg_type)
947
+ text = f"[{msg_type}]"
948
+ elif msg_type == "sticker":
949
+ keywords = msg.get("keywords") or []
950
+ text = f"[sticker: {', '.join(keywords)}]" if keywords else "[sticker]"
951
+ elif msg_type == "location":
952
+ title = msg.get("title", "")
953
+ address = msg.get("address", "")
954
+ text = f"[location: {title} {address}]".strip()
955
+ else:
956
+ text = f"[unsupported message type: {msg_type}]"
957
+
958
+ # Best-effort typing indicator (DM only).
959
+ if chat_type == "dm" and self._client:
960
+ asyncio.create_task(self._client.loading(chat_id))
961
+
962
+ source_obj = self.build_source(
963
+ chat_id=chat_id,
964
+ chat_type=chat_type,
965
+ user_id=user_id,
966
+ user_name=user_id,
967
+ chat_name=chat_id,
968
+ )
969
+
970
+ event_obj = MessageEvent(
971
+ text=text,
972
+ message_type=MessageType.TEXT if msg_type == "text" else MessageType.IMAGE,
973
+ source=source_obj,
974
+ raw_message=event,
975
+ message_id=message_id,
976
+ media_urls=media_urls,
977
+ media_types=media_types,
978
+ )
979
+
980
+ await self.handle_message(event_obj)
981
+
982
+ async def _handle_postback_event(self, event: Dict[str, Any]) -> None:
983
+ """User tapped the slow-LLM postback button — deliver cached payload."""
984
+ postback = event.get("postback") or {}
985
+ data = postback.get("data", "") or ""
986
+ reply_token = event.get("replyToken", "")
987
+ source = event.get("source") or {}
988
+ chat_id, _ = _resolve_chat(source)
989
+
990
+ try:
991
+ parsed = json.loads(data)
992
+ except (TypeError, json.JSONDecodeError):
993
+ return
994
+
995
+ if parsed.get("action") != "show_response":
996
+ return
997
+ request_id = parsed.get("request_id", "")
998
+ if not request_id:
999
+ return
1000
+
1001
+ entry = self._cache.get(request_id)
1002
+ if not self._client or not reply_token or not entry:
1003
+ return
1004
+
1005
+ if entry.state is State.READY:
1006
+ payload = entry.payload or ""
1007
+ chunks = split_for_line(strip_markdown_preserving_urls(str(payload)))
1008
+ messages = [_text_message(c) for c in chunks][:LINE_MAX_MESSAGES_PER_CALL]
1009
+ try:
1010
+ await self._client.reply(reply_token, messages)
1011
+ self._cache.mark_delivered(request_id)
1012
+ self._pending_buttons.pop(chat_id, None)
1013
+ except Exception as exc:
1014
+ logger.warning("LINE: postback reply failed (%s); falling back to push", exc)
1015
+ try:
1016
+ await self._client.push(chat_id, messages)
1017
+ self._cache.mark_delivered(request_id)
1018
+ self._pending_buttons.pop(chat_id, None)
1019
+ except Exception as exc2:
1020
+ logger.error("LINE: postback push fallback failed: %s", exc2)
1021
+ elif entry.state is State.ERROR:
1022
+ text = str(entry.payload or self.interrupted_text)
1023
+ try:
1024
+ await self._client.reply(reply_token, [_text_message(text)])
1025
+ self._cache.mark_delivered(request_id)
1026
+ self._pending_buttons.pop(chat_id, None)
1027
+ except Exception as exc:
1028
+ logger.warning("LINE: postback ERROR reply failed: %s", exc)
1029
+ elif entry.state is State.DELIVERED:
1030
+ try:
1031
+ await self._client.reply(reply_token, [_text_message(self.delivered_text)])
1032
+ except Exception:
1033
+ pass
1034
+ elif entry.state is State.PENDING:
1035
+ # Still working — re-issue the wait notice.
1036
+ try:
1037
+ await self._client.reply(reply_token, [_text_message(self.pending_text)])
1038
+ except Exception:
1039
+ pass
1040
+
1041
+ async def _download_media(self, message_id: str, msg_type: str) -> Optional[str]:
1042
+ if not self._client or not message_id:
1043
+ return None
1044
+ try:
1045
+ data = await self._client.fetch_content(message_id)
1046
+ except Exception as exc:
1047
+ logger.warning("LINE: failed to fetch %s content for %s: %s", msg_type, message_id, exc)
1048
+ return None
1049
+ ext = {
1050
+ "image": ".jpg",
1051
+ "audio": ".m4a",
1052
+ "video": ".mp4",
1053
+ "file": ".bin",
1054
+ }.get(msg_type, ".bin")
1055
+ try:
1056
+ return cache_image_from_bytes(data, ext=ext)
1057
+ except Exception as exc:
1058
+ logger.warning("LINE: failed to cache %s payload: %s", msg_type, exc)
1059
+ return None
1060
+
1061
+ # ------------------------------------------------------------------
1062
+ # Outbound send (text)
1063
+ # ------------------------------------------------------------------
1064
+
1065
+ async def send(
1066
+ self,
1067
+ chat_id: str,
1068
+ content: str,
1069
+ reply_to: Optional[str] = None,
1070
+ metadata: Optional[Dict[str, Any]] = None,
1071
+ ) -> SendResult:
1072
+ if not self._client:
1073
+ return SendResult(success=False, error="LINE adapter not connected")
1074
+
1075
+ # System busy-acks (interrupting / queued / steered) bypass the
1076
+ # postback cache and route directly to LINE so they reach the user
1077
+ # as visible bubbles. Source: PR #18153.
1078
+ if _is_system_bypass(content):
1079
+ return await self._send_text_chunks(chat_id, content, force_push=False)
1080
+
1081
+ # If the chat has a PENDING postback button outstanding, route the
1082
+ # response into the cache for the user to fetch via tap.
1083
+ pending_rid = self._pending_buttons.get(chat_id)
1084
+ if pending_rid:
1085
+ self._cache.set_ready(pending_rid, content)
1086
+ return SendResult(success=True, message_id=pending_rid)
1087
+
1088
+ return await self._send_text_chunks(chat_id, content, force_push=False)
1089
+
1090
+ async def _send_text_chunks(
1091
+ self,
1092
+ chat_id: str,
1093
+ content: str,
1094
+ *,
1095
+ force_push: bool,
1096
+ ) -> SendResult:
1097
+ if not self._client:
1098
+ return SendResult(success=False, error="LINE adapter not connected")
1099
+
1100
+ chunks = split_for_line(strip_markdown_preserving_urls(content))
1101
+ if not chunks:
1102
+ return SendResult(success=True, message_id=None)
1103
+ messages = [_text_message(c) for c in chunks][:LINE_MAX_MESSAGES_PER_CALL]
1104
+
1105
+ token, used_reply = self._consume_reply_token(chat_id)
1106
+ if used_reply and not force_push:
1107
+ try:
1108
+ await self._client.reply(token, messages)
1109
+ return SendResult(success=True, message_id=token)
1110
+ except Exception as exc:
1111
+ logger.info("LINE: reply token rejected (%s); falling back to push", exc)
1112
+ # fall through to push
1113
+
1114
+ try:
1115
+ await self._client.push(chat_id, messages)
1116
+ return SendResult(success=True, message_id=None)
1117
+ except Exception as exc:
1118
+ logger.error("LINE: push send failed: %s", exc)
1119
+ return SendResult(success=False, error=str(exc))
1120
+
1121
+ def _consume_reply_token(self, chat_id: str) -> Tuple[str, bool]:
1122
+ """Consume a stashed reply token if present and unexpired.
1123
+
1124
+ Returns ``(token, used_reply)``.
1125
+ """
1126
+ entry = self._reply_tokens.pop(chat_id, None)
1127
+ if not entry:
1128
+ return "", False
1129
+ token, expires_at = entry
1130
+ if not token or time.time() >= expires_at:
1131
+ return "", False
1132
+ return token, True
1133
+
1134
+ async def send_typing(self, chat_id: str, metadata=None) -> None:
1135
+ """Trigger LINE's loading-animation indicator (DM only)."""
1136
+ if self._client and chat_id:
1137
+ await self._client.loading(chat_id)
1138
+
1139
+ async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
1140
+ """Best-effort chat info derived from the chat_id prefix.
1141
+
1142
+ LINE's chat-info APIs are limited and per-source-type — instead of
1143
+ chasing them we infer from the well-known ID prefixes:
1144
+ ``U`` = user (1:1), ``C`` = group, ``R`` = room. The agent only
1145
+ needs ``name`` + ``type`` from this method.
1146
+ """
1147
+ prefix = (chat_id or "")[:1]
1148
+ chat_type = {"U": "dm", "C": "group", "R": "channel"}.get(prefix, "dm")
1149
+ return {"name": chat_id or "", "type": chat_type}
1150
+
1151
+ def format_message(self, content: str) -> str:
1152
+ """Strip Markdown that LINE can't render. URLs are preserved."""
1153
+ return strip_markdown_preserving_urls(content)
1154
+
1155
+ # ------------------------------------------------------------------
1156
+ # Slow-LLM postback button — driven by _keep_typing
1157
+ # ------------------------------------------------------------------
1158
+
1159
+ async def _keep_typing(self, chat_id: str, *args, **kwargs) -> None:
1160
+ """Override the base loop to fire the postback button at threshold.
1161
+
1162
+ We intentionally keep the base implementation behind us: it's
1163
+ responsible for the typing-indicator heartbeat, while *this*
1164
+ wrapper layers in the slow-LLM postback bubble at threshold.
1165
+ """
1166
+ if (
1167
+ self.slow_response_threshold <= 0
1168
+ or not self._client
1169
+ or not chat_id
1170
+ ):
1171
+ await super()._keep_typing(chat_id, *args, **kwargs)
1172
+ return
1173
+
1174
+ async def _fire_postback() -> None:
1175
+ try:
1176
+ await asyncio.sleep(self.slow_response_threshold)
1177
+ except asyncio.CancelledError:
1178
+ raise
1179
+ # Only fire if we still have a usable reply token. If the agent
1180
+ # already responded, _consume_reply_token has cleared it.
1181
+ if chat_id not in self._reply_tokens:
1182
+ return
1183
+ if chat_id in self._pending_buttons:
1184
+ return
1185
+ rid = self._cache.register_pending(chat_id)
1186
+ self._pending_buttons[chat_id] = rid
1187
+ token, used = self._consume_reply_token(chat_id)
1188
+ if not used:
1189
+ self._pending_buttons.pop(chat_id, None)
1190
+ return
1191
+ msg = build_postback_button_message(
1192
+ self.pending_text, self.button_label, rid
1193
+ )
1194
+ try:
1195
+ await self._client.reply(token, [msg])
1196
+ logger.info("LINE: sent slow-LLM postback button for chat %s (rid=%s)", chat_id, rid)
1197
+ except Exception as exc:
1198
+ logger.warning("LINE: postback button send failed: %s", exc)
1199
+ self._pending_buttons.pop(chat_id, None)
1200
+
1201
+ post_task = asyncio.create_task(_fire_postback())
1202
+ try:
1203
+ await super()._keep_typing(chat_id, *args, **kwargs)
1204
+ finally:
1205
+ if not post_task.done():
1206
+ post_task.cancel()
1207
+ try:
1208
+ await post_task
1209
+ except (asyncio.CancelledError, Exception):
1210
+ pass
1211
+
1212
+ async def interrupt_session_activity(self, session_key: str, chat_id: str) -> None:
1213
+ """Resolve any orphan PENDING postback so the button doesn't loop."""
1214
+ await super().interrupt_session_activity(session_key, chat_id)
1215
+ rid = self._pending_buttons.pop(chat_id, None)
1216
+ if rid:
1217
+ self._cache.set_error(rid, self.interrupted_text)
1218
+
1219
+ # ------------------------------------------------------------------
1220
+ # Outbound media (image / voice / video)
1221
+ # ------------------------------------------------------------------
1222
+
1223
+ def _register_media(self, file_path: str, *, cleanup: bool = False) -> str:
1224
+ """Register a local file for HTTPS serving; return the URL token."""
1225
+ # Evict expired tokens first.
1226
+ now = time.time()
1227
+ for token in list(self._media_tokens.keys()):
1228
+ path, exp = self._media_tokens[token]
1229
+ if now > exp:
1230
+ self._media_tokens.pop(token, None)
1231
+ if path in self._media_temp_paths:
1232
+ self._media_temp_paths.discard(path)
1233
+ try:
1234
+ os.unlink(path)
1235
+ except OSError:
1236
+ pass
1237
+
1238
+ resolved = str(Path(file_path).resolve())
1239
+ token = secrets.token_urlsafe(32)
1240
+ self._media_tokens[token] = (resolved, now + self._media_ttl)
1241
+ if cleanup:
1242
+ self._media_temp_paths.add(resolved)
1243
+ return token
1244
+
1245
+ def _media_url(self, token: str, filename: str) -> str:
1246
+ """Build the public HTTPS URL for a media token. PR #8398 style."""
1247
+ if self.public_base_url:
1248
+ base = self.public_base_url
1249
+ else:
1250
+ host = self.webhook_host
1251
+ port = self.webhook_port
1252
+ if port == 443:
1253
+ base = f"https://{host}"
1254
+ else:
1255
+ base = f"https://{host}:{port}"
1256
+ safe_name = _urlquote(filename, safe="")
1257
+ return f"{base}{DEFAULT_MEDIA_PATH_PREFIX}/{token}/{safe_name}"
1258
+
1259
+ async def _handle_media(self, request) -> Any:
1260
+ """Serve a registered local file over HTTPS for LINE's media URLs.
1261
+
1262
+ Defence-in-depth: even though ``_register_media`` is only called
1263
+ from trusted internal code, we recheck the resolved path against
1264
+ an allowed-roots set before serving. Sources allowed:
1265
+ ``tempfile.gettempdir()``, ``/tmp`` (which resolves to
1266
+ ``/private/tmp`` on macOS), and ``HERMES_HOME``. PR #8398.
1267
+ """
1268
+ from aiohttp import web
1269
+
1270
+ token = request.match_info["token"]
1271
+ entry = self._media_tokens.get(token)
1272
+ if not entry:
1273
+ return web.Response(status=404, text="not found")
1274
+
1275
+ file_path, expires_at = entry
1276
+ if time.time() > expires_at:
1277
+ self._media_tokens.pop(token, None)
1278
+ return web.Response(status=410, text="gone")
1279
+
1280
+ path = Path(file_path)
1281
+ if not path.exists() or not path.is_file():
1282
+ return web.Response(status=404, text="not found")
1283
+
1284
+ try:
1285
+ from calvyn_constants import get_hermes_home
1286
+ hermes_home = Path(get_hermes_home()).resolve()
1287
+ except Exception:
1288
+ hermes_home = Path.home().joinpath(".hermes").resolve()
1289
+
1290
+ allowed_roots = {
1291
+ Path(tempfile.gettempdir()).resolve(),
1292
+ Path("/tmp").resolve(), # → /private/tmp on macOS
1293
+ hermes_home,
1294
+ }
1295
+ resolved = path.resolve()
1296
+ if not any(_is_relative_to(resolved, r) for r in allowed_roots):
1297
+ logger.warning("LINE: refusing to serve outside allowed roots: %s", resolved)
1298
+ return web.Response(status=403, text="forbidden")
1299
+
1300
+ content_type, _ = mimetypes.guess_type(str(path))
1301
+ return web.FileResponse(
1302
+ path,
1303
+ headers={"Content-Type": content_type or "application/octet-stream"},
1304
+ )
1305
+
1306
+ async def send_image_file(
1307
+ self,
1308
+ chat_id: str,
1309
+ image_path: str,
1310
+ caption: Optional[str] = None,
1311
+ metadata: Optional[Dict[str, Any]] = None,
1312
+ ) -> SendResult:
1313
+ path = Path(image_path)
1314
+ if not path.exists() or not path.is_file():
1315
+ return SendResult(success=False, error=f"image file not found: {image_path}")
1316
+ if path.stat().st_size > LINE_IMAGE_MAX_BYTES:
1317
+ return SendResult(success=False, error="image exceeds 10 MB LINE limit")
1318
+ if not self._client:
1319
+ return SendResult(success=False, error="LINE adapter not connected")
1320
+ if not self.public_base_url and self.webhook_host == "0.0.0.0":
1321
+ return SendResult(
1322
+ success=False,
1323
+ error="LINE_PUBLIC_URL must be set to send images "
1324
+ "(LINE only accepts publicly reachable HTTPS URLs)",
1325
+ )
1326
+
1327
+ token = self._register_media(str(path.resolve()))
1328
+ url = self._media_url(token, path.name)
1329
+ if not url.lower().startswith("https://"):
1330
+ return SendResult(success=False, error=f"LINE image URL must be HTTPS: {url}")
1331
+ msgs: List[Dict[str, Any]] = [_image_message(url)]
1332
+ if caption:
1333
+ msgs.append(_text_message(caption))
1334
+ return await self._send_messages(chat_id, msgs)
1335
+
1336
+ async def send_voice(
1337
+ self,
1338
+ chat_id: str,
1339
+ audio_path: str,
1340
+ duration_ms: int = 1000,
1341
+ metadata: Optional[Dict[str, Any]] = None,
1342
+ ) -> SendResult:
1343
+ path = Path(audio_path)
1344
+ if not path.exists() or not path.is_file():
1345
+ return SendResult(success=False, error=f"audio file not found: {audio_path}")
1346
+ if path.stat().st_size > LINE_AV_MAX_BYTES:
1347
+ return SendResult(success=False, error="audio exceeds 200 MB LINE limit")
1348
+ if not self._client:
1349
+ return SendResult(success=False, error="LINE adapter not connected")
1350
+ if not self.public_base_url and self.webhook_host == "0.0.0.0":
1351
+ return SendResult(
1352
+ success=False,
1353
+ error="LINE_PUBLIC_URL must be set to send audio",
1354
+ )
1355
+
1356
+ token = self._register_media(str(path.resolve()))
1357
+ url = self._media_url(token, path.name)
1358
+ return await self._send_messages(chat_id, [_audio_message(url, duration_ms)])
1359
+
1360
+ async def send_video(
1361
+ self,
1362
+ chat_id: str,
1363
+ video_path: str,
1364
+ preview_path: Optional[str] = None,
1365
+ metadata: Optional[Dict[str, Any]] = None,
1366
+ ) -> SendResult:
1367
+ path = Path(video_path)
1368
+ if not path.exists() or not path.is_file():
1369
+ return SendResult(success=False, error=f"video file not found: {video_path}")
1370
+ if path.stat().st_size > LINE_AV_MAX_BYTES:
1371
+ return SendResult(success=False, error="video exceeds 200 MB LINE limit")
1372
+ if not self._client:
1373
+ return SendResult(success=False, error="LINE adapter not connected")
1374
+ if not self.public_base_url and self.webhook_host == "0.0.0.0":
1375
+ return SendResult(
1376
+ success=False,
1377
+ error="LINE_PUBLIC_URL must be set to send video",
1378
+ )
1379
+
1380
+ # LINE requires a previewImageUrl. Use one if supplied, otherwise
1381
+ # write a stdlib 1×1 PNG to /tmp and serve it. PR #8398.
1382
+ if preview_path and Path(preview_path).is_file():
1383
+ preview_token = self._register_media(str(Path(preview_path).resolve()))
1384
+ preview_filename = Path(preview_path).name
1385
+ else:
1386
+ tmp = tempfile.NamedTemporaryFile(suffix=".png", delete=False)
1387
+ try:
1388
+ tmp.write(_FALLBACK_PNG_PREVIEW)
1389
+ tmp.flush()
1390
+ tmp.close()
1391
+ preview_token = self._register_media(tmp.name, cleanup=True)
1392
+ preview_filename = "preview.png"
1393
+ except Exception:
1394
+ try:
1395
+ os.unlink(tmp.name)
1396
+ except OSError:
1397
+ pass
1398
+ raise
1399
+
1400
+ video_token = self._register_media(str(path.resolve()))
1401
+ video_url = self._media_url(video_token, path.name)
1402
+ preview_url = self._media_url(preview_token, preview_filename)
1403
+ return await self._send_messages(chat_id, [_video_message(video_url, preview_url)])
1404
+
1405
+ async def _send_messages(
1406
+ self,
1407
+ chat_id: str,
1408
+ messages: List[Dict[str, Any]],
1409
+ ) -> SendResult:
1410
+ """Send already-built message objects, batched at 5/call."""
1411
+ if not self._client:
1412
+ return SendResult(success=False, error="LINE adapter not connected")
1413
+ if not messages:
1414
+ return SendResult(success=True, message_id=None)
1415
+
1416
+ first_batch = messages[:LINE_MAX_MESSAGES_PER_CALL]
1417
+ rest = messages[LINE_MAX_MESSAGES_PER_CALL:]
1418
+
1419
+ # First batch: try reply token, fall back to push.
1420
+ token, used_reply = self._consume_reply_token(chat_id)
1421
+ if used_reply:
1422
+ try:
1423
+ await self._client.reply(token, first_batch)
1424
+ except Exception as exc:
1425
+ logger.info("LINE: reply token rejected (%s); falling back to push", exc)
1426
+ try:
1427
+ await self._client.push(chat_id, first_batch)
1428
+ except Exception as exc2:
1429
+ return SendResult(success=False, error=str(exc2))
1430
+ else:
1431
+ try:
1432
+ await self._client.push(chat_id, first_batch)
1433
+ except Exception as exc:
1434
+ return SendResult(success=False, error=str(exc))
1435
+
1436
+ # Subsequent batches: always push (reply token is single-use).
1437
+ while rest:
1438
+ batch = rest[:LINE_MAX_MESSAGES_PER_CALL]
1439
+ rest = rest[LINE_MAX_MESSAGES_PER_CALL:]
1440
+ try:
1441
+ await self._client.push(chat_id, batch)
1442
+ except Exception as exc:
1443
+ logger.warning("LINE: push for follow-up batch failed: %s", exc)
1444
+ return SendResult(success=False, error=str(exc))
1445
+
1446
+ return SendResult(success=True, message_id=None)
1447
+
1448
+
1449
+ def _is_relative_to(child: Path, parent: Path) -> bool:
1450
+ """Backport for Path.is_relative_to (Python 3.9+) — defensive against
1451
+ cwd-resolution differences across CI runners."""
1452
+ try:
1453
+ return child.resolve().is_relative_to(parent.resolve())
1454
+ except (AttributeError, ValueError):
1455
+ try:
1456
+ child.resolve().relative_to(parent.resolve())
1457
+ return True
1458
+ except ValueError:
1459
+ return False
1460
+
1461
+
1462
+ # ---------------------------------------------------------------------------
1463
+ # Plugin entry-point hooks
1464
+ # ---------------------------------------------------------------------------
1465
+
1466
+ def check_requirements() -> bool:
1467
+ """Plugin gate: require credentials AND aiohttp at runtime."""
1468
+ if not os.getenv("LINE_CHANNEL_ACCESS_TOKEN"):
1469
+ return False
1470
+ if not os.getenv("LINE_CHANNEL_SECRET"):
1471
+ return False
1472
+ try:
1473
+ import aiohttp # noqa: F401
1474
+ except ImportError:
1475
+ return False
1476
+ return True
1477
+
1478
+
1479
+ def validate_config(config) -> bool:
1480
+ extra = getattr(config, "extra", {}) or {}
1481
+ has_token = bool(
1482
+ os.getenv("LINE_CHANNEL_ACCESS_TOKEN") or extra.get("channel_access_token")
1483
+ )
1484
+ has_secret = bool(
1485
+ os.getenv("LINE_CHANNEL_SECRET") or extra.get("channel_secret")
1486
+ )
1487
+ return has_token and has_secret
1488
+
1489
+
1490
+ def is_connected(config) -> bool:
1491
+ """Surface in ``hermes status`` even before the adapter is instantiated."""
1492
+ return validate_config(config)
1493
+
1494
+
1495
+ def _env_enablement() -> Optional[Dict[str, Any]]:
1496
+ """Auto-seed PlatformConfig.extra from env-only setups.
1497
+
1498
+ Lets ``hermes status`` reflect a LINE configuration that lives entirely
1499
+ in ``.env`` without a ``platforms.line`` block in ``config.yaml``.
1500
+ Mirrors the IRC plugin's pattern.
1501
+ """
1502
+ if not (os.getenv("LINE_CHANNEL_ACCESS_TOKEN") and os.getenv("LINE_CHANNEL_SECRET")):
1503
+ return None
1504
+ seeded: Dict[str, Any] = {}
1505
+ if os.getenv("LINE_PORT"):
1506
+ try:
1507
+ seeded["port"] = int(os.environ["LINE_PORT"])
1508
+ except ValueError:
1509
+ pass
1510
+ if os.getenv("LINE_HOST"):
1511
+ seeded["host"] = os.environ["LINE_HOST"]
1512
+ if os.getenv("LINE_PUBLIC_URL"):
1513
+ seeded["public_url"] = os.environ["LINE_PUBLIC_URL"]
1514
+ if os.getenv("LINE_HOME_CHANNEL"):
1515
+ seeded["home_channel"] = os.environ["LINE_HOME_CHANNEL"]
1516
+ return seeded or {}
1517
+
1518
+
1519
+ async def _standalone_send(
1520
+ pconfig,
1521
+ chat_id: str,
1522
+ message: str,
1523
+ *,
1524
+ thread_id: Optional[str] = None,
1525
+ media_files: Optional[List[str]] = None,
1526
+ force_document: bool = False,
1527
+ ) -> Dict[str, Any]:
1528
+ """Out-of-process push delivery for cron jobs running detached from the gateway.
1529
+
1530
+ Without this hook ``deliver=line`` cron jobs fail with ``no live adapter``
1531
+ when cron runs as its own process. We always Push (reply tokens require
1532
+ an inbound webhook event we don't have in this path).
1533
+
1534
+ ``thread_id`` is accepted for signature parity but ignored — LINE has
1535
+ no native thread primitive on the channel-side API. ``media_files``
1536
+ likewise: cron-side media delivery requires a publicly-reachable URL,
1537
+ which the standalone path can't construct without binding the webhook
1538
+ server, so we send a text reference instead.
1539
+ """
1540
+ extra = getattr(pconfig, "extra", {}) or {}
1541
+ token = (
1542
+ os.getenv("LINE_CHANNEL_ACCESS_TOKEN")
1543
+ or extra.get("channel_access_token", "")
1544
+ )
1545
+ if not token or not chat_id:
1546
+ return {"error": "LINE standalone send: missing token or chat_id"}
1547
+
1548
+ plain = strip_markdown_preserving_urls(message or "")
1549
+ chunks = split_for_line(plain) or [""]
1550
+ messages = [_text_message(c) for c in chunks][:LINE_MAX_MESSAGES_PER_CALL]
1551
+ if media_files:
1552
+ # Tack on a hint so the recipient knows media was generated but not delivered.
1553
+ messages.append(_text_message(f"[{len(media_files)} attachment(s) generated; not deliverable from cron]"))
1554
+ messages = messages[:LINE_MAX_MESSAGES_PER_CALL]
1555
+
1556
+ client = _LineClient(token)
1557
+ try:
1558
+ await client.push(chat_id, messages)
1559
+ return {"success": True, "message_id": None}
1560
+ except Exception as exc:
1561
+ return {"error": str(exc)}
1562
+
1563
+
1564
+ def interactive_setup() -> None:
1565
+ """Minimal stdin wizard for ``hermes setup line``.
1566
+
1567
+ Mirrors the irc/teams style: prompts for the two required vars, plus
1568
+ one optional public URL. Writes to ``~/.hermes/.env`` via ``hermes_cli.config``.
1569
+ """
1570
+ print()
1571
+ print("LINE Messaging API setup")
1572
+ print("------------------------")
1573
+ print("Create a Messaging API channel at https://developers.line.biz/console/")
1574
+ print("then copy the values below.")
1575
+ print()
1576
+
1577
+ try:
1578
+ from hermes_cli.config import get_env_var, set_env_var
1579
+ except ImportError:
1580
+ print("hermes_cli.config not available; set LINE_* vars manually in ~/.hermes/.env")
1581
+ return
1582
+
1583
+ def _prompt(var: str, prompt: str, *, secret: bool = False) -> None:
1584
+ existing = get_env_var(var) if callable(get_env_var) else None
1585
+ suffix = " [keep current]" if existing else ""
1586
+ try:
1587
+ if secret:
1588
+ import getpass
1589
+ value = getpass.getpass(f"{prompt}{suffix}: ")
1590
+ else:
1591
+ value = input(f"{prompt}{suffix}: ").strip()
1592
+ except (EOFError, KeyboardInterrupt):
1593
+ print()
1594
+ return
1595
+ if value:
1596
+ set_env_var(var, value)
1597
+
1598
+ _prompt("LINE_CHANNEL_ACCESS_TOKEN", "Channel access token", secret=True)
1599
+ _prompt("LINE_CHANNEL_SECRET", "Channel secret", secret=True)
1600
+ _prompt("LINE_PUBLIC_URL", "Public HTTPS base URL (optional, e.g. https://my-tunnel.example.com)")
1601
+ _prompt("LINE_ALLOWED_USERS", "Allowed user IDs (comma-separated; blank=skip)")
1602
+ print("Done. Set the webhook URL in the LINE console to "
1603
+ "<your-public-url>/line/webhook and enable 'Use webhook'.")
1604
+
1605
+
1606
+ def register(ctx) -> None:
1607
+ """Plugin entry point — called by the Hermes plugin system at startup."""
1608
+ ctx.register_platform(
1609
+ name="line",
1610
+ label="LINE",
1611
+ adapter_factory=lambda cfg: LineAdapter(cfg),
1612
+ check_fn=check_requirements,
1613
+ validate_config=validate_config,
1614
+ is_connected=is_connected,
1615
+ required_env=["LINE_CHANNEL_ACCESS_TOKEN", "LINE_CHANNEL_SECRET"],
1616
+ install_hint="pip install aiohttp",
1617
+ setup_fn=interactive_setup,
1618
+ env_enablement_fn=_env_enablement,
1619
+ cron_deliver_env_var="LINE_HOME_CHANNEL",
1620
+ standalone_sender_fn=_standalone_send,
1621
+ allowed_users_env="LINE_ALLOWED_USERS",
1622
+ allow_all_env="LINE_ALLOW_ALL_USERS",
1623
+ # LINE per-bubble cap is 5000; smart-chunker uses 4500.
1624
+ max_message_length=LINE_SAFE_BUBBLE_CHARS,
1625
+ emoji="💚",
1626
+ pii_safe=False,
1627
+ allow_update_command=True,
1628
+ platform_hint=(
1629
+ "You are chatting via LINE Messaging API. LINE does NOT render "
1630
+ "Markdown — text bubbles show ** and # literally. Bare URLs are "
1631
+ "auto-linked, but \\[label\\](url) syntax is not. Each text bubble "
1632
+ "is capped at 5000 characters and at most 5 bubbles are sent per "
1633
+ "reply, so keep responses concise. Image/audio/video sending "
1634
+ "requires LINE_PUBLIC_URL configured to a publicly reachable HTTPS "
1635
+ "host. Slow responses surface a 'Get answer' button the user taps "
1636
+ "to fetch the reply via a fresh free token."
1637
+ ),
1638
+ )
1639
+