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.
- package/LICENSE +21 -0
- package/README.md +217 -0
- package/README.zh-CN.md +180 -0
- package/acp_adapter/__init__.py +1 -0
- package/acp_adapter/__main__.py +5 -0
- package/acp_adapter/auth.py +68 -0
- package/acp_adapter/bootstrap/__init__.py +0 -0
- package/acp_adapter/bootstrap/bootstrap_browser_tools.ps1 +288 -0
- package/acp_adapter/bootstrap/bootstrap_browser_tools.sh +399 -0
- package/acp_adapter/entry.py +292 -0
- package/acp_adapter/events.py +265 -0
- package/acp_adapter/permissions.py +148 -0
- package/acp_adapter/server.py +1713 -0
- package/acp_adapter/session.py +629 -0
- package/acp_adapter/tools.py +1180 -0
- package/agent/__init__.py +6 -0
- package/agent/__pycache__/__init__.cpython-312.pyc +0 -0
- package/agent/__pycache__/account_usage.cpython-312.pyc +0 -0
- package/agent/__pycache__/anthropic_adapter.cpython-312.pyc +0 -0
- package/agent/__pycache__/async_utils.cpython-312.pyc +0 -0
- package/agent/__pycache__/auxiliary_client.cpython-312.pyc +0 -0
- package/agent/__pycache__/codex_responses_adapter.cpython-312.pyc +0 -0
- package/agent/__pycache__/context_compressor.cpython-312.pyc +0 -0
- package/agent/__pycache__/context_engine.cpython-312.pyc +0 -0
- package/agent/__pycache__/context_references.cpython-312.pyc +0 -0
- package/agent/__pycache__/credential_pool.cpython-312.pyc +0 -0
- package/agent/__pycache__/curator.cpython-312.pyc +0 -0
- package/agent/__pycache__/display.cpython-312.pyc +0 -0
- package/agent/__pycache__/error_classifier.cpython-312.pyc +0 -0
- package/agent/__pycache__/file_safety.cpython-312.pyc +0 -0
- package/agent/__pycache__/google_code_assist.cpython-312.pyc +0 -0
- package/agent/__pycache__/google_oauth.cpython-312.pyc +0 -0
- package/agent/__pycache__/i18n.cpython-312.pyc +0 -0
- package/agent/__pycache__/image_gen_provider.cpython-312.pyc +0 -0
- package/agent/__pycache__/image_gen_registry.cpython-312.pyc +0 -0
- package/agent/__pycache__/insights.cpython-312.pyc +0 -0
- package/agent/__pycache__/lmstudio_reasoning.cpython-312.pyc +0 -0
- package/agent/__pycache__/manual_compression_feedback.cpython-312.pyc +0 -0
- package/agent/__pycache__/markdown_tables.cpython-312.pyc +0 -0
- package/agent/__pycache__/memory_manager.cpython-312.pyc +0 -0
- package/agent/__pycache__/memory_provider.cpython-312.pyc +0 -0
- package/agent/__pycache__/model_metadata.cpython-312.pyc +0 -0
- package/agent/__pycache__/models_dev.cpython-312.pyc +0 -0
- package/agent/__pycache__/moonshot_schema.cpython-312.pyc +0 -0
- package/agent/__pycache__/onboarding.cpython-312.pyc +0 -0
- package/agent/__pycache__/portal_tags.cpython-312.pyc +0 -0
- package/agent/__pycache__/prompt_builder.cpython-312.pyc +0 -0
- package/agent/__pycache__/prompt_caching.cpython-312.pyc +0 -0
- package/agent/__pycache__/redact.cpython-312.pyc +0 -0
- package/agent/__pycache__/retry_utils.cpython-312.pyc +0 -0
- package/agent/__pycache__/shell_hooks.cpython-312.pyc +0 -0
- package/agent/__pycache__/skill_commands.cpython-312.pyc +0 -0
- package/agent/__pycache__/skill_preprocessing.cpython-312.pyc +0 -0
- package/agent/__pycache__/skill_utils.cpython-312.pyc +0 -0
- package/agent/__pycache__/subdirectory_hints.cpython-312.pyc +0 -0
- package/agent/__pycache__/think_scrubber.cpython-312.pyc +0 -0
- package/agent/__pycache__/title_generator.cpython-312.pyc +0 -0
- package/agent/__pycache__/tool_guardrails.cpython-312.pyc +0 -0
- package/agent/__pycache__/tool_result_classification.cpython-312.pyc +0 -0
- package/agent/__pycache__/trajectory.cpython-312.pyc +0 -0
- package/agent/__pycache__/usage_pricing.cpython-312.pyc +0 -0
- package/agent/__pycache__/video_gen_provider.cpython-312.pyc +0 -0
- package/agent/__pycache__/video_gen_registry.cpython-312.pyc +0 -0
- package/agent/__pycache__/web_search_provider.cpython-312.pyc +0 -0
- package/agent/__pycache__/web_search_registry.cpython-312.pyc +0 -0
- package/agent/account_usage.py +326 -0
- package/agent/anthropic_adapter.py +2087 -0
- package/agent/async_utils.py +68 -0
- package/agent/auxiliary_client.py +4893 -0
- package/agent/bedrock_adapter.py +1276 -0
- package/agent/codex_responses_adapter.py +1084 -0
- package/agent/context_compressor.py +1583 -0
- package/agent/context_engine.py +211 -0
- package/agent/context_references.py +519 -0
- package/agent/copilot_acp_client.py +684 -0
- package/agent/credential_pool.py +1780 -0
- package/agent/credential_sources.py +449 -0
- package/agent/curator.py +1782 -0
- package/agent/curator_backup.py +694 -0
- package/agent/display.py +987 -0
- package/agent/error_classifier.py +1058 -0
- package/agent/file_safety.py +112 -0
- package/agent/gemini_cloudcode_adapter.py +909 -0
- package/agent/gemini_native_adapter.py +971 -0
- package/agent/gemini_schema.py +99 -0
- package/agent/google_code_assist.py +452 -0
- package/agent/google_oauth.py +1062 -0
- package/agent/i18n.py +258 -0
- package/agent/image_gen_provider.py +243 -0
- package/agent/image_gen_registry.py +145 -0
- package/agent/image_routing.py +301 -0
- package/agent/insights.py +931 -0
- package/agent/lmstudio_reasoning.py +48 -0
- package/agent/lsp/__init__.py +106 -0
- package/agent/lsp/__pycache__/__init__.cpython-312.pyc +0 -0
- package/agent/lsp/__pycache__/cli.cpython-312.pyc +0 -0
- package/agent/lsp/__pycache__/client.cpython-312.pyc +0 -0
- package/agent/lsp/__pycache__/eventlog.cpython-312.pyc +0 -0
- package/agent/lsp/__pycache__/manager.cpython-312.pyc +0 -0
- package/agent/lsp/__pycache__/protocol.cpython-312.pyc +0 -0
- package/agent/lsp/__pycache__/servers.cpython-312.pyc +0 -0
- package/agent/lsp/__pycache__/workspace.cpython-312.pyc +0 -0
- package/agent/lsp/cli.py +308 -0
- package/agent/lsp/client.py +930 -0
- package/agent/lsp/eventlog.py +213 -0
- package/agent/lsp/install.py +376 -0
- package/agent/lsp/manager.py +644 -0
- package/agent/lsp/protocol.py +196 -0
- package/agent/lsp/range_shift.py +149 -0
- package/agent/lsp/reporter.py +78 -0
- package/agent/lsp/servers.py +1040 -0
- package/agent/lsp/workspace.py +223 -0
- package/agent/manual_compression_feedback.py +49 -0
- package/agent/markdown_tables.py +309 -0
- package/agent/memory_manager.py +556 -0
- package/agent/memory_provider.py +279 -0
- package/agent/model_metadata.py +1827 -0
- package/agent/models_dev.py +724 -0
- package/agent/moonshot_schema.py +231 -0
- package/agent/nous_rate_guard.py +326 -0
- package/agent/onboarding.py +193 -0
- package/agent/plugin_llm.py +1046 -0
- package/agent/portal_tags.py +64 -0
- package/agent/prompt_builder.py +1457 -0
- package/agent/prompt_caching.py +79 -0
- package/agent/rate_limit_tracker.py +246 -0
- package/agent/redact.py +403 -0
- package/agent/retry_utils.py +57 -0
- package/agent/shell_hooks.py +837 -0
- package/agent/skill_commands.py +502 -0
- package/agent/skill_preprocessing.py +131 -0
- package/agent/skill_utils.py +512 -0
- package/agent/subdirectory_hints.py +224 -0
- package/agent/think_scrubber.py +386 -0
- package/agent/title_generator.py +171 -0
- package/agent/tool_guardrails.py +458 -0
- package/agent/tool_result_classification.py +26 -0
- package/agent/trajectory.py +56 -0
- package/agent/transports/__init__.py +68 -0
- package/agent/transports/__pycache__/__init__.cpython-312.pyc +0 -0
- package/agent/transports/__pycache__/anthropic.cpython-312.pyc +0 -0
- package/agent/transports/__pycache__/base.cpython-312.pyc +0 -0
- package/agent/transports/__pycache__/bedrock.cpython-312.pyc +0 -0
- package/agent/transports/__pycache__/chat_completions.cpython-312.pyc +0 -0
- package/agent/transports/__pycache__/codex.cpython-312.pyc +0 -0
- package/agent/transports/__pycache__/types.cpython-312.pyc +0 -0
- package/agent/transports/anthropic.py +179 -0
- package/agent/transports/base.py +89 -0
- package/agent/transports/bedrock.py +154 -0
- package/agent/transports/chat_completions.py +614 -0
- package/agent/transports/codex.py +283 -0
- package/agent/transports/codex_app_server.py +368 -0
- package/agent/transports/codex_app_server_session.py +810 -0
- package/agent/transports/codex_event_projector.py +312 -0
- package/agent/transports/hermes_tools_mcp_server.py +233 -0
- package/agent/transports/types.py +162 -0
- package/agent/usage_pricing.py +877 -0
- package/agent/video_gen_provider.py +300 -0
- package/agent/video_gen_registry.py +117 -0
- package/agent/web_search_provider.py +221 -0
- package/agent/web_search_registry.py +262 -0
- package/assets/banner.png +0 -0
- package/batch_runner.py +1303 -0
- package/bin/calvyn.js +67 -0
- package/calvyn_bootstrap.py +130 -0
- package/calvyn_constants.py +346 -0
- package/calvyn_logging.py +390 -0
- package/calvyn_state.py +2967 -0
- package/calvyn_time.py +105 -0
- package/cli.py +14160 -0
- package/cron/__init__.py +42 -0
- package/cron/__pycache__/__init__.cpython-312.pyc +0 -0
- package/cron/__pycache__/jobs.cpython-312.pyc +0 -0
- package/cron/__pycache__/scheduler.cpython-312.pyc +0 -0
- package/cron/jobs.py +1160 -0
- package/cron/scheduler.py +1832 -0
- package/gateway/__init__.py +35 -0
- package/gateway/__pycache__/__init__.cpython-312.pyc +0 -0
- package/gateway/__pycache__/channel_directory.cpython-312.pyc +0 -0
- package/gateway/__pycache__/config.cpython-312.pyc +0 -0
- package/gateway/__pycache__/delivery.cpython-312.pyc +0 -0
- package/gateway/__pycache__/display_config.cpython-312.pyc +0 -0
- package/gateway/__pycache__/hooks.cpython-312.pyc +0 -0
- package/gateway/__pycache__/pairing.cpython-312.pyc +0 -0
- package/gateway/__pycache__/platform_registry.cpython-312.pyc +0 -0
- package/gateway/__pycache__/restart.cpython-312.pyc +0 -0
- package/gateway/__pycache__/run.cpython-312.pyc +0 -0
- package/gateway/__pycache__/runtime_footer.cpython-312.pyc +0 -0
- package/gateway/__pycache__/session.cpython-312.pyc +0 -0
- package/gateway/__pycache__/session_context.cpython-312.pyc +0 -0
- package/gateway/__pycache__/shutdown_forensics.cpython-312.pyc +0 -0
- package/gateway/__pycache__/slash_access.cpython-312.pyc +0 -0
- package/gateway/__pycache__/status.cpython-312.pyc +0 -0
- package/gateway/__pycache__/stream_consumer.cpython-312.pyc +0 -0
- package/gateway/__pycache__/whatsapp_identity.cpython-312.pyc +0 -0
- package/gateway/assets/telegram-botfather-threads-settings.jpg +0 -0
- package/gateway/builtin_hooks/__init__.py +1 -0
- package/gateway/channel_directory.py +357 -0
- package/gateway/config.py +1873 -0
- package/gateway/delivery.py +258 -0
- package/gateway/display_config.py +206 -0
- package/gateway/hooks.py +210 -0
- package/gateway/mirror.py +179 -0
- package/gateway/pairing.py +322 -0
- package/gateway/platform_registry.py +260 -0
- package/gateway/platforms/ADDING_A_PLATFORM.md +374 -0
- package/gateway/platforms/__init__.py +45 -0
- package/gateway/platforms/__pycache__/__init__.cpython-312.pyc +0 -0
- package/gateway/platforms/__pycache__/base.cpython-312.pyc +0 -0
- package/gateway/platforms/__pycache__/helpers.cpython-312.pyc +0 -0
- package/gateway/platforms/__pycache__/telegram.cpython-312.pyc +0 -0
- package/gateway/platforms/__pycache__/telegram_network.cpython-312.pyc +0 -0
- package/gateway/platforms/__pycache__/yuanbao.cpython-312.pyc +0 -0
- package/gateway/platforms/__pycache__/yuanbao_media.cpython-312.pyc +0 -0
- package/gateway/platforms/__pycache__/yuanbao_proto.cpython-312.pyc +0 -0
- package/gateway/platforms/_http_client_limits.py +84 -0
- package/gateway/platforms/api_server.py +3488 -0
- package/gateway/platforms/base.py +3747 -0
- package/gateway/platforms/bluebubbles.py +937 -0
- package/gateway/platforms/dingtalk.py +1473 -0
- package/gateway/platforms/discord.py +5584 -0
- package/gateway/platforms/email.py +773 -0
- package/gateway/platforms/feishu.py +5059 -0
- package/gateway/platforms/feishu_comment.py +1382 -0
- package/gateway/platforms/feishu_comment_rules.py +430 -0
- package/gateway/platforms/helpers.py +279 -0
- package/gateway/platforms/homeassistant.py +449 -0
- package/gateway/platforms/matrix.py +2777 -0
- package/gateway/platforms/mattermost.py +852 -0
- package/gateway/platforms/msgraph_webhook.py +397 -0
- package/gateway/platforms/qqbot/__init__.py +91 -0
- package/gateway/platforms/qqbot/adapter.py +3072 -0
- package/gateway/platforms/qqbot/chunked_upload.py +602 -0
- package/gateway/platforms/qqbot/constants.py +74 -0
- package/gateway/platforms/qqbot/crypto.py +45 -0
- package/gateway/platforms/qqbot/keyboards.py +473 -0
- package/gateway/platforms/qqbot/onboard.py +220 -0
- package/gateway/platforms/qqbot/utils.py +71 -0
- package/gateway/platforms/signal.py +1518 -0
- package/gateway/platforms/signal_rate_limit.py +369 -0
- package/gateway/platforms/slack.py +3028 -0
- package/gateway/platforms/sms.py +377 -0
- package/gateway/platforms/telegram.py +4836 -0
- package/gateway/platforms/telegram_network.py +249 -0
- package/gateway/platforms/webhook.py +806 -0
- package/gateway/platforms/wecom.py +1610 -0
- package/gateway/platforms/wecom_callback.py +403 -0
- package/gateway/platforms/wecom_crypto.py +142 -0
- package/gateway/platforms/weixin.py +2170 -0
- package/gateway/platforms/whatsapp.py +1283 -0
- package/gateway/platforms/yuanbao.py +4873 -0
- package/gateway/platforms/yuanbao_media.py +645 -0
- package/gateway/platforms/yuanbao_proto.py +1209 -0
- package/gateway/platforms/yuanbao_sticker.py +558 -0
- package/gateway/restart.py +20 -0
- package/gateway/run.py +17074 -0
- package/gateway/runtime_footer.py +150 -0
- package/gateway/session.py +1399 -0
- package/gateway/session_context.py +156 -0
- package/gateway/shutdown_forensics.py +462 -0
- package/gateway/slash_access.py +229 -0
- package/gateway/status.py +972 -0
- package/gateway/sticker_cache.py +111 -0
- package/gateway/stream_consumer.py +1286 -0
- package/gateway/whatsapp_identity.py +156 -0
- package/hermes_cli/__init__.py +47 -0
- package/hermes_cli/__pycache__/__init__.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/_parser.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/auth.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/banner.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/browser_connect.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/callbacks.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/checkpoints.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/cli_output.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/codex_models.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/codex_runtime_switch.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/colors.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/commands.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/config.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/copilot_auth.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/curator.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/curses_ui.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/debug.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/default_soul.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/env_loader.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/fallback_cmd.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/gateway.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/gateway_windows.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/goals.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/inventory.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/kanban.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/kanban_db.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/main.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/model_catalog.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/model_normalize.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/model_switch.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/models.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/nous_subscription.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/pairing.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/platforms.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/plugins.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/profiles.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/providers.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/pt_input_extras.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/runtime_provider.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/security_advisories.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/setup.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/skills_hub.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/skin_engine.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/stdio.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/timeouts.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/tips.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/tools_config.cpython-312.pyc +0 -0
- package/hermes_cli/__pycache__/voice.cpython-312.pyc +0 -0
- package/hermes_cli/_parser.py +365 -0
- package/hermes_cli/_subprocess_compat.py +175 -0
- package/hermes_cli/auth.py +6299 -0
- package/hermes_cli/auth_commands.py +749 -0
- package/hermes_cli/azure_detect.py +300 -0
- package/hermes_cli/backup.py +938 -0
- package/hermes_cli/banner.py +703 -0
- package/hermes_cli/browser_connect.py +139 -0
- package/hermes_cli/callbacks.py +243 -0
- package/hermes_cli/checkpoints.py +244 -0
- package/hermes_cli/claw.py +810 -0
- package/hermes_cli/cli_output.py +78 -0
- package/hermes_cli/clipboard.py +495 -0
- package/hermes_cli/codex_models.py +198 -0
- package/hermes_cli/codex_runtime_plugin_migration.py +757 -0
- package/hermes_cli/codex_runtime_switch.py +266 -0
- package/hermes_cli/colors.py +38 -0
- package/hermes_cli/commands.py +1728 -0
- package/hermes_cli/completion.py +315 -0
- package/hermes_cli/config.py +5382 -0
- package/hermes_cli/copilot_auth.py +392 -0
- package/hermes_cli/cron.py +313 -0
- package/hermes_cli/curator.py +598 -0
- package/hermes_cli/curses_ui.py +472 -0
- package/hermes_cli/debug.py +747 -0
- package/hermes_cli/default_soul.py +11 -0
- package/hermes_cli/dep_ensure.py +107 -0
- package/hermes_cli/dingtalk_auth.py +293 -0
- package/hermes_cli/doctor.py +1863 -0
- package/hermes_cli/dump.py +326 -0
- package/hermes_cli/env_loader.py +175 -0
- package/hermes_cli/fallback_cmd.py +361 -0
- package/hermes_cli/gateway.py +5422 -0
- package/hermes_cli/gateway_windows.py +692 -0
- package/hermes_cli/goals.py +757 -0
- package/hermes_cli/hooks.py +385 -0
- package/hermes_cli/inventory.py +240 -0
- package/hermes_cli/kanban.py +2252 -0
- package/hermes_cli/kanban_db.py +4840 -0
- package/hermes_cli/kanban_diagnostics.py +776 -0
- package/hermes_cli/kanban_specify.py +266 -0
- package/hermes_cli/logs.py +391 -0
- package/hermes_cli/main.py +12396 -0
- package/hermes_cli/mcp_config.py +781 -0
- package/hermes_cli/memory_setup.py +465 -0
- package/hermes_cli/model_catalog.py +330 -0
- package/hermes_cli/model_normalize.py +473 -0
- package/hermes_cli/model_switch.py +1777 -0
- package/hermes_cli/models.py +3789 -0
- package/hermes_cli/nous_subscription.py +799 -0
- package/hermes_cli/oneshot.py +351 -0
- package/hermes_cli/pairing.py +115 -0
- package/hermes_cli/platforms.py +83 -0
- package/hermes_cli/plugins.py +1562 -0
- package/hermes_cli/plugins_cmd.py +1587 -0
- package/hermes_cli/profile_distribution.py +703 -0
- package/hermes_cli/profiles.py +1319 -0
- package/hermes_cli/providers.py +720 -0
- package/hermes_cli/proxy/__init__.py +20 -0
- package/hermes_cli/proxy/adapters/__init__.py +35 -0
- package/hermes_cli/proxy/adapters/base.py +94 -0
- package/hermes_cli/proxy/adapters/nous_portal.py +137 -0
- package/hermes_cli/proxy/cli.py +141 -0
- package/hermes_cli/proxy/server.py +265 -0
- package/hermes_cli/pt_input_extras.py +83 -0
- package/hermes_cli/pty_bridge.py +237 -0
- package/hermes_cli/relaunch.py +205 -0
- package/hermes_cli/runtime_provider.py +1428 -0
- package/hermes_cli/security_advisories.py +452 -0
- package/hermes_cli/setup.py +3559 -0
- package/hermes_cli/skills_config.py +177 -0
- package/hermes_cli/skills_hub.py +1595 -0
- package/hermes_cli/skin_engine.py +929 -0
- package/hermes_cli/slack_cli.py +160 -0
- package/hermes_cli/status.py +550 -0
- package/hermes_cli/stdio.py +252 -0
- package/hermes_cli/timeouts.py +82 -0
- package/hermes_cli/tips.py +487 -0
- package/hermes_cli/tools_config.py +3151 -0
- package/hermes_cli/uninstall.py +681 -0
- package/hermes_cli/vercel_auth.py +70 -0
- package/hermes_cli/voice.py +846 -0
- package/hermes_cli/web_server.py +4438 -0
- package/hermes_cli/webhook.py +275 -0
- package/locales/af.yaml +350 -0
- package/locales/de.yaml +350 -0
- package/locales/en.yaml +365 -0
- package/locales/es.yaml +350 -0
- package/locales/fr.yaml +350 -0
- package/locales/ga.yaml +354 -0
- package/locales/hu.yaml +350 -0
- package/locales/it.yaml +350 -0
- package/locales/ja.yaml +350 -0
- package/locales/ko.yaml +350 -0
- package/locales/pt.yaml +350 -0
- package/locales/ru.yaml +350 -0
- package/locales/tr.yaml +350 -0
- package/locales/uk.yaml +350 -0
- package/locales/zh-hant.yaml +350 -0
- package/locales/zh.yaml +350 -0
- package/mcp_serve.py +898 -0
- package/model_tools.py +899 -0
- package/optional-skills/DESCRIPTION.md +24 -0
- package/optional-skills/autonomous-ai-agents/DESCRIPTION.md +2 -0
- package/optional-skills/autonomous-ai-agents/blackbox/SKILL.md +144 -0
- package/optional-skills/autonomous-ai-agents/honcho/SKILL.md +431 -0
- package/optional-skills/blockchain/evm/SKILL.md +211 -0
- package/optional-skills/blockchain/evm/scripts/evm_client.py +1508 -0
- package/optional-skills/blockchain/hyperliquid/SKILL.md +211 -0
- package/optional-skills/blockchain/hyperliquid/scripts/hyperliquid_client.py +1660 -0
- package/optional-skills/blockchain/solana/SKILL.md +208 -0
- package/optional-skills/blockchain/solana/scripts/solana_client.py +698 -0
- package/optional-skills/communication/DESCRIPTION.md +1 -0
- package/optional-skills/communication/one-three-one-rule/SKILL.md +104 -0
- package/optional-skills/creative/blender-mcp/SKILL.md +117 -0
- package/optional-skills/creative/concept-diagrams/SKILL.md +362 -0
- package/optional-skills/creative/concept-diagrams/examples/apartment-floor-plan-conversion.md +244 -0
- package/optional-skills/creative/concept-diagrams/examples/automated-password-reset-flow.md +276 -0
- package/optional-skills/creative/concept-diagrams/examples/autonomous-llm-research-agent-flow.md +240 -0
- package/optional-skills/creative/concept-diagrams/examples/banana-journey-tree-to-smoothie.md +161 -0
- package/optional-skills/creative/concept-diagrams/examples/commercial-aircraft-structure.md +209 -0
- package/optional-skills/creative/concept-diagrams/examples/cpu-ooo-microarchitecture.md +236 -0
- package/optional-skills/creative/concept-diagrams/examples/electricity-grid-flow.md +182 -0
- package/optional-skills/creative/concept-diagrams/examples/feature-film-production-pipeline.md +172 -0
- package/optional-skills/creative/concept-diagrams/examples/hospital-emergency-department-flow.md +165 -0
- package/optional-skills/creative/concept-diagrams/examples/ml-benchmark-grouped-bar-chart.md +114 -0
- package/optional-skills/creative/concept-diagrams/examples/place-order-uml-sequence.md +325 -0
- package/optional-skills/creative/concept-diagrams/examples/smart-city-infrastructure.md +173 -0
- package/optional-skills/creative/concept-diagrams/examples/smartphone-layer-anatomy.md +154 -0
- package/optional-skills/creative/concept-diagrams/examples/sn2-reaction-mechanism.md +247 -0
- package/optional-skills/creative/concept-diagrams/examples/wind-turbine-structure.md +338 -0
- package/optional-skills/creative/concept-diagrams/references/dashboard-patterns.md +43 -0
- package/optional-skills/creative/concept-diagrams/references/infrastructure-patterns.md +144 -0
- package/optional-skills/creative/concept-diagrams/references/physical-shape-cookbook.md +42 -0
- package/optional-skills/creative/concept-diagrams/templates/template.html +174 -0
- package/optional-skills/creative/hyperframes/SKILL.md +191 -0
- package/optional-skills/creative/hyperframes/references/cli.md +185 -0
- package/optional-skills/creative/hyperframes/references/composition.md +129 -0
- package/optional-skills/creative/hyperframes/references/features.md +289 -0
- package/optional-skills/creative/hyperframes/references/gsap.md +136 -0
- package/optional-skills/creative/hyperframes/references/troubleshooting.md +137 -0
- package/optional-skills/creative/hyperframes/references/website-to-video.md +145 -0
- package/optional-skills/creative/hyperframes/scripts/setup.sh +135 -0
- package/optional-skills/creative/kanban-video-orchestrator/SKILL.md +207 -0
- package/optional-skills/creative/kanban-video-orchestrator/assets/brief.md.tmpl +79 -0
- package/optional-skills/creative/kanban-video-orchestrator/assets/setup.sh.tmpl +185 -0
- package/optional-skills/creative/kanban-video-orchestrator/assets/soul.md.tmpl +38 -0
- package/optional-skills/creative/kanban-video-orchestrator/references/examples.md +227 -0
- package/optional-skills/creative/kanban-video-orchestrator/references/intake.md +166 -0
- package/optional-skills/creative/kanban-video-orchestrator/references/kanban-setup.md +276 -0
- package/optional-skills/creative/kanban-video-orchestrator/references/monitoring.md +180 -0
- package/optional-skills/creative/kanban-video-orchestrator/references/role-archetypes.md +298 -0
- package/optional-skills/creative/kanban-video-orchestrator/references/tool-matrix.md +317 -0
- package/optional-skills/creative/kanban-video-orchestrator/scripts/bootstrap_pipeline.py +501 -0
- package/optional-skills/creative/kanban-video-orchestrator/scripts/monitor.py +195 -0
- package/optional-skills/creative/meme-generation/EXAMPLES.md +46 -0
- package/optional-skills/creative/meme-generation/SKILL.md +130 -0
- package/optional-skills/creative/meme-generation/scripts/generate_meme.py +471 -0
- package/optional-skills/creative/meme-generation/scripts/templates.json +97 -0
- package/optional-skills/devops/cli/SKILL.md +156 -0
- package/optional-skills/devops/cli/references/app-discovery.md +112 -0
- package/optional-skills/devops/cli/references/authentication.md +59 -0
- package/optional-skills/devops/cli/references/cli-reference.md +104 -0
- package/optional-skills/devops/cli/references/running-apps.md +171 -0
- package/optional-skills/devops/docker-management/SKILL.md +281 -0
- package/optional-skills/devops/pinggy-tunnel/SKILL.md +309 -0
- package/optional-skills/devops/watchers/SKILL.md +112 -0
- package/optional-skills/devops/watchers/scripts/_watermark.py +148 -0
- package/optional-skills/devops/watchers/scripts/watch_github.py +168 -0
- package/optional-skills/devops/watchers/scripts/watch_http_json.py +131 -0
- package/optional-skills/devops/watchers/scripts/watch_rss.py +121 -0
- package/optional-skills/dogfood/DESCRIPTION.md +3 -0
- package/optional-skills/dogfood/adversarial-ux-test/SKILL.md +191 -0
- package/optional-skills/email/agentmail/SKILL.md +126 -0
- package/optional-skills/finance/3-statement-model/SKILL.md +433 -0
- package/optional-skills/finance/3-statement-model/references/formatting.md +118 -0
- package/optional-skills/finance/3-statement-model/references/formulas.md +292 -0
- package/optional-skills/finance/3-statement-model/references/sec-filings.md +125 -0
- package/optional-skills/finance/comps-analysis/SKILL.md +662 -0
- package/optional-skills/finance/dcf-model/SKILL.md +1270 -0
- package/optional-skills/finance/dcf-model/TROUBLESHOOTING.md +40 -0
- package/optional-skills/finance/dcf-model/requirements.txt +7 -0
- package/optional-skills/finance/dcf-model/scripts/validate_dcf.py +292 -0
- package/optional-skills/finance/excel-author/SKILL.md +244 -0
- package/optional-skills/finance/excel-author/scripts/recalc.py +88 -0
- package/optional-skills/finance/lbo-model/SKILL.md +291 -0
- package/optional-skills/finance/merger-model/SKILL.md +144 -0
- package/optional-skills/finance/pptx-author/SKILL.md +173 -0
- package/optional-skills/finance/stocks/SKILL.md +95 -0
- package/optional-skills/finance/stocks/scripts/stocks_client.py +755 -0
- package/optional-skills/health/DESCRIPTION.md +1 -0
- package/optional-skills/health/fitness-nutrition/SKILL.md +256 -0
- package/optional-skills/health/fitness-nutrition/references/FORMULAS.md +100 -0
- package/optional-skills/health/fitness-nutrition/scripts/body_calc.py +210 -0
- package/optional-skills/health/fitness-nutrition/scripts/nutrition_search.py +86 -0
- package/optional-skills/health/neuroskill-bci/SKILL.md +459 -0
- package/optional-skills/health/neuroskill-bci/references/api.md +286 -0
- package/optional-skills/health/neuroskill-bci/references/metrics.md +220 -0
- package/optional-skills/health/neuroskill-bci/references/protocols.md +452 -0
- package/optional-skills/mcp/DESCRIPTION.md +3 -0
- package/optional-skills/mcp/fastmcp/SKILL.md +300 -0
- package/optional-skills/mcp/fastmcp/references/fastmcp-cli.md +110 -0
- package/optional-skills/mcp/fastmcp/scripts/scaffold_fastmcp.py +56 -0
- package/optional-skills/mcp/fastmcp/templates/api_wrapper.py +54 -0
- package/optional-skills/mcp/fastmcp/templates/database_server.py +77 -0
- package/optional-skills/mcp/fastmcp/templates/file_processor.py +55 -0
- package/optional-skills/mcp/mcporter/SKILL.md +123 -0
- package/optional-skills/migration/DESCRIPTION.md +2 -0
- package/optional-skills/migration/openclaw-migration/SKILL.md +298 -0
- package/optional-skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py +3136 -0
- package/optional-skills/mlops/accelerate/SKILL.md +336 -0
- package/optional-skills/mlops/accelerate/references/custom-plugins.md +453 -0
- package/optional-skills/mlops/accelerate/references/megatron-integration.md +489 -0
- package/optional-skills/mlops/accelerate/references/performance.md +525 -0
- package/optional-skills/mlops/chroma/SKILL.md +410 -0
- package/optional-skills/mlops/chroma/references/integration.md +38 -0
- package/optional-skills/mlops/clip/SKILL.md +257 -0
- package/optional-skills/mlops/clip/references/applications.md +207 -0
- package/optional-skills/mlops/faiss/SKILL.md +225 -0
- package/optional-skills/mlops/faiss/references/index_types.md +280 -0
- package/optional-skills/mlops/flash-attention/SKILL.md +367 -0
- package/optional-skills/mlops/flash-attention/references/benchmarks.md +215 -0
- package/optional-skills/mlops/flash-attention/references/transformers-integration.md +293 -0
- package/optional-skills/mlops/guidance/SKILL.md +576 -0
- package/optional-skills/mlops/guidance/references/backends.md +554 -0
- package/optional-skills/mlops/guidance/references/constraints.md +674 -0
- package/optional-skills/mlops/guidance/references/examples.md +767 -0
- package/optional-skills/mlops/huggingface-tokenizers/SKILL.md +520 -0
- package/optional-skills/mlops/huggingface-tokenizers/references/algorithms.md +653 -0
- package/optional-skills/mlops/huggingface-tokenizers/references/integration.md +637 -0
- package/optional-skills/mlops/huggingface-tokenizers/references/pipeline.md +723 -0
- package/optional-skills/mlops/huggingface-tokenizers/references/training.md +565 -0
- package/optional-skills/mlops/inference/outlines/SKILL.md +656 -0
- package/optional-skills/mlops/inference/outlines/references/backends.md +615 -0
- package/optional-skills/mlops/inference/outlines/references/examples.md +773 -0
- package/optional-skills/mlops/inference/outlines/references/json_generation.md +652 -0
- package/optional-skills/mlops/instructor/SKILL.md +744 -0
- package/optional-skills/mlops/instructor/references/examples.md +107 -0
- package/optional-skills/mlops/instructor/references/providers.md +70 -0
- package/optional-skills/mlops/instructor/references/validation.md +606 -0
- package/optional-skills/mlops/lambda-labs/SKILL.md +549 -0
- package/optional-skills/mlops/lambda-labs/references/advanced-usage.md +611 -0
- package/optional-skills/mlops/lambda-labs/references/troubleshooting.md +530 -0
- package/optional-skills/mlops/llava/SKILL.md +308 -0
- package/optional-skills/mlops/llava/references/training.md +197 -0
- package/optional-skills/mlops/modal/SKILL.md +345 -0
- package/optional-skills/mlops/modal/references/advanced-usage.md +503 -0
- package/optional-skills/mlops/modal/references/troubleshooting.md +494 -0
- package/optional-skills/mlops/nemo-curator/SKILL.md +387 -0
- package/optional-skills/mlops/nemo-curator/references/deduplication.md +87 -0
- package/optional-skills/mlops/nemo-curator/references/filtering.md +102 -0
- package/optional-skills/mlops/peft/SKILL.md +435 -0
- package/optional-skills/mlops/peft/references/advanced-usage.md +514 -0
- package/optional-skills/mlops/peft/references/troubleshooting.md +480 -0
- package/optional-skills/mlops/pinecone/SKILL.md +362 -0
- package/optional-skills/mlops/pinecone/references/deployment.md +181 -0
- package/optional-skills/mlops/pytorch-fsdp/SKILL.md +130 -0
- package/optional-skills/mlops/pytorch-fsdp/references/index.md +7 -0
- package/optional-skills/mlops/pytorch-fsdp/references/other.md +4261 -0
- package/optional-skills/mlops/pytorch-lightning/SKILL.md +350 -0
- package/optional-skills/mlops/pytorch-lightning/references/callbacks.md +436 -0
- package/optional-skills/mlops/pytorch-lightning/references/distributed.md +490 -0
- package/optional-skills/mlops/pytorch-lightning/references/hyperparameter-tuning.md +556 -0
- package/optional-skills/mlops/qdrant/SKILL.md +497 -0
- package/optional-skills/mlops/qdrant/references/advanced-usage.md +648 -0
- package/optional-skills/mlops/qdrant/references/troubleshooting.md +631 -0
- package/optional-skills/mlops/saelens/SKILL.md +390 -0
- package/optional-skills/mlops/saelens/references/README.md +69 -0
- package/optional-skills/mlops/saelens/references/api.md +333 -0
- package/optional-skills/mlops/saelens/references/tutorials.md +318 -0
- package/optional-skills/mlops/simpo/SKILL.md +223 -0
- package/optional-skills/mlops/simpo/references/datasets.md +478 -0
- package/optional-skills/mlops/simpo/references/hyperparameters.md +452 -0
- package/optional-skills/mlops/simpo/references/loss-functions.md +350 -0
- package/optional-skills/mlops/slime/SKILL.md +468 -0
- package/optional-skills/mlops/slime/references/api-reference.md +392 -0
- package/optional-skills/mlops/slime/references/troubleshooting.md +386 -0
- package/optional-skills/mlops/stable-diffusion/SKILL.md +523 -0
- package/optional-skills/mlops/stable-diffusion/references/advanced-usage.md +716 -0
- package/optional-skills/mlops/stable-diffusion/references/troubleshooting.md +555 -0
- package/optional-skills/mlops/tensorrt-llm/SKILL.md +191 -0
- package/optional-skills/mlops/tensorrt-llm/references/multi-gpu.md +298 -0
- package/optional-skills/mlops/tensorrt-llm/references/optimization.md +242 -0
- package/optional-skills/mlops/tensorrt-llm/references/serving.md +470 -0
- package/optional-skills/mlops/torchtitan/SKILL.md +362 -0
- package/optional-skills/mlops/torchtitan/references/checkpoint.md +181 -0
- package/optional-skills/mlops/torchtitan/references/custom-models.md +258 -0
- package/optional-skills/mlops/torchtitan/references/float8.md +133 -0
- package/optional-skills/mlops/torchtitan/references/fsdp.md +126 -0
- package/optional-skills/mlops/training/axolotl/SKILL.md +166 -0
- package/optional-skills/mlops/training/axolotl/references/api.md +5548 -0
- package/optional-skills/mlops/training/axolotl/references/dataset-formats.md +1029 -0
- package/optional-skills/mlops/training/axolotl/references/index.md +15 -0
- package/optional-skills/mlops/training/axolotl/references/other.md +3563 -0
- package/optional-skills/mlops/training/trl-fine-tuning/SKILL.md +463 -0
- package/optional-skills/mlops/training/trl-fine-tuning/references/dpo-variants.md +227 -0
- package/optional-skills/mlops/training/trl-fine-tuning/references/grpo-training.md +504 -0
- package/optional-skills/mlops/training/trl-fine-tuning/references/online-rl.md +82 -0
- package/optional-skills/mlops/training/trl-fine-tuning/references/reward-modeling.md +122 -0
- package/optional-skills/mlops/training/trl-fine-tuning/references/sft-training.md +168 -0
- package/optional-skills/mlops/training/trl-fine-tuning/templates/basic_grpo_training.py +228 -0
- package/optional-skills/mlops/training/unsloth/SKILL.md +84 -0
- package/optional-skills/mlops/training/unsloth/references/index.md +7 -0
- package/optional-skills/mlops/training/unsloth/references/llms-full.md +16799 -0
- package/optional-skills/mlops/training/unsloth/references/llms-txt.md +12044 -0
- package/optional-skills/mlops/training/unsloth/references/llms.md +82 -0
- package/optional-skills/mlops/whisper/SKILL.md +321 -0
- package/optional-skills/mlops/whisper/references/languages.md +189 -0
- package/optional-skills/productivity/canvas/SKILL.md +98 -0
- package/optional-skills/productivity/canvas/scripts/canvas_api.py +157 -0
- package/optional-skills/productivity/here-now/SKILL.md +217 -0
- package/optional-skills/productivity/here-now/scripts/drive.sh +406 -0
- package/optional-skills/productivity/here-now/scripts/publish.sh +445 -0
- package/optional-skills/productivity/memento-flashcards/SKILL.md +324 -0
- package/optional-skills/productivity/memento-flashcards/scripts/memento_cards.py +353 -0
- package/optional-skills/productivity/memento-flashcards/scripts/youtube_quiz.py +88 -0
- package/optional-skills/productivity/shop-app/SKILL.md +340 -0
- package/optional-skills/productivity/shopify/SKILL.md +373 -0
- package/optional-skills/productivity/siyuan/SKILL.md +298 -0
- package/optional-skills/productivity/telephony/SKILL.md +418 -0
- package/optional-skills/productivity/telephony/scripts/telephony.py +1343 -0
- package/optional-skills/research/bioinformatics/SKILL.md +235 -0
- package/optional-skills/research/darwinian-evolver/SKILL.md +199 -0
- package/optional-skills/research/darwinian-evolver/scripts/parrot_openrouter.py +218 -0
- package/optional-skills/research/darwinian-evolver/scripts/show_snapshot.py +69 -0
- package/optional-skills/research/darwinian-evolver/templates/custom_problem_template.py +240 -0
- package/optional-skills/research/domain-intel/SKILL.md +97 -0
- package/optional-skills/research/domain-intel/scripts/domain_intel.py +397 -0
- package/optional-skills/research/drug-discovery/SKILL.md +227 -0
- package/optional-skills/research/drug-discovery/references/ADMET_REFERENCE.md +66 -0
- package/optional-skills/research/drug-discovery/scripts/chembl_target.py +53 -0
- package/optional-skills/research/drug-discovery/scripts/ro5_screen.py +44 -0
- package/optional-skills/research/duckduckgo-search/SKILL.md +238 -0
- package/optional-skills/research/duckduckgo-search/scripts/duckduckgo.sh +28 -0
- package/optional-skills/research/gitnexus-explorer/SKILL.md +214 -0
- package/optional-skills/research/gitnexus-explorer/scripts/proxy.mjs +92 -0
- package/optional-skills/research/osint-investigation/SKILL.md +277 -0
- package/optional-skills/research/osint-investigation/references/sources/courtlistener.md +98 -0
- package/optional-skills/research/osint-investigation/references/sources/gdelt.md +104 -0
- package/optional-skills/research/osint-investigation/references/sources/icij-offshore.md +104 -0
- package/optional-skills/research/osint-investigation/references/sources/nyc-acris.md +90 -0
- package/optional-skills/research/osint-investigation/references/sources/ofac-sdn.md +92 -0
- package/optional-skills/research/osint-investigation/references/sources/opencorporates.md +103 -0
- package/optional-skills/research/osint-investigation/references/sources/sec-edgar.md +83 -0
- package/optional-skills/research/osint-investigation/references/sources/senate-ld.md +89 -0
- package/optional-skills/research/osint-investigation/references/sources/usaspending.md +97 -0
- package/optional-skills/research/osint-investigation/references/sources/wayback.md +93 -0
- package/optional-skills/research/osint-investigation/references/sources/wikipedia.md +107 -0
- package/optional-skills/research/osint-investigation/scripts/_http.py +82 -0
- package/optional-skills/research/osint-investigation/scripts/_normalize.py +67 -0
- package/optional-skills/research/osint-investigation/scripts/build_findings.py +221 -0
- package/optional-skills/research/osint-investigation/scripts/entity_resolution.py +228 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_courtlistener.py +149 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_gdelt.py +162 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_icij_offshore.py +234 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_nyc_acris.py +203 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_ofac_sdn.py +175 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_opencorporates.py +192 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_sec_edgar.py +184 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_senate_ld.py +146 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_usaspending.py +170 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_wayback.py +142 -0
- package/optional-skills/research/osint-investigation/scripts/fetch_wikipedia.py +267 -0
- package/optional-skills/research/osint-investigation/scripts/timing_analysis.py +253 -0
- package/optional-skills/research/osint-investigation/templates/source-template.md +59 -0
- package/optional-skills/research/parallel-cli/SKILL.md +391 -0
- package/optional-skills/research/qmd/SKILL.md +441 -0
- package/optional-skills/research/scrapling/SKILL.md +336 -0
- package/optional-skills/research/searxng-search/SKILL.md +212 -0
- package/optional-skills/research/searxng-search/scripts/searxng.sh +22 -0
- package/optional-skills/security/1password/SKILL.md +163 -0
- package/optional-skills/security/1password/references/cli-examples.md +31 -0
- package/optional-skills/security/1password/references/get-started.md +21 -0
- package/optional-skills/security/DESCRIPTION.md +3 -0
- package/optional-skills/security/oss-forensics/SKILL.md +423 -0
- package/optional-skills/security/oss-forensics/references/evidence-types.md +89 -0
- package/optional-skills/security/oss-forensics/references/github-archive-guide.md +184 -0
- package/optional-skills/security/oss-forensics/references/investigation-templates.md +131 -0
- package/optional-skills/security/oss-forensics/references/recovery-techniques.md +164 -0
- package/optional-skills/security/oss-forensics/scripts/evidence-store.py +313 -0
- package/optional-skills/security/oss-forensics/templates/forensic-report.md +151 -0
- package/optional-skills/security/oss-forensics/templates/malicious-package-report.md +43 -0
- package/optional-skills/security/sherlock/SKILL.md +193 -0
- package/optional-skills/software-development/rest-graphql-debug/SKILL.md +514 -0
- package/optional-skills/web-development/DESCRIPTION.md +5 -0
- package/optional-skills/web-development/page-agent/SKILL.md +190 -0
- package/package.json +78 -0
- package/plugins/__init__.py +1 -0
- package/plugins/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/context_engine/__init__.py +219 -0
- package/plugins/disk-cleanup/README.md +51 -0
- package/plugins/disk-cleanup/__init__.py +316 -0
- package/plugins/disk-cleanup/disk_cleanup.py +497 -0
- package/plugins/disk-cleanup/plugin.yaml +7 -0
- package/plugins/example-dashboard/dashboard/manifest.json +14 -0
- package/plugins/example-dashboard/dashboard/plugin_api.py +17 -0
- package/plugins/google_meet/README.md +131 -0
- package/plugins/google_meet/SKILL.md +148 -0
- package/plugins/google_meet/__init__.py +103 -0
- package/plugins/google_meet/audio_bridge.py +244 -0
- package/plugins/google_meet/cli.py +479 -0
- package/plugins/google_meet/meet_bot.py +852 -0
- package/plugins/google_meet/node/__init__.py +54 -0
- package/plugins/google_meet/node/cli.py +125 -0
- package/plugins/google_meet/node/client.py +107 -0
- package/plugins/google_meet/node/protocol.py +124 -0
- package/plugins/google_meet/node/registry.py +113 -0
- package/plugins/google_meet/node/server.py +201 -0
- package/plugins/google_meet/plugin.yaml +16 -0
- package/plugins/google_meet/process_manager.py +324 -0
- package/plugins/google_meet/realtime/__init__.py +10 -0
- package/plugins/google_meet/realtime/openai_client.py +332 -0
- package/plugins/google_meet/tools.py +348 -0
- package/plugins/hermes-achievements/LICENSE +21 -0
- package/plugins/hermes-achievements/README.md +150 -0
- package/plugins/hermes-achievements/dashboard/dist/index.js +732 -0
- package/plugins/hermes-achievements/dashboard/dist/style.css +146 -0
- package/plugins/hermes-achievements/dashboard/manifest.json +11 -0
- package/plugins/hermes-achievements/dashboard/plugin_api.py +1062 -0
- package/plugins/hermes-achievements/docs/achievements-performance-implementation-plan.md +157 -0
- package/plugins/hermes-achievements/docs/achievements-performance-implementation-spec.md +219 -0
- package/plugins/hermes-achievements/docs/achievements-performance-spec.md +174 -0
- package/plugins/hermes-achievements/docs/assets/achievements-dashboard-hd.png +0 -0
- package/plugins/hermes-achievements/docs/assets/achievements-tier-showcase-hd.png +0 -0
- package/plugins/hermes-achievements/tests/test_achievement_engine.py +156 -0
- package/plugins/image_gen/openai/__init__.py +303 -0
- package/plugins/image_gen/openai/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/image_gen/openai/plugin.yaml +7 -0
- package/plugins/image_gen/openai-codex/__init__.py +378 -0
- package/plugins/image_gen/openai-codex/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/image_gen/openai-codex/plugin.yaml +5 -0
- package/plugins/image_gen/xai/__init__.py +316 -0
- package/plugins/image_gen/xai/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/image_gen/xai/plugin.yaml +7 -0
- package/plugins/kanban/dashboard/dist/index.js +3143 -0
- package/plugins/kanban/dashboard/dist/style.css +1500 -0
- package/plugins/kanban/dashboard/manifest.json +14 -0
- package/plugins/kanban/dashboard/plugin_api.py +1612 -0
- package/plugins/kanban/systemd/hermes-kanban-dispatcher.service +32 -0
- package/plugins/memory/__init__.py +408 -0
- package/plugins/memory/byterover/README.md +41 -0
- package/plugins/memory/byterover/__init__.py +384 -0
- package/plugins/memory/byterover/plugin.yaml +9 -0
- package/plugins/memory/hindsight/README.md +138 -0
- package/plugins/memory/hindsight/__init__.py +1758 -0
- package/plugins/memory/hindsight/plugin.yaml +8 -0
- package/plugins/memory/holographic/README.md +36 -0
- package/plugins/memory/holographic/__init__.py +409 -0
- package/plugins/memory/holographic/holographic.py +203 -0
- package/plugins/memory/holographic/plugin.yaml +5 -0
- package/plugins/memory/holographic/retrieval.py +593 -0
- package/plugins/memory/holographic/store.py +579 -0
- package/plugins/memory/honcho/README.md +328 -0
- package/plugins/memory/honcho/__init__.py +1329 -0
- package/plugins/memory/honcho/cli.py +1452 -0
- package/plugins/memory/honcho/client.py +784 -0
- package/plugins/memory/honcho/plugin.yaml +7 -0
- package/plugins/memory/honcho/session.py +1255 -0
- package/plugins/memory/mem0/README.md +38 -0
- package/plugins/memory/mem0/__init__.py +374 -0
- package/plugins/memory/mem0/plugin.yaml +5 -0
- package/plugins/memory/openviking/README.md +40 -0
- package/plugins/memory/openviking/__init__.py +945 -0
- package/plugins/memory/openviking/plugin.yaml +9 -0
- package/plugins/memory/retaindb/README.md +40 -0
- package/plugins/memory/retaindb/__init__.py +767 -0
- package/plugins/memory/retaindb/plugin.yaml +7 -0
- package/plugins/memory/supermemory/README.md +99 -0
- package/plugins/memory/supermemory/__init__.py +792 -0
- package/plugins/memory/supermemory/plugin.yaml +5 -0
- package/plugins/model-providers/README.md +70 -0
- package/plugins/model-providers/ai-gateway/__init__.py +43 -0
- package/plugins/model-providers/ai-gateway/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/ai-gateway/plugin.yaml +5 -0
- package/plugins/model-providers/alibaba/__init__.py +13 -0
- package/plugins/model-providers/alibaba/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/alibaba/plugin.yaml +5 -0
- package/plugins/model-providers/alibaba-coding-plan/__init__.py +21 -0
- package/plugins/model-providers/alibaba-coding-plan/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/alibaba-coding-plan/plugin.yaml +5 -0
- package/plugins/model-providers/anthropic/__init__.py +52 -0
- package/plugins/model-providers/anthropic/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/anthropic/plugin.yaml +5 -0
- package/plugins/model-providers/arcee/__init__.py +13 -0
- package/plugins/model-providers/arcee/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/arcee/plugin.yaml +5 -0
- package/plugins/model-providers/azure-foundry/__init__.py +21 -0
- package/plugins/model-providers/azure-foundry/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/azure-foundry/plugin.yaml +5 -0
- package/plugins/model-providers/bedrock/__init__.py +29 -0
- package/plugins/model-providers/bedrock/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/bedrock/plugin.yaml +5 -0
- package/plugins/model-providers/copilot/__init__.py +58 -0
- package/plugins/model-providers/copilot/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/copilot/plugin.yaml +5 -0
- package/plugins/model-providers/copilot-acp/__init__.py +34 -0
- package/plugins/model-providers/copilot-acp/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/copilot-acp/plugin.yaml +5 -0
- package/plugins/model-providers/custom/__init__.py +68 -0
- package/plugins/model-providers/custom/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/custom/plugin.yaml +5 -0
- package/plugins/model-providers/deepseek/__init__.py +99 -0
- package/plugins/model-providers/deepseek/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/deepseek/plugin.yaml +5 -0
- package/plugins/model-providers/gemini/__init__.py +72 -0
- package/plugins/model-providers/gemini/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/gemini/plugin.yaml +5 -0
- package/plugins/model-providers/gmi/__init__.py +31 -0
- package/plugins/model-providers/gmi/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/gmi/plugin.yaml +5 -0
- package/plugins/model-providers/huggingface/__init__.py +20 -0
- package/plugins/model-providers/huggingface/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/huggingface/plugin.yaml +5 -0
- package/plugins/model-providers/kilocode/__init__.py +14 -0
- package/plugins/model-providers/kilocode/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/kilocode/plugin.yaml +5 -0
- package/plugins/model-providers/kimi-coding/__init__.py +71 -0
- package/plugins/model-providers/kimi-coding/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/kimi-coding/plugin.yaml +5 -0
- package/plugins/model-providers/minimax/__init__.py +45 -0
- package/plugins/model-providers/minimax/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/minimax/plugin.yaml +5 -0
- package/plugins/model-providers/nous/__init__.py +54 -0
- package/plugins/model-providers/nous/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/nous/plugin.yaml +5 -0
- package/plugins/model-providers/novita/__init__.py +27 -0
- package/plugins/model-providers/novita/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/novita/plugin.yaml +5 -0
- package/plugins/model-providers/nvidia/__init__.py +21 -0
- package/plugins/model-providers/nvidia/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/nvidia/plugin.yaml +5 -0
- package/plugins/model-providers/ollama-cloud/__init__.py +14 -0
- package/plugins/model-providers/ollama-cloud/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/ollama-cloud/plugin.yaml +5 -0
- package/plugins/model-providers/openai-codex/__init__.py +15 -0
- package/plugins/model-providers/openai-codex/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/openai-codex/plugin.yaml +5 -0
- package/plugins/model-providers/opencode-zen/__init__.py +30 -0
- package/plugins/model-providers/opencode-zen/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/opencode-zen/plugin.yaml +5 -0
- package/plugins/model-providers/openrouter/__init__.py +115 -0
- package/plugins/model-providers/openrouter/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/openrouter/plugin.yaml +5 -0
- package/plugins/model-providers/qwen-oauth/__init__.py +82 -0
- package/plugins/model-providers/qwen-oauth/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/qwen-oauth/plugin.yaml +5 -0
- package/plugins/model-providers/stepfun/__init__.py +14 -0
- package/plugins/model-providers/stepfun/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/stepfun/plugin.yaml +5 -0
- package/plugins/model-providers/xai/__init__.py +15 -0
- package/plugins/model-providers/xai/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/xai/plugin.yaml +5 -0
- package/plugins/model-providers/xiaomi/__init__.py +14 -0
- package/plugins/model-providers/xiaomi/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/xiaomi/plugin.yaml +5 -0
- package/plugins/model-providers/zai/__init__.py +21 -0
- package/plugins/model-providers/zai/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/model-providers/zai/plugin.yaml +5 -0
- package/plugins/observability/langfuse/README.md +53 -0
- package/plugins/observability/langfuse/__init__.py +1004 -0
- package/plugins/observability/langfuse/plugin.yaml +14 -0
- package/plugins/platforms/google_chat/__init__.py +3 -0
- package/plugins/platforms/google_chat/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/platforms/google_chat/__pycache__/adapter.cpython-312.pyc +0 -0
- package/plugins/platforms/google_chat/adapter.py +3343 -0
- package/plugins/platforms/google_chat/oauth.py +639 -0
- package/plugins/platforms/google_chat/plugin.yaml +39 -0
- package/plugins/platforms/irc/__init__.py +3 -0
- package/plugins/platforms/irc/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/platforms/irc/__pycache__/adapter.cpython-312.pyc +0 -0
- package/plugins/platforms/irc/adapter.py +969 -0
- package/plugins/platforms/irc/plugin.yaml +54 -0
- package/plugins/platforms/line/__init__.py +3 -0
- package/plugins/platforms/line/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/platforms/line/__pycache__/adapter.cpython-312.pyc +0 -0
- package/plugins/platforms/line/adapter.py +1639 -0
- package/plugins/platforms/line/plugin.yaml +65 -0
- package/plugins/platforms/simplex/__init__.py +3 -0
- package/plugins/platforms/simplex/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/platforms/simplex/__pycache__/adapter.cpython-312.pyc +0 -0
- package/plugins/platforms/simplex/adapter.py +746 -0
- package/plugins/platforms/simplex/plugin.yaml +37 -0
- package/plugins/platforms/teams/__init__.py +3 -0
- package/plugins/platforms/teams/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/platforms/teams/__pycache__/adapter.cpython-312.pyc +0 -0
- package/plugins/platforms/teams/adapter.py +1188 -0
- package/plugins/platforms/teams/plugin.yaml +48 -0
- package/plugins/spotify/__init__.py +66 -0
- package/plugins/spotify/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/spotify/__pycache__/client.cpython-312.pyc +0 -0
- package/plugins/spotify/__pycache__/tools.cpython-312.pyc +0 -0
- package/plugins/spotify/client.py +435 -0
- package/plugins/spotify/plugin.yaml +13 -0
- package/plugins/spotify/tools.py +454 -0
- package/plugins/teams_pipeline/__init__.py +23 -0
- package/plugins/teams_pipeline/cli.py +463 -0
- package/plugins/teams_pipeline/meetings.py +333 -0
- package/plugins/teams_pipeline/models.py +350 -0
- package/plugins/teams_pipeline/pipeline.py +692 -0
- package/plugins/teams_pipeline/plugin.yaml +9 -0
- package/plugins/teams_pipeline/runtime.py +135 -0
- package/plugins/teams_pipeline/store.py +194 -0
- package/plugins/teams_pipeline/subscriptions.py +249 -0
- package/plugins/video_gen/fal/__init__.py +523 -0
- package/plugins/video_gen/fal/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/video_gen/fal/plugin.yaml +7 -0
- package/plugins/video_gen/xai/__init__.py +441 -0
- package/plugins/video_gen/xai/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/video_gen/xai/plugin.yaml +7 -0
- package/plugins/web/__init__.py +7 -0
- package/plugins/web/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/web/brave_free/__init__.py +14 -0
- package/plugins/web/brave_free/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/web/brave_free/__pycache__/provider.cpython-312.pyc +0 -0
- package/plugins/web/brave_free/plugin.yaml +7 -0
- package/plugins/web/brave_free/provider.py +137 -0
- package/plugins/web/ddgs/__init__.py +15 -0
- package/plugins/web/ddgs/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/web/ddgs/__pycache__/provider.cpython-312.pyc +0 -0
- package/plugins/web/ddgs/plugin.yaml +7 -0
- package/plugins/web/ddgs/provider.py +104 -0
- package/plugins/web/exa/__init__.py +15 -0
- package/plugins/web/exa/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/web/exa/__pycache__/provider.cpython-312.pyc +0 -0
- package/plugins/web/exa/plugin.yaml +7 -0
- package/plugins/web/exa/provider.py +212 -0
- package/plugins/web/firecrawl/__init__.py +28 -0
- package/plugins/web/firecrawl/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/web/firecrawl/__pycache__/provider.cpython-312.pyc +0 -0
- package/plugins/web/firecrawl/plugin.yaml +7 -0
- package/plugins/web/firecrawl/provider.py +773 -0
- package/plugins/web/parallel/__init__.py +16 -0
- package/plugins/web/parallel/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/web/parallel/__pycache__/provider.cpython-312.pyc +0 -0
- package/plugins/web/parallel/plugin.yaml +7 -0
- package/plugins/web/parallel/provider.py +291 -0
- package/plugins/web/searxng/__init__.py +15 -0
- package/plugins/web/searxng/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/web/searxng/__pycache__/provider.cpython-312.pyc +0 -0
- package/plugins/web/searxng/plugin.yaml +7 -0
- package/plugins/web/searxng/provider.py +140 -0
- package/plugins/web/tavily/__init__.py +15 -0
- package/plugins/web/tavily/__pycache__/__init__.cpython-312.pyc +0 -0
- package/plugins/web/tavily/__pycache__/provider.cpython-312.pyc +0 -0
- package/plugins/web/tavily/plugin.yaml +7 -0
- package/plugins/web/tavily/provider.py +285 -0
- package/providers/README.md +78 -0
- package/providers/__init__.py +192 -0
- package/providers/__pycache__/__init__.cpython-312.pyc +0 -0
- package/providers/__pycache__/base.cpython-312.pyc +0 -0
- package/providers/base.py +184 -0
- package/pyproject.toml +255 -0
- package/run_agent.py +16409 -0
- package/scripts/benchmark_browser_eval.py +138 -0
- package/scripts/build_model_catalog.py +95 -0
- package/scripts/build_skills_index.py +325 -0
- package/scripts/check-windows-footguns.py +624 -0
- package/scripts/contributor_audit.py +473 -0
- package/scripts/discord-voice-doctor.py +396 -0
- package/scripts/hermes-gateway +416 -0
- package/scripts/install.cmd +28 -0
- package/scripts/install.ps1 +1611 -0
- package/scripts/install.sh +2007 -0
- package/scripts/install_psutil_android.py +117 -0
- package/scripts/keystroke_diagnostic.py +81 -0
- package/scripts/kill_modal.sh +34 -0
- package/scripts/lib/node-bootstrap.sh +238 -0
- package/scripts/lint_diff.py +207 -0
- package/scripts/postinstall.js +150 -0
- package/scripts/profile-tui.py +626 -0
- package/scripts/release.py +1680 -0
- package/scripts/run_tests.sh +129 -0
- package/scripts/sample_and_compress.py +409 -0
- package/scripts/setup_open_webui.sh +349 -0
- package/scripts/whatsapp-bridge/allowlist.js +88 -0
- package/scripts/whatsapp-bridge/allowlist.test.mjs +80 -0
- package/scripts/whatsapp-bridge/bridge.js +729 -0
- package/scripts/whatsapp-bridge/package-lock.json +2141 -0
- package/scripts/whatsapp-bridge/package.json +19 -0
- package/skills/apple/DESCRIPTION.md +2 -0
- package/skills/apple/apple-notes/SKILL.md +90 -0
- package/skills/apple/apple-reminders/SKILL.md +98 -0
- package/skills/apple/findmy/SKILL.md +131 -0
- package/skills/apple/imessage/SKILL.md +102 -0
- package/skills/apple/macos-computer-use/SKILL.md +201 -0
- package/skills/autonomous-ai-agents/DESCRIPTION.md +3 -0
- package/skills/autonomous-ai-agents/claude-code/SKILL.md +745 -0
- package/skills/autonomous-ai-agents/codex/SKILL.md +130 -0
- package/skills/autonomous-ai-agents/hermes-agent/SKILL.md +1014 -0
- package/skills/autonomous-ai-agents/opencode/SKILL.md +219 -0
- package/skills/creative/DESCRIPTION.md +3 -0
- package/skills/creative/architecture-diagram/SKILL.md +148 -0
- package/skills/creative/architecture-diagram/templates/template.html +319 -0
- package/skills/creative/ascii-art/SKILL.md +322 -0
- package/skills/creative/ascii-video/README.md +290 -0
- package/skills/creative/ascii-video/SKILL.md +241 -0
- package/skills/creative/ascii-video/references/architecture.md +802 -0
- package/skills/creative/ascii-video/references/composition.md +892 -0
- package/skills/creative/ascii-video/references/effects.md +1865 -0
- package/skills/creative/ascii-video/references/inputs.md +685 -0
- package/skills/creative/ascii-video/references/optimization.md +688 -0
- package/skills/creative/ascii-video/references/scenes.md +1011 -0
- package/skills/creative/ascii-video/references/shaders.md +1385 -0
- package/skills/creative/ascii-video/references/troubleshooting.md +367 -0
- package/skills/creative/baoyu-comic/PORT_NOTES.md +77 -0
- package/skills/creative/baoyu-comic/SKILL.md +247 -0
- package/skills/creative/baoyu-comic/references/analysis-framework.md +176 -0
- package/skills/creative/baoyu-comic/references/art-styles/chalk.md +101 -0
- package/skills/creative/baoyu-comic/references/art-styles/ink-brush.md +97 -0
- package/skills/creative/baoyu-comic/references/art-styles/ligne-claire.md +75 -0
- package/skills/creative/baoyu-comic/references/art-styles/manga.md +93 -0
- package/skills/creative/baoyu-comic/references/art-styles/minimalist.md +84 -0
- package/skills/creative/baoyu-comic/references/art-styles/realistic.md +89 -0
- package/skills/creative/baoyu-comic/references/auto-selection.md +71 -0
- package/skills/creative/baoyu-comic/references/base-prompt.md +98 -0
- package/skills/creative/baoyu-comic/references/character-template.md +180 -0
- package/skills/creative/baoyu-comic/references/layouts/cinematic.md +23 -0
- package/skills/creative/baoyu-comic/references/layouts/dense.md +23 -0
- package/skills/creative/baoyu-comic/references/layouts/four-panel.md +40 -0
- package/skills/creative/baoyu-comic/references/layouts/mixed.md +23 -0
- package/skills/creative/baoyu-comic/references/layouts/splash.md +23 -0
- package/skills/creative/baoyu-comic/references/layouts/standard.md +23 -0
- package/skills/creative/baoyu-comic/references/layouts/webtoon.md +30 -0
- package/skills/creative/baoyu-comic/references/ohmsha-guide.md +85 -0
- package/skills/creative/baoyu-comic/references/partial-workflows.md +106 -0
- package/skills/creative/baoyu-comic/references/presets/concept-story.md +121 -0
- package/skills/creative/baoyu-comic/references/presets/four-panel.md +107 -0
- package/skills/creative/baoyu-comic/references/presets/ohmsha.md +114 -0
- package/skills/creative/baoyu-comic/references/presets/shoujo.md +116 -0
- package/skills/creative/baoyu-comic/references/presets/wuxia.md +110 -0
- package/skills/creative/baoyu-comic/references/storyboard-template.md +143 -0
- package/skills/creative/baoyu-comic/references/tones/action.md +110 -0
- package/skills/creative/baoyu-comic/references/tones/dramatic.md +95 -0
- package/skills/creative/baoyu-comic/references/tones/energetic.md +105 -0
- package/skills/creative/baoyu-comic/references/tones/neutral.md +63 -0
- package/skills/creative/baoyu-comic/references/tones/romantic.md +100 -0
- package/skills/creative/baoyu-comic/references/tones/vintage.md +104 -0
- package/skills/creative/baoyu-comic/references/tones/warm.md +94 -0
- package/skills/creative/baoyu-comic/references/workflow.md +401 -0
- package/skills/creative/baoyu-infographic/PORT_NOTES.md +43 -0
- package/skills/creative/baoyu-infographic/SKILL.md +237 -0
- package/skills/creative/baoyu-infographic/references/analysis-framework.md +182 -0
- package/skills/creative/baoyu-infographic/references/base-prompt.md +43 -0
- package/skills/creative/baoyu-infographic/references/layouts/bento-grid.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/binary-comparison.md +48 -0
- package/skills/creative/baoyu-infographic/references/layouts/bridge.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/circular-flow.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/comic-strip.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/comparison-matrix.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/dashboard.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/dense-modules.md +72 -0
- package/skills/creative/baoyu-infographic/references/layouts/funnel.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/hierarchical-layers.md +48 -0
- package/skills/creative/baoyu-infographic/references/layouts/hub-spoke.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/iceberg.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/isometric-map.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/jigsaw.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/linear-progression.md +48 -0
- package/skills/creative/baoyu-infographic/references/layouts/periodic-table.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/story-mountain.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/structural-breakdown.md +48 -0
- package/skills/creative/baoyu-infographic/references/layouts/tree-branching.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/venn-diagram.md +41 -0
- package/skills/creative/baoyu-infographic/references/layouts/winding-roadmap.md +41 -0
- package/skills/creative/baoyu-infographic/references/structured-content-template.md +244 -0
- package/skills/creative/baoyu-infographic/references/styles/aged-academia.md +36 -0
- package/skills/creative/baoyu-infographic/references/styles/bold-graphic.md +36 -0
- package/skills/creative/baoyu-infographic/references/styles/chalkboard.md +61 -0
- package/skills/creative/baoyu-infographic/references/styles/claymation.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/corporate-memphis.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/craft-handmade.md +44 -0
- package/skills/creative/baoyu-infographic/references/styles/cyberpunk-neon.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/hand-drawn-edu.md +63 -0
- package/skills/creative/baoyu-infographic/references/styles/ikea-manual.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/kawaii.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/knolling.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/lego-brick.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/morandi-journal.md +60 -0
- package/skills/creative/baoyu-infographic/references/styles/origami.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/pixel-art.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/pop-laboratory.md +48 -0
- package/skills/creative/baoyu-infographic/references/styles/retro-pop-grid.md +47 -0
- package/skills/creative/baoyu-infographic/references/styles/storybook-watercolor.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/subway-map.md +29 -0
- package/skills/creative/baoyu-infographic/references/styles/technical-schematic.md +36 -0
- package/skills/creative/baoyu-infographic/references/styles/ui-wireframe.md +29 -0
- package/skills/creative/claude-design/SKILL.md +591 -0
- package/skills/creative/comfyui/SKILL.md +612 -0
- package/skills/creative/comfyui/references/official-cli.md +255 -0
- package/skills/creative/comfyui/references/rest-api.md +312 -0
- package/skills/creative/comfyui/references/template-integrity.md +243 -0
- package/skills/creative/comfyui/references/workflow-format.md +226 -0
- package/skills/creative/comfyui/scripts/_common.py +835 -0
- package/skills/creative/comfyui/scripts/auto_fix_deps.py +225 -0
- package/skills/creative/comfyui/scripts/check_deps.py +437 -0
- package/skills/creative/comfyui/scripts/comfyui_setup.sh +286 -0
- package/skills/creative/comfyui/scripts/extract_schema.py +315 -0
- package/skills/creative/comfyui/scripts/fetch_logs.py +158 -0
- package/skills/creative/comfyui/scripts/hardware_check.py +497 -0
- package/skills/creative/comfyui/scripts/health_check.py +223 -0
- package/skills/creative/comfyui/scripts/run_batch.py +243 -0
- package/skills/creative/comfyui/scripts/run_workflow.py +796 -0
- package/skills/creative/comfyui/scripts/ws_monitor.py +267 -0
- package/skills/creative/comfyui/tests/README.md +50 -0
- package/skills/creative/comfyui/tests/conftest.py +64 -0
- package/skills/creative/comfyui/tests/pytest.ini +5 -0
- package/skills/creative/comfyui/tests/test_check_deps.py +68 -0
- package/skills/creative/comfyui/tests/test_cloud_integration.py +95 -0
- package/skills/creative/comfyui/tests/test_common.py +447 -0
- package/skills/creative/comfyui/tests/test_extract_schema.py +185 -0
- package/skills/creative/comfyui/tests/test_run_workflow.py +213 -0
- package/skills/creative/comfyui/workflows/README.md +86 -0
- package/skills/creative/comfyui/workflows/animatediff_video.json +64 -0
- package/skills/creative/comfyui/workflows/flux_dev_txt2img.json +78 -0
- package/skills/creative/comfyui/workflows/sd15_txt2img.json +49 -0
- package/skills/creative/comfyui/workflows/sdxl_img2img.json +54 -0
- package/skills/creative/comfyui/workflows/sdxl_inpaint.json +59 -0
- package/skills/creative/comfyui/workflows/sdxl_txt2img.json +49 -0
- package/skills/creative/comfyui/workflows/upscale_4x.json +27 -0
- package/skills/creative/comfyui/workflows/wan_video_t2v.json +69 -0
- package/skills/creative/creative-ideation/SKILL.md +152 -0
- package/skills/creative/creative-ideation/references/full-prompt-library.md +110 -0
- package/skills/creative/design-md/SKILL.md +199 -0
- package/skills/creative/design-md/templates/starter.md +99 -0
- package/skills/creative/excalidraw/SKILL.md +199 -0
- package/skills/creative/excalidraw/references/colors.md +44 -0
- package/skills/creative/excalidraw/references/dark-mode.md +68 -0
- package/skills/creative/excalidraw/references/examples.md +141 -0
- package/skills/creative/excalidraw/scripts/upload.py +133 -0
- package/skills/creative/humanizer/LICENSE +21 -0
- package/skills/creative/humanizer/SKILL.md +578 -0
- package/skills/creative/manim-video/README.md +23 -0
- package/skills/creative/manim-video/SKILL.md +269 -0
- package/skills/creative/manim-video/references/animation-design-thinking.md +161 -0
- package/skills/creative/manim-video/references/animations.md +282 -0
- package/skills/creative/manim-video/references/camera-and-3d.md +135 -0
- package/skills/creative/manim-video/references/decorations.md +202 -0
- package/skills/creative/manim-video/references/equations.md +216 -0
- package/skills/creative/manim-video/references/graphs-and-data.md +163 -0
- package/skills/creative/manim-video/references/mobjects.md +333 -0
- package/skills/creative/manim-video/references/paper-explainer.md +255 -0
- package/skills/creative/manim-video/references/production-quality.md +190 -0
- package/skills/creative/manim-video/references/rendering.md +185 -0
- package/skills/creative/manim-video/references/scene-planning.md +118 -0
- package/skills/creative/manim-video/references/troubleshooting.md +135 -0
- package/skills/creative/manim-video/references/updaters-and-trackers.md +260 -0
- package/skills/creative/manim-video/references/visual-design.md +124 -0
- package/skills/creative/manim-video/scripts/setup.sh +14 -0
- package/skills/creative/p5js/README.md +64 -0
- package/skills/creative/p5js/SKILL.md +556 -0
- package/skills/creative/p5js/references/animation.md +439 -0
- package/skills/creative/p5js/references/color-systems.md +352 -0
- package/skills/creative/p5js/references/core-api.md +410 -0
- package/skills/creative/p5js/references/export-pipeline.md +566 -0
- package/skills/creative/p5js/references/interaction.md +398 -0
- package/skills/creative/p5js/references/shapes-and-geometry.md +300 -0
- package/skills/creative/p5js/references/troubleshooting.md +532 -0
- package/skills/creative/p5js/references/typography.md +302 -0
- package/skills/creative/p5js/references/visual-effects.md +895 -0
- package/skills/creative/p5js/references/webgl-and-3d.md +423 -0
- package/skills/creative/p5js/scripts/export-frames.js +179 -0
- package/skills/creative/p5js/scripts/render.sh +108 -0
- package/skills/creative/p5js/scripts/serve.sh +28 -0
- package/skills/creative/p5js/scripts/setup.sh +87 -0
- package/skills/creative/p5js/templates/viewer.html +395 -0
- package/skills/creative/pixel-art/ATTRIBUTION.md +54 -0
- package/skills/creative/pixel-art/SKILL.md +218 -0
- package/skills/creative/pixel-art/references/palettes.md +49 -0
- package/skills/creative/pixel-art/scripts/__init__.py +0 -0
- package/skills/creative/pixel-art/scripts/palettes.py +167 -0
- package/skills/creative/pixel-art/scripts/pixel_art.py +162 -0
- package/skills/creative/pixel-art/scripts/pixel_art_video.py +345 -0
- package/skills/creative/popular-web-designs/SKILL.md +214 -0
- package/skills/creative/popular-web-designs/templates/airbnb.md +259 -0
- package/skills/creative/popular-web-designs/templates/airtable.md +102 -0
- package/skills/creative/popular-web-designs/templates/apple.md +326 -0
- package/skills/creative/popular-web-designs/templates/bmw.md +193 -0
- package/skills/creative/popular-web-designs/templates/cal.md +272 -0
- package/skills/creative/popular-web-designs/templates/claude.md +325 -0
- package/skills/creative/popular-web-designs/templates/clay.md +317 -0
- package/skills/creative/popular-web-designs/templates/clickhouse.md +294 -0
- package/skills/creative/popular-web-designs/templates/cohere.md +279 -0
- package/skills/creative/popular-web-designs/templates/coinbase.md +142 -0
- package/skills/creative/popular-web-designs/templates/composio.md +320 -0
- package/skills/creative/popular-web-designs/templates/cursor.md +322 -0
- package/skills/creative/popular-web-designs/templates/elevenlabs.md +278 -0
- package/skills/creative/popular-web-designs/templates/expo.md +294 -0
- package/skills/creative/popular-web-designs/templates/figma.md +233 -0
- package/skills/creative/popular-web-designs/templates/framer.md +259 -0
- package/skills/creative/popular-web-designs/templates/hashicorp.md +291 -0
- package/skills/creative/popular-web-designs/templates/ibm.md +345 -0
- package/skills/creative/popular-web-designs/templates/intercom.md +159 -0
- package/skills/creative/popular-web-designs/templates/kraken.md +138 -0
- package/skills/creative/popular-web-designs/templates/linear.app.md +380 -0
- package/skills/creative/popular-web-designs/templates/lovable.md +311 -0
- package/skills/creative/popular-web-designs/templates/minimax.md +270 -0
- package/skills/creative/popular-web-designs/templates/mintlify.md +339 -0
- package/skills/creative/popular-web-designs/templates/miro.md +121 -0
- package/skills/creative/popular-web-designs/templates/mistral.ai.md +274 -0
- package/skills/creative/popular-web-designs/templates/mongodb.md +279 -0
- package/skills/creative/popular-web-designs/templates/notion.md +322 -0
- package/skills/creative/popular-web-designs/templates/nvidia.md +306 -0
- package/skills/creative/popular-web-designs/templates/ollama.md +280 -0
- package/skills/creative/popular-web-designs/templates/opencode.ai.md +294 -0
- package/skills/creative/popular-web-designs/templates/pinterest.md +243 -0
- package/skills/creative/popular-web-designs/templates/posthog.md +269 -0
- package/skills/creative/popular-web-designs/templates/raycast.md +281 -0
- package/skills/creative/popular-web-designs/templates/replicate.md +274 -0
- package/skills/creative/popular-web-designs/templates/resend.md +316 -0
- package/skills/creative/popular-web-designs/templates/revolut.md +198 -0
- package/skills/creative/popular-web-designs/templates/runwayml.md +257 -0
- package/skills/creative/popular-web-designs/templates/sanity.md +370 -0
- package/skills/creative/popular-web-designs/templates/sentry.md +275 -0
- package/skills/creative/popular-web-designs/templates/spacex.md +207 -0
- package/skills/creative/popular-web-designs/templates/spotify.md +259 -0
- package/skills/creative/popular-web-designs/templates/stripe.md +335 -0
- package/skills/creative/popular-web-designs/templates/supabase.md +268 -0
- package/skills/creative/popular-web-designs/templates/superhuman.md +265 -0
- package/skills/creative/popular-web-designs/templates/together.ai.md +276 -0
- package/skills/creative/popular-web-designs/templates/uber.md +308 -0
- package/skills/creative/popular-web-designs/templates/vercel.md +323 -0
- package/skills/creative/popular-web-designs/templates/voltagent.md +336 -0
- package/skills/creative/popular-web-designs/templates/warp.md +266 -0
- package/skills/creative/popular-web-designs/templates/webflow.md +105 -0
- package/skills/creative/popular-web-designs/templates/wise.md +186 -0
- package/skills/creative/popular-web-designs/templates/x.ai.md +270 -0
- package/skills/creative/popular-web-designs/templates/zapier.md +341 -0
- package/skills/creative/pretext/SKILL.md +220 -0
- package/skills/creative/pretext/references/patterns.md +258 -0
- package/skills/creative/pretext/templates/donut-orbit.html +1468 -0
- package/skills/creative/pretext/templates/hello-orb-flow.html +95 -0
- package/skills/creative/sketch/SKILL.md +218 -0
- package/skills/creative/songwriting-and-ai-music/SKILL.md +287 -0
- package/skills/creative/touchdesigner-mcp/SKILL.md +356 -0
- package/skills/creative/touchdesigner-mcp/references/3d-scene.md +275 -0
- package/skills/creative/touchdesigner-mcp/references/animation.md +221 -0
- package/skills/creative/touchdesigner-mcp/references/audio-reactive.md +175 -0
- package/skills/creative/touchdesigner-mcp/references/dat-scripting.md +352 -0
- package/skills/creative/touchdesigner-mcp/references/external-data.md +322 -0
- package/skills/creative/touchdesigner-mcp/references/geometry-comp.md +121 -0
- package/skills/creative/touchdesigner-mcp/references/glsl.md +151 -0
- package/skills/creative/touchdesigner-mcp/references/layout-compositor.md +131 -0
- package/skills/creative/touchdesigner-mcp/references/mcp-tools.md +382 -0
- package/skills/creative/touchdesigner-mcp/references/midi-osc.md +211 -0
- package/skills/creative/touchdesigner-mcp/references/network-patterns.md +966 -0
- package/skills/creative/touchdesigner-mcp/references/operator-tips.md +106 -0
- package/skills/creative/touchdesigner-mcp/references/operators.md +239 -0
- package/skills/creative/touchdesigner-mcp/references/panel-ui.md +281 -0
- package/skills/creative/touchdesigner-mcp/references/particles.md +245 -0
- package/skills/creative/touchdesigner-mcp/references/pitfalls.md +704 -0
- package/skills/creative/touchdesigner-mcp/references/postfx.md +183 -0
- package/skills/creative/touchdesigner-mcp/references/projection-mapping.md +211 -0
- package/skills/creative/touchdesigner-mcp/references/python-api.md +463 -0
- package/skills/creative/touchdesigner-mcp/references/replicator.md +198 -0
- package/skills/creative/touchdesigner-mcp/references/troubleshooting.md +244 -0
- package/skills/creative/touchdesigner-mcp/scripts/setup.sh +115 -0
- package/skills/data-science/DESCRIPTION.md +3 -0
- package/skills/data-science/jupyter-live-kernel/SKILL.md +167 -0
- package/skills/devops/kanban-orchestrator/SKILL.md +189 -0
- package/skills/devops/kanban-worker/SKILL.md +184 -0
- package/skills/devops/webhook-subscriptions/SKILL.md +204 -0
- package/skills/diagramming/DESCRIPTION.md +3 -0
- package/skills/dogfood/SKILL.md +162 -0
- package/skills/dogfood/references/issue-taxonomy.md +109 -0
- package/skills/dogfood/templates/dogfood-report-template.md +86 -0
- package/skills/domain/DESCRIPTION.md +24 -0
- package/skills/email/DESCRIPTION.md +3 -0
- package/skills/email/himalaya/SKILL.md +299 -0
- package/skills/email/himalaya/references/configuration.md +227 -0
- package/skills/email/himalaya/references/message-composition.md +199 -0
- package/skills/gaming/DESCRIPTION.md +3 -0
- package/skills/gaming/minecraft-modpack-server/SKILL.md +187 -0
- package/skills/gaming/pokemon-player/SKILL.md +216 -0
- package/skills/gifs/DESCRIPTION.md +3 -0
- package/skills/github/DESCRIPTION.md +3 -0
- package/skills/github/codebase-inspection/SKILL.md +116 -0
- package/skills/github/github-auth/SKILL.md +247 -0
- package/skills/github/github-auth/scripts/gh-env.sh +66 -0
- package/skills/github/github-code-review/SKILL.md +481 -0
- package/skills/github/github-code-review/references/review-output-template.md +74 -0
- package/skills/github/github-issues/SKILL.md +370 -0
- package/skills/github/github-issues/templates/bug-report.md +35 -0
- package/skills/github/github-issues/templates/feature-request.md +31 -0
- package/skills/github/github-pr-workflow/SKILL.md +367 -0
- package/skills/github/github-pr-workflow/references/ci-troubleshooting.md +183 -0
- package/skills/github/github-pr-workflow/references/conventional-commits.md +71 -0
- package/skills/github/github-pr-workflow/templates/pr-body-bugfix.md +35 -0
- package/skills/github/github-pr-workflow/templates/pr-body-feature.md +33 -0
- package/skills/github/github-repo-management/SKILL.md +516 -0
- package/skills/github/github-repo-management/references/github-api-cheatsheet.md +161 -0
- package/skills/index-cache/anthropics_skills_skills_.json +1 -0
- package/skills/index-cache/claude_marketplace_anthropics_skills.json +1 -0
- package/skills/index-cache/lobehub_index.json +1 -0
- package/skills/index-cache/openai_skills_skills_.json +1 -0
- package/skills/inference-sh/DESCRIPTION.md +19 -0
- package/skills/mcp/DESCRIPTION.md +3 -0
- package/skills/mcp/native-mcp/SKILL.md +357 -0
- package/skills/media/DESCRIPTION.md +3 -0
- package/skills/media/gif-search/SKILL.md +91 -0
- package/skills/media/heartmula/SKILL.md +171 -0
- package/skills/media/songsee/SKILL.md +83 -0
- package/skills/media/spotify/SKILL.md +135 -0
- package/skills/media/youtube-content/SKILL.md +73 -0
- package/skills/media/youtube-content/references/output-formats.md +56 -0
- package/skills/media/youtube-content/scripts/fetch_transcript.py +124 -0
- package/skills/mlops/DESCRIPTION.md +3 -0
- package/skills/mlops/evaluation/DESCRIPTION.md +3 -0
- package/skills/mlops/evaluation/lm-evaluation-harness/SKILL.md +498 -0
- package/skills/mlops/evaluation/lm-evaluation-harness/references/api-evaluation.md +490 -0
- package/skills/mlops/evaluation/lm-evaluation-harness/references/benchmark-guide.md +488 -0
- package/skills/mlops/evaluation/lm-evaluation-harness/references/custom-tasks.md +602 -0
- package/skills/mlops/evaluation/lm-evaluation-harness/references/distributed-eval.md +519 -0
- package/skills/mlops/evaluation/weights-and-biases/SKILL.md +594 -0
- package/skills/mlops/evaluation/weights-and-biases/references/artifacts.md +584 -0
- package/skills/mlops/evaluation/weights-and-biases/references/integrations.md +700 -0
- package/skills/mlops/evaluation/weights-and-biases/references/sweeps.md +847 -0
- package/skills/mlops/huggingface-hub/SKILL.md +81 -0
- package/skills/mlops/inference/DESCRIPTION.md +3 -0
- package/skills/mlops/inference/llama-cpp/SKILL.md +249 -0
- package/skills/mlops/inference/llama-cpp/references/advanced-usage.md +504 -0
- package/skills/mlops/inference/llama-cpp/references/hub-discovery.md +168 -0
- package/skills/mlops/inference/llama-cpp/references/optimization.md +89 -0
- package/skills/mlops/inference/llama-cpp/references/quantization.md +243 -0
- package/skills/mlops/inference/llama-cpp/references/server.md +150 -0
- package/skills/mlops/inference/llama-cpp/references/troubleshooting.md +442 -0
- package/skills/mlops/inference/obliteratus/SKILL.md +342 -0
- package/skills/mlops/inference/obliteratus/references/analysis-modules.md +166 -0
- package/skills/mlops/inference/obliteratus/references/methods-guide.md +141 -0
- package/skills/mlops/inference/obliteratus/templates/abliteration-config.yaml +33 -0
- package/skills/mlops/inference/obliteratus/templates/analysis-study.yaml +40 -0
- package/skills/mlops/inference/obliteratus/templates/batch-abliteration.yaml +41 -0
- package/skills/mlops/inference/vllm/SKILL.md +372 -0
- package/skills/mlops/inference/vllm/references/optimization.md +226 -0
- package/skills/mlops/inference/vllm/references/quantization.md +284 -0
- package/skills/mlops/inference/vllm/references/server-deployment.md +255 -0
- package/skills/mlops/inference/vllm/references/troubleshooting.md +447 -0
- package/skills/mlops/models/DESCRIPTION.md +3 -0
- package/skills/mlops/models/audiocraft/SKILL.md +568 -0
- package/skills/mlops/models/audiocraft/references/advanced-usage.md +666 -0
- package/skills/mlops/models/audiocraft/references/troubleshooting.md +504 -0
- package/skills/mlops/models/segment-anything/SKILL.md +506 -0
- package/skills/mlops/models/segment-anything/references/advanced-usage.md +589 -0
- package/skills/mlops/models/segment-anything/references/troubleshooting.md +484 -0
- package/skills/mlops/research/DESCRIPTION.md +3 -0
- package/skills/mlops/research/dspy/SKILL.md +594 -0
- package/skills/mlops/research/dspy/references/examples.md +663 -0
- package/skills/mlops/research/dspy/references/modules.md +475 -0
- package/skills/mlops/research/dspy/references/optimizers.md +566 -0
- package/skills/mlops/training/DESCRIPTION.md +3 -0
- package/skills/mlops/vector-databases/DESCRIPTION.md +3 -0
- package/skills/note-taking/DESCRIPTION.md +3 -0
- package/skills/note-taking/obsidian/SKILL.md +61 -0
- package/skills/productivity/DESCRIPTION.md +3 -0
- package/skills/productivity/airtable/SKILL.md +229 -0
- package/skills/productivity/google-workspace/SKILL.md +335 -0
- package/skills/productivity/google-workspace/references/gmail-search-syntax.md +63 -0
- package/skills/productivity/google-workspace/scripts/_hermes_home.py +43 -0
- package/skills/productivity/google-workspace/scripts/google_api.py +1221 -0
- package/skills/productivity/google-workspace/scripts/gws_bridge.py +108 -0
- package/skills/productivity/google-workspace/scripts/setup.py +454 -0
- package/skills/productivity/linear/SKILL.md +380 -0
- package/skills/productivity/linear/scripts/linear_api.py +445 -0
- package/skills/productivity/maps/SKILL.md +195 -0
- package/skills/productivity/maps/scripts/maps_client.py +1298 -0
- package/skills/productivity/nano-pdf/SKILL.md +52 -0
- package/skills/productivity/notion/SKILL.md +448 -0
- package/skills/productivity/notion/references/block-types.md +112 -0
- package/skills/productivity/ocr-and-documents/DESCRIPTION.md +3 -0
- package/skills/productivity/ocr-and-documents/SKILL.md +172 -0
- package/skills/productivity/ocr-and-documents/scripts/extract_marker.py +87 -0
- package/skills/productivity/ocr-and-documents/scripts/extract_pymupdf.py +98 -0
- package/skills/productivity/powerpoint/LICENSE.txt +30 -0
- package/skills/productivity/powerpoint/SKILL.md +237 -0
- package/skills/productivity/powerpoint/editing.md +205 -0
- package/skills/productivity/powerpoint/pptxgenjs.md +420 -0
- package/skills/productivity/powerpoint/scripts/__init__.py +0 -0
- package/skills/productivity/powerpoint/scripts/add_slide.py +195 -0
- package/skills/productivity/powerpoint/scripts/clean.py +286 -0
- package/skills/productivity/powerpoint/scripts/office/helpers/__init__.py +0 -0
- package/skills/productivity/powerpoint/scripts/office/helpers/merge_runs.py +199 -0
- package/skills/productivity/powerpoint/scripts/office/helpers/simplify_redlines.py +197 -0
- package/skills/productivity/powerpoint/scripts/office/pack.py +159 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ecma/fourth-edition/opc-contentTypes.xsd +42 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ecma/fourth-edition/opc-coreProperties.xsd +50 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ecma/fourth-edition/opc-digSig.xsd +49 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/ecma/fourth-edition/opc-relationships.xsd +33 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/mce/mc.xsd +75 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/productivity/powerpoint/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/productivity/teams-meeting-pipeline/SKILL.md +116 -0
- package/skills/red-teaming/godmode/SKILL.md +404 -0
- package/skills/red-teaming/godmode/references/jailbreak-templates.md +128 -0
- package/skills/red-teaming/godmode/references/refusal-detection.md +142 -0
- package/skills/red-teaming/godmode/scripts/auto_jailbreak.py +769 -0
- package/skills/red-teaming/godmode/scripts/godmode_race.py +530 -0
- package/skills/red-teaming/godmode/scripts/load_godmode.py +45 -0
- package/skills/red-teaming/godmode/scripts/parseltongue.py +550 -0
- package/skills/red-teaming/godmode/templates/prefill-subtle.json +10 -0
- package/skills/red-teaming/godmode/templates/prefill.json +18 -0
- package/skills/research/DESCRIPTION.md +3 -0
- package/skills/research/arxiv/SKILL.md +282 -0
- package/skills/research/arxiv/scripts/search_arxiv.py +114 -0
- package/skills/research/blogwatcher/SKILL.md +137 -0
- package/skills/research/llm-wiki/SKILL.md +507 -0
- package/skills/research/polymarket/SKILL.md +77 -0
- package/skills/research/polymarket/references/api-endpoints.md +220 -0
- package/skills/research/polymarket/scripts/polymarket.py +284 -0
- package/skills/research/research-paper-writing/SKILL.md +2377 -0
- package/skills/research/research-paper-writing/references/autoreason-methodology.md +394 -0
- package/skills/research/research-paper-writing/references/checklists.md +434 -0
- package/skills/research/research-paper-writing/references/citation-workflow.md +564 -0
- package/skills/research/research-paper-writing/references/experiment-patterns.md +728 -0
- package/skills/research/research-paper-writing/references/human-evaluation.md +476 -0
- package/skills/research/research-paper-writing/references/paper-types.md +481 -0
- package/skills/research/research-paper-writing/references/reviewer-guidelines.md +433 -0
- package/skills/research/research-paper-writing/references/sources.md +191 -0
- package/skills/research/research-paper-writing/references/writing-guide.md +474 -0
- package/skills/research/research-paper-writing/templates/README.md +251 -0
- package/skills/research/research-paper-writing/templates/aaai2026/README.md +534 -0
- package/skills/research/research-paper-writing/templates/aaai2026/aaai2026-unified-supp.tex +144 -0
- package/skills/research/research-paper-writing/templates/aaai2026/aaai2026-unified-template.tex +952 -0
- package/skills/research/research-paper-writing/templates/aaai2026/aaai2026.bib +111 -0
- package/skills/research/research-paper-writing/templates/aaai2026/aaai2026.bst +1493 -0
- package/skills/research/research-paper-writing/templates/aaai2026/aaai2026.sty +315 -0
- package/skills/research/research-paper-writing/templates/acl/README.md +50 -0
- package/skills/research/research-paper-writing/templates/acl/acl.sty +312 -0
- package/skills/research/research-paper-writing/templates/acl/acl_latex.tex +377 -0
- package/skills/research/research-paper-writing/templates/acl/acl_lualatex.tex +101 -0
- package/skills/research/research-paper-writing/templates/acl/acl_natbib.bst +1940 -0
- package/skills/research/research-paper-writing/templates/acl/anthology.bib.txt +26 -0
- package/skills/research/research-paper-writing/templates/acl/custom.bib +70 -0
- package/skills/research/research-paper-writing/templates/acl/formatting.md +326 -0
- package/skills/research/research-paper-writing/templates/colm2025/README.md +3 -0
- package/skills/research/research-paper-writing/templates/colm2025/colm2025_conference.bib +11 -0
- package/skills/research/research-paper-writing/templates/colm2025/colm2025_conference.bst +1440 -0
- package/skills/research/research-paper-writing/templates/colm2025/colm2025_conference.pdf +0 -0
- package/skills/research/research-paper-writing/templates/colm2025/colm2025_conference.sty +218 -0
- package/skills/research/research-paper-writing/templates/colm2025/colm2025_conference.tex +305 -0
- package/skills/research/research-paper-writing/templates/colm2025/fancyhdr.sty +485 -0
- package/skills/research/research-paper-writing/templates/colm2025/math_commands.tex +508 -0
- package/skills/research/research-paper-writing/templates/colm2025/natbib.sty +1246 -0
- package/skills/research/research-paper-writing/templates/iclr2026/fancyhdr.sty +485 -0
- package/skills/research/research-paper-writing/templates/iclr2026/iclr2026_conference.bib +24 -0
- package/skills/research/research-paper-writing/templates/iclr2026/iclr2026_conference.bst +1440 -0
- package/skills/research/research-paper-writing/templates/iclr2026/iclr2026_conference.pdf +0 -0
- package/skills/research/research-paper-writing/templates/iclr2026/iclr2026_conference.sty +246 -0
- package/skills/research/research-paper-writing/templates/iclr2026/iclr2026_conference.tex +414 -0
- package/skills/research/research-paper-writing/templates/iclr2026/math_commands.tex +508 -0
- package/skills/research/research-paper-writing/templates/iclr2026/natbib.sty +1246 -0
- package/skills/research/research-paper-writing/templates/icml2026/algorithm.sty +79 -0
- package/skills/research/research-paper-writing/templates/icml2026/algorithmic.sty +201 -0
- package/skills/research/research-paper-writing/templates/icml2026/example_paper.bib +75 -0
- package/skills/research/research-paper-writing/templates/icml2026/example_paper.pdf +0 -0
- package/skills/research/research-paper-writing/templates/icml2026/example_paper.tex +662 -0
- package/skills/research/research-paper-writing/templates/icml2026/fancyhdr.sty +864 -0
- package/skills/research/research-paper-writing/templates/icml2026/icml2026.bst +1443 -0
- package/skills/research/research-paper-writing/templates/icml2026/icml2026.sty +767 -0
- package/skills/research/research-paper-writing/templates/icml2026/icml_numpapers.pdf +0 -0
- package/skills/research/research-paper-writing/templates/neurips2025/Makefile +36 -0
- package/skills/research/research-paper-writing/templates/neurips2025/extra_pkgs.tex +53 -0
- package/skills/research/research-paper-writing/templates/neurips2025/main.tex +38 -0
- package/skills/research/research-paper-writing/templates/neurips2025/neurips.sty +382 -0
- package/skills/smart-home/DESCRIPTION.md +3 -0
- package/skills/smart-home/openhue/SKILL.md +109 -0
- package/skills/social-media/DESCRIPTION.md +3 -0
- package/skills/social-media/xurl/SKILL.md +414 -0
- package/skills/software-development/debugging-hermes-tui-commands/SKILL.md +152 -0
- package/skills/software-development/hermes-agent-skill-authoring/SKILL.md +165 -0
- package/skills/software-development/node-inspect-debugger/SKILL.md +319 -0
- package/skills/software-development/plan/SKILL.md +58 -0
- package/skills/software-development/python-debugpy/SKILL.md +375 -0
- package/skills/software-development/requesting-code-review/SKILL.md +280 -0
- package/skills/software-development/spike/SKILL.md +197 -0
- package/skills/software-development/subagent-driven-development/SKILL.md +352 -0
- package/skills/software-development/subagent-driven-development/references/context-budget-discipline.md +53 -0
- package/skills/software-development/subagent-driven-development/references/gates-taxonomy.md +93 -0
- package/skills/software-development/systematic-debugging/SKILL.md +367 -0
- package/skills/software-development/test-driven-development/SKILL.md +343 -0
- package/skills/software-development/writing-plans/SKILL.md +297 -0
- package/skills/yuanbao/SKILL.md +108 -0
- package/tools/__init__.py +25 -0
- package/tools/__pycache__/__init__.cpython-312.pyc +0 -0
- package/tools/__pycache__/approval.cpython-312.pyc +0 -0
- package/tools/__pycache__/binary_extensions.cpython-312.pyc +0 -0
- package/tools/__pycache__/browser_camofox.cpython-312.pyc +0 -0
- package/tools/__pycache__/browser_camofox_state.cpython-312.pyc +0 -0
- package/tools/__pycache__/browser_cdp_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/browser_dialog_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/browser_supervisor.cpython-312.pyc +0 -0
- package/tools/__pycache__/browser_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/budget_config.cpython-312.pyc +0 -0
- package/tools/__pycache__/checkpoint_manager.cpython-312.pyc +0 -0
- package/tools/__pycache__/clarify_gateway.cpython-312.pyc +0 -0
- package/tools/__pycache__/clarify_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/code_execution_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/computer_use_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/cronjob_tools.cpython-312.pyc +0 -0
- package/tools/__pycache__/debug_helpers.cpython-312.pyc +0 -0
- package/tools/__pycache__/delegate_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/discord_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/feishu_doc_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/feishu_drive_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/file_operations.cpython-312.pyc +0 -0
- package/tools/__pycache__/file_state.cpython-312.pyc +0 -0
- package/tools/__pycache__/file_tools.cpython-312.pyc +0 -0
- package/tools/__pycache__/homeassistant_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/image_generation_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/interrupt.cpython-312.pyc +0 -0
- package/tools/__pycache__/kanban_tools.cpython-312.pyc +0 -0
- package/tools/__pycache__/lazy_deps.cpython-312.pyc +0 -0
- package/tools/__pycache__/managed_tool_gateway.cpython-312.pyc +0 -0
- package/tools/__pycache__/mcp_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/memory_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/mixture_of_agents_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/openrouter_client.cpython-312.pyc +0 -0
- package/tools/__pycache__/process_registry.cpython-312.pyc +0 -0
- package/tools/__pycache__/registry.cpython-312.pyc +0 -0
- package/tools/__pycache__/schema_sanitizer.cpython-312.pyc +0 -0
- package/tools/__pycache__/send_message_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/session_search_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/skill_manager_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/skill_provenance.cpython-312.pyc +0 -0
- package/tools/__pycache__/skill_usage.cpython-312.pyc +0 -0
- package/tools/__pycache__/skills_guard.cpython-312.pyc +0 -0
- package/tools/__pycache__/skills_sync.cpython-312.pyc +0 -0
- package/tools/__pycache__/skills_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/slash_confirm.cpython-312.pyc +0 -0
- package/tools/__pycache__/terminal_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/tirith_security.cpython-312.pyc +0 -0
- package/tools/__pycache__/todo_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/tool_backend_helpers.cpython-312.pyc +0 -0
- package/tools/__pycache__/tool_result_storage.cpython-312.pyc +0 -0
- package/tools/__pycache__/tts_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/url_safety.cpython-312.pyc +0 -0
- package/tools/__pycache__/video_generation_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/vision_tools.cpython-312.pyc +0 -0
- package/tools/__pycache__/voice_mode.cpython-312.pyc +0 -0
- package/tools/__pycache__/web_tools.cpython-312.pyc +0 -0
- package/tools/__pycache__/website_policy.cpython-312.pyc +0 -0
- package/tools/__pycache__/x_search_tool.cpython-312.pyc +0 -0
- package/tools/__pycache__/xai_http.cpython-312.pyc +0 -0
- package/tools/__pycache__/yuanbao_tools.cpython-312.pyc +0 -0
- package/tools/ansi_strip.py +44 -0
- package/tools/approval.py +1392 -0
- package/tools/binary_extensions.py +42 -0
- package/tools/browser_camofox.py +700 -0
- package/tools/browser_camofox_state.py +48 -0
- package/tools/browser_cdp_tool.py +569 -0
- package/tools/browser_dialog_tool.py +148 -0
- package/tools/browser_providers/__init__.py +10 -0
- package/tools/browser_providers/__pycache__/__init__.cpython-312.pyc +0 -0
- package/tools/browser_providers/__pycache__/base.cpython-312.pyc +0 -0
- package/tools/browser_providers/__pycache__/browser_use.cpython-312.pyc +0 -0
- package/tools/browser_providers/__pycache__/browserbase.cpython-312.pyc +0 -0
- package/tools/browser_providers/__pycache__/firecrawl.cpython-312.pyc +0 -0
- package/tools/browser_providers/base.py +59 -0
- package/tools/browser_providers/browser_use.py +225 -0
- package/tools/browser_providers/browserbase.py +222 -0
- package/tools/browser_providers/firecrawl.py +112 -0
- package/tools/browser_supervisor.py +1457 -0
- package/tools/browser_tool.py +3676 -0
- package/tools/budget_config.py +51 -0
- package/tools/checkpoint_manager.py +1639 -0
- package/tools/clarify_gateway.py +278 -0
- package/tools/clarify_tool.py +141 -0
- package/tools/code_execution_tool.py +1782 -0
- package/tools/computer_use/__init__.py +43 -0
- package/tools/computer_use/__pycache__/__init__.cpython-312.pyc +0 -0
- package/tools/computer_use/__pycache__/backend.cpython-312.pyc +0 -0
- package/tools/computer_use/__pycache__/schema.cpython-312.pyc +0 -0
- package/tools/computer_use/__pycache__/tool.cpython-312.pyc +0 -0
- package/tools/computer_use/backend.py +150 -0
- package/tools/computer_use/cua_backend.py +682 -0
- package/tools/computer_use/schema.py +191 -0
- package/tools/computer_use/tool.py +521 -0
- package/tools/computer_use_tool.py +39 -0
- package/tools/credential_files.py +437 -0
- package/tools/cronjob_tools.py +719 -0
- package/tools/debug_helpers.py +106 -0
- package/tools/delegate_tool.py +2797 -0
- package/tools/discord_tool.py +959 -0
- package/tools/env_passthrough.py +145 -0
- package/tools/environments/__init__.py +14 -0
- package/tools/environments/__pycache__/__init__.cpython-312.pyc +0 -0
- package/tools/environments/__pycache__/base.cpython-312.pyc +0 -0
- package/tools/environments/__pycache__/docker.cpython-312.pyc +0 -0
- package/tools/environments/__pycache__/file_sync.cpython-312.pyc +0 -0
- package/tools/environments/__pycache__/local.cpython-312.pyc +0 -0
- package/tools/environments/__pycache__/managed_modal.cpython-312.pyc +0 -0
- package/tools/environments/__pycache__/modal.cpython-312.pyc +0 -0
- package/tools/environments/__pycache__/modal_utils.cpython-312.pyc +0 -0
- package/tools/environments/__pycache__/singularity.cpython-312.pyc +0 -0
- package/tools/environments/__pycache__/ssh.cpython-312.pyc +0 -0
- package/tools/environments/base.py +844 -0
- package/tools/environments/daytona.py +270 -0
- package/tools/environments/docker.py +656 -0
- package/tools/environments/file_sync.py +400 -0
- package/tools/environments/local.py +658 -0
- package/tools/environments/managed_modal.py +282 -0
- package/tools/environments/modal.py +479 -0
- package/tools/environments/modal_utils.py +199 -0
- package/tools/environments/singularity.py +263 -0
- package/tools/environments/ssh.py +295 -0
- package/tools/environments/vercel_sandbox.py +655 -0
- package/tools/feishu_doc_tool.py +138 -0
- package/tools/feishu_drive_tool.py +431 -0
- package/tools/file_operations.py +1825 -0
- package/tools/file_state.py +332 -0
- package/tools/file_tools.py +1172 -0
- package/tools/fuzzy_match.py +703 -0
- package/tools/homeassistant_tool.py +513 -0
- package/tools/image_generation_tool.py +1098 -0
- package/tools/interrupt.py +98 -0
- package/tools/kanban_tools.py +1139 -0
- package/tools/lazy_deps.py +608 -0
- package/tools/managed_tool_gateway.py +168 -0
- package/tools/mcp_oauth.py +633 -0
- package/tools/mcp_oauth_manager.py +607 -0
- package/tools/mcp_tool.py +3483 -0
- package/tools/memory_tool.py +584 -0
- package/tools/microsoft_graph_auth.py +245 -0
- package/tools/microsoft_graph_client.py +408 -0
- package/tools/mixture_of_agents_tool.py +542 -0
- package/tools/neutts_samples/jo.txt +1 -0
- package/tools/neutts_samples/jo.wav +0 -0
- package/tools/neutts_synth.py +104 -0
- package/tools/openrouter_client.py +33 -0
- package/tools/osv_check.py +155 -0
- package/tools/patch_parser.py +592 -0
- package/tools/path_security.py +43 -0
- package/tools/process_registry.py +1534 -0
- package/tools/registry.py +589 -0
- package/tools/schema_sanitizer.py +370 -0
- package/tools/send_message_tool.py +1900 -0
- package/tools/session_search_tool.py +613 -0
- package/tools/skill_manager_tool.py +932 -0
- package/tools/skill_provenance.py +78 -0
- package/tools/skill_usage.py +610 -0
- package/tools/skills_guard.py +932 -0
- package/tools/skills_hub.py +3263 -0
- package/tools/skills_sync.py +432 -0
- package/tools/skills_tool.py +1569 -0
- package/tools/slash_confirm.py +167 -0
- package/tools/terminal_tool.py +2376 -0
- package/tools/tirith_security.py +775 -0
- package/tools/todo_tool.py +277 -0
- package/tools/tool_backend_helpers.py +144 -0
- package/tools/tool_output_limits.py +92 -0
- package/tools/tool_result_storage.py +232 -0
- package/tools/transcription_tools.py +936 -0
- package/tools/tts_tool.py +2285 -0
- package/tools/url_safety.py +330 -0
- package/tools/video_generation_tool.py +561 -0
- package/tools/vision_tools.py +1422 -0
- package/tools/voice_mode.py +1019 -0
- package/tools/web_tools.py +1551 -0
- package/tools/website_policy.py +283 -0
- package/tools/x_search_tool.py +424 -0
- package/tools/xai_http.py +83 -0
- package/tools/yuanbao_tools.py +736 -0
- package/toolset_distributions.py +364 -0
- package/toolsets.py +866 -0
- package/trajectory_compressor.py +1509 -0
- package/tui_gateway/__init__.py +0 -0
- package/tui_gateway/entry.py +251 -0
- package/tui_gateway/event_publisher.py +126 -0
- package/tui_gateway/render.py +49 -0
- package/tui_gateway/server.py +6623 -0
- package/tui_gateway/slash_worker.py +76 -0
- package/tui_gateway/transport.py +219 -0
- package/tui_gateway/ws.py +178 -0
- 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
|
+
|