octo-agent 0.11.2
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.
- checksums.yaml +7 -0
- data/.clacky/skills/commit/SKILL.md +423 -0
- data/.clacky/skills/gem-release/SKILL.md +199 -0
- data/.clacky/skills/gem-release/scripts/release.sh +304 -0
- data/.clacky/skills/oss-upload/SKILL.md +47 -0
- data/.octorules +106 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +76 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/CONTRIBUTING.md +92 -0
- data/Dockerfile +28 -0
- data/LICENSE.txt +22 -0
- data/POSITIONING.md +46 -0
- data/README.md +134 -0
- data/README_CN.md +134 -0
- data/Rakefile +34 -0
- data/benchmark/fixtures/sample_project/Gemfile +3 -0
- data/benchmark/fixtures/sample_project/lib/api_handler.rb +32 -0
- data/benchmark/fixtures/sample_project/lib/order_calculator.rb +23 -0
- data/benchmark/fixtures/sample_project/lib/user_renderer.rb +20 -0
- data/benchmark/fixtures/sample_project/spec/order_calculator_spec.rb +20 -0
- data/benchmark/results/EVALUATION_REPORT.md +165 -0
- data/benchmark/results/baseline_20260511_174424.json +128 -0
- data/benchmark/results/report_20260511_175256.json +271 -0
- data/benchmark/results/report_20260511_175444.json +271 -0
- data/benchmark/results/treatment_20260511_175103.json +130 -0
- data/benchmark/runner.rb +441 -0
- data/bin/octo +7 -0
- data/docs/agent-first-ui-design.md +77 -0
- data/docs/billing-system.md +318 -0
- data/docs/channel-architecture.md +235 -0
- data/docs/engineering-article.md +343 -0
- data/docs/session-skill-invocation.md +69 -0
- data/docs/time_machine_design.md +247 -0
- data/docs/ui2-architecture.md +124 -0
- data/homebrew/README.md +96 -0
- data/homebrew/openocto.rb +24 -0
- data/lib/octo/agent/hook_manager.rb +61 -0
- data/lib/octo/agent/llm_caller.rb +800 -0
- data/lib/octo/agent/memory_updater.rb +246 -0
- data/lib/octo/agent/message_compressor.rb +225 -0
- data/lib/octo/agent/message_compressor_helper.rb +869 -0
- data/lib/octo/agent/next_message_suggester.rb +215 -0
- data/lib/octo/agent/session_serializer.rb +685 -0
- data/lib/octo/agent/skill_auto_creator.rb +114 -0
- data/lib/octo/agent/skill_evolution.rb +61 -0
- data/lib/octo/agent/skill_manager.rb +466 -0
- data/lib/octo/agent/skill_reflector.rb +89 -0
- data/lib/octo/agent/system_prompt_builder.rb +101 -0
- data/lib/octo/agent/time_machine.rb +214 -0
- data/lib/octo/agent/tool_executor.rb +454 -0
- data/lib/octo/agent/tool_registry.rb +150 -0
- data/lib/octo/agent.rb +2180 -0
- data/lib/octo/agent_config.rb +989 -0
- data/lib/octo/agent_profile.rb +112 -0
- data/lib/octo/anthropic_stream_aggregator.rb +137 -0
- data/lib/octo/background_task_registry.rb +324 -0
- data/lib/octo/banner.rb +34 -0
- data/lib/octo/bedrock_stream_aggregator.rb +137 -0
- data/lib/octo/block_font.rb +331 -0
- data/lib/octo/cli.rb +968 -0
- data/lib/octo/client.rb +623 -0
- data/lib/octo/default_agents/SOUL.md +3 -0
- data/lib/octo/default_agents/USER.md +1 -0
- data/lib/octo/default_agents/base_prompt.md +66 -0
- data/lib/octo/default_agents/coding/profile.yml +2 -0
- data/lib/octo/default_agents/coding/system_prompt.md +67 -0
- data/lib/octo/default_agents/general/profile.yml +2 -0
- data/lib/octo/default_agents/general/system_prompt.md +16 -0
- data/lib/octo/default_parsers/doc_parser.rb +69 -0
- data/lib/octo/default_parsers/docx_parser.rb +188 -0
- data/lib/octo/default_parsers/pdf_parser.rb +120 -0
- data/lib/octo/default_parsers/pdf_parser_ocr.py +103 -0
- data/lib/octo/default_parsers/pdf_parser_plumber.py +62 -0
- data/lib/octo/default_parsers/pptx_parser.rb +140 -0
- data/lib/octo/default_parsers/xlsx_parser.rb +121 -0
- data/lib/octo/default_skills/browser-setup/SKILL.md +426 -0
- data/lib/octo/default_skills/channel-manager/SKILL.md +623 -0
- data/lib/octo/default_skills/channel-manager/dingtalk_setup.rb +191 -0
- data/lib/octo/default_skills/channel-manager/discord_setup.rb +199 -0
- data/lib/octo/default_skills/channel-manager/feishu_setup.rb +574 -0
- data/lib/octo/default_skills/channel-manager/import_lark_skills.rb +97 -0
- data/lib/octo/default_skills/channel-manager/install_feishu_skills.rb +105 -0
- data/lib/octo/default_skills/channel-manager/weixin_setup.rb +274 -0
- data/lib/octo/default_skills/code-explorer/SKILL.md +36 -0
- data/lib/octo/default_skills/cron-task-creator/SKILL.md +257 -0
- data/lib/octo/default_skills/cron-task-creator/evals/evals.json +38 -0
- data/lib/octo/default_skills/onboard/SKILL.md +578 -0
- data/lib/octo/default_skills/onboard/scripts/import_external_skills.rb +413 -0
- data/lib/octo/default_skills/onboard/scripts/install_builtin_skills.rb +97 -0
- data/lib/octo/default_skills/persist-memory/SKILL.md +59 -0
- data/lib/octo/default_skills/personal-website/SKILL.md +113 -0
- data/lib/octo/default_skills/personal-website/publish.rb +235 -0
- data/lib/octo/default_skills/product-help/SKILL.md +123 -0
- data/lib/octo/default_skills/product-help/docs/agent-config.md +74 -0
- data/lib/octo/default_skills/product-help/docs/best-practices.md +49 -0
- data/lib/octo/default_skills/product-help/docs/browser-tool.md +53 -0
- data/lib/octo/default_skills/product-help/docs/built-in-skills.md +43 -0
- data/lib/octo/default_skills/product-help/docs/cli-reference.md +82 -0
- data/lib/octo/default_skills/product-help/docs/create-your-first-skill.md +47 -0
- data/lib/octo/default_skills/product-help/docs/faq.md +98 -0
- data/lib/octo/default_skills/product-help/docs/how-to-use-a-skill.md +58 -0
- data/lib/octo/default_skills/product-help/docs/installation.md +59 -0
- data/lib/octo/default_skills/product-help/docs/memory-system.md +61 -0
- data/lib/octo/default_skills/product-help/docs/octorules.md +62 -0
- data/lib/octo/default_skills/product-help/docs/session-management.md +63 -0
- data/lib/octo/default_skills/product-help/docs/skill-basics.md +55 -0
- data/lib/octo/default_skills/product-help/docs/skill-frontmatter.md +61 -0
- data/lib/octo/default_skills/product-help/docs/web-server.md +49 -0
- data/lib/octo/default_skills/product-help/docs/what-is-octo.md +37 -0
- data/lib/octo/default_skills/product-help/docs/windows-installation.md +36 -0
- data/lib/octo/default_skills/product-help/docs/writing-tips.md +53 -0
- data/lib/octo/default_skills/recall-memory/SKILL.md +65 -0
- data/lib/octo/default_skills/skill-add/SKILL.md +59 -0
- data/lib/octo/default_skills/skill-add/scripts/install_from_zip.rb +295 -0
- data/lib/octo/default_skills/skill-creator/SKILL.md +602 -0
- data/lib/octo/default_skills/skill-creator/agents/analyzer.md +274 -0
- data/lib/octo/default_skills/skill-creator/agents/comparator.md +202 -0
- data/lib/octo/default_skills/skill-creator/agents/grader.md +223 -0
- data/lib/octo/default_skills/skill-creator/eval-viewer/generate_review.py +471 -0
- data/lib/octo/default_skills/skill-creator/eval-viewer/viewer.html +1325 -0
- data/lib/octo/default_skills/skill-creator/references/schemas.md +430 -0
- data/lib/octo/default_skills/skill-creator/scripts/__init__.py +0 -0
- data/lib/octo/default_skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- data/lib/octo/default_skills/skill-creator/scripts/generate_report.py +326 -0
- data/lib/octo/default_skills/skill-creator/scripts/improve_description.py +310 -0
- data/lib/octo/default_skills/skill-creator/scripts/quick_validate.py +103 -0
- data/lib/octo/default_skills/skill-creator/scripts/run_eval.py +317 -0
- data/lib/octo/default_skills/skill-creator/scripts/run_loop.py +331 -0
- data/lib/octo/default_skills/skill-creator/scripts/utils.py +47 -0
- data/lib/octo/default_skills/skill-creator/scripts/validate_skill_frontmatter.rb +143 -0
- data/lib/octo/idle_compression_timer.rb +115 -0
- data/lib/octo/json_ui_controller.rb +204 -0
- data/lib/octo/message_format/anthropic.rb +409 -0
- data/lib/octo/message_format/bedrock.rb +361 -0
- data/lib/octo/message_format/open_ai.rb +222 -0
- data/lib/octo/message_history.rb +373 -0
- data/lib/octo/openai_stream_aggregator.rb +130 -0
- data/lib/octo/plain_ui_controller.rb +166 -0
- data/lib/octo/providers.rb +534 -0
- data/lib/octo/server/browser_manager.rb +397 -0
- data/lib/octo/server/channel/adapters/base.rb +82 -0
- data/lib/octo/server/channel/adapters/dingtalk/adapter.rb +314 -0
- data/lib/octo/server/channel/adapters/dingtalk/api_client.rb +391 -0
- data/lib/octo/server/channel/adapters/dingtalk/stream_client.rb +203 -0
- data/lib/octo/server/channel/adapters/discord/adapter.rb +229 -0
- data/lib/octo/server/channel/adapters/discord/api_client.rb +107 -0
- data/lib/octo/server/channel/adapters/discord/gateway_client.rb +270 -0
- data/lib/octo/server/channel/adapters/feishu/adapter.rb +320 -0
- data/lib/octo/server/channel/adapters/feishu/bot.rb +478 -0
- data/lib/octo/server/channel/adapters/feishu/file_processor.rb +36 -0
- data/lib/octo/server/channel/adapters/feishu/message_parser.rb +129 -0
- data/lib/octo/server/channel/adapters/feishu/ws_client.rb +423 -0
- data/lib/octo/server/channel/adapters/telegram/adapter.rb +375 -0
- data/lib/octo/server/channel/adapters/telegram/api_client.rb +205 -0
- data/lib/octo/server/channel/adapters/wecom/adapter.rb +148 -0
- data/lib/octo/server/channel/adapters/wecom/media_downloader.rb +115 -0
- data/lib/octo/server/channel/adapters/wecom/ws_client.rb +395 -0
- data/lib/octo/server/channel/adapters/weixin/adapter.rb +692 -0
- data/lib/octo/server/channel/adapters/weixin/api_client.rb +402 -0
- data/lib/octo/server/channel/channel_config.rb +178 -0
- data/lib/octo/server/channel/channel_manager.rb +468 -0
- data/lib/octo/server/channel/channel_ui_controller.rb +224 -0
- data/lib/octo/server/channel.rb +33 -0
- data/lib/octo/server/discover.rb +77 -0
- data/lib/octo/server/epipe_safe_io.rb +105 -0
- data/lib/octo/server/http_server.rb +3554 -0
- data/lib/octo/server/scheduler.rb +317 -0
- data/lib/octo/server/server_master.rb +325 -0
- data/lib/octo/server/session_registry.rb +431 -0
- data/lib/octo/server/web_ui_controller.rb +487 -0
- data/lib/octo/session_manager.rb +385 -0
- data/lib/octo/skill.rb +466 -0
- data/lib/octo/skill_loader.rb +328 -0
- data/lib/octo/tools/base.rb +118 -0
- data/lib/octo/tools/browser.rb +625 -0
- data/lib/octo/tools/edit.rb +165 -0
- data/lib/octo/tools/file_reader.rb +549 -0
- data/lib/octo/tools/glob.rb +162 -0
- data/lib/octo/tools/grep.rb +356 -0
- data/lib/octo/tools/invoke_skill.rb +96 -0
- data/lib/octo/tools/list_tasks.rb +54 -0
- data/lib/octo/tools/redo_task.rb +41 -0
- data/lib/octo/tools/request_user_feedback.rb +84 -0
- data/lib/octo/tools/security.rb +333 -0
- data/lib/octo/tools/terminal/output_cleaner.rb +63 -0
- data/lib/octo/tools/terminal/persistent_session.rb +268 -0
- data/lib/octo/tools/terminal/safe_rm.sh +106 -0
- data/lib/octo/tools/terminal/session_manager.rb +213 -0
- data/lib/octo/tools/terminal.rb +1828 -0
- data/lib/octo/tools/todo_manager.rb +374 -0
- data/lib/octo/tools/trash_manager.rb +388 -0
- data/lib/octo/tools/undo_task.rb +35 -0
- data/lib/octo/tools/web_fetch.rb +242 -0
- data/lib/octo/tools/web_search.rb +260 -0
- data/lib/octo/tools/write.rb +77 -0
- data/lib/octo/ui2/block_font.rb +10 -0
- data/lib/octo/ui2/components/base_component.rb +163 -0
- data/lib/octo/ui2/components/command_suggestions.rb +290 -0
- data/lib/octo/ui2/components/common_component.rb +96 -0
- data/lib/octo/ui2/components/inline_input.rb +226 -0
- data/lib/octo/ui2/components/input_area.rb +1338 -0
- data/lib/octo/ui2/components/message_component.rb +99 -0
- data/lib/octo/ui2/components/modal_component.rb +419 -0
- data/lib/octo/ui2/components/todo_area.rb +149 -0
- data/lib/octo/ui2/components/tool_component.rb +107 -0
- data/lib/octo/ui2/components/welcome_banner.rb +139 -0
- data/lib/octo/ui2/layout_manager.rb +807 -0
- data/lib/octo/ui2/line_editor.rb +363 -0
- data/lib/octo/ui2/markdown_renderer.rb +100 -0
- data/lib/octo/ui2/output_buffer.rb +370 -0
- data/lib/octo/ui2/progress_handle.rb +362 -0
- data/lib/octo/ui2/progress_indicator.rb +55 -0
- data/lib/octo/ui2/screen_buffer.rb +273 -0
- data/lib/octo/ui2/terminal_detector.rb +119 -0
- data/lib/octo/ui2/theme_manager.rb +85 -0
- data/lib/octo/ui2/themes/base_theme.rb +105 -0
- data/lib/octo/ui2/themes/hacker_theme.rb +62 -0
- data/lib/octo/ui2/themes/minimal_theme.rb +56 -0
- data/lib/octo/ui2/thinking_verbs.rb +26 -0
- data/lib/octo/ui2/ui_controller.rb +1625 -0
- data/lib/octo/ui2/view_renderer.rb +177 -0
- data/lib/octo/ui2.rb +40 -0
- data/lib/octo/ui_interface.rb +154 -0
- data/lib/octo/utils/arguments_parser.rb +191 -0
- data/lib/octo/utils/browser_detector.rb +195 -0
- data/lib/octo/utils/encoding.rb +92 -0
- data/lib/octo/utils/environment_detector.rb +140 -0
- data/lib/octo/utils/file_ignore_helper.rb +170 -0
- data/lib/octo/utils/file_processor.rb +601 -0
- data/lib/octo/utils/gitignore_parser.rb +154 -0
- data/lib/octo/utils/limit_stack.rb +152 -0
- data/lib/octo/utils/logger.rb +124 -0
- data/lib/octo/utils/login_shell.rb +72 -0
- data/lib/octo/utils/model_pricing.rb +646 -0
- data/lib/octo/utils/parser_manager.rb +165 -0
- data/lib/octo/utils/path_helper.rb +15 -0
- data/lib/octo/utils/scripts_manager.rb +59 -0
- data/lib/octo/utils/string_matcher.rb +158 -0
- data/lib/octo/utils/trash_directory.rb +112 -0
- data/lib/octo/utils/workspace_rules.rb +46 -0
- data/lib/octo/version.rb +5 -0
- data/lib/octo/web/app.css +7141 -0
- data/lib/octo/web/app.js +543 -0
- data/lib/octo/web/apple-touch-icon.png +0 -0
- data/lib/octo/web/auth.js +150 -0
- data/lib/octo/web/channels.js +276 -0
- data/lib/octo/web/datepicker.js +205 -0
- data/lib/octo/web/favicon.png +0 -0
- data/lib/octo/web/i18n.js +1073 -0
- data/lib/octo/web/icon-512.png +0 -0
- data/lib/octo/web/icon-dark.svg +25 -0
- data/lib/octo/web/icon.svg +29 -0
- data/lib/octo/web/index.html +871 -0
- data/lib/octo/web/marked.min.js +69 -0
- data/lib/octo/web/onboard.js +491 -0
- data/lib/octo/web/profile.js +442 -0
- data/lib/octo/web/sessions.js +4421 -0
- data/lib/octo/web/settings.js +913 -0
- data/lib/octo/web/sidebar.js +32 -0
- data/lib/octo/web/skills.js +885 -0
- data/lib/octo/web/tasks.js +297 -0
- data/lib/octo/web/theme.js +105 -0
- data/lib/octo/web/trash.js +343 -0
- data/lib/octo/web/vendor/hljs/highlight.min.js +1244 -0
- data/lib/octo/web/vendor/hljs/hljs-theme.css +95 -0
- data/lib/octo/web/vendor/katex/auto-render.min.js +1 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/katex.min.css +1 -0
- data/lib/octo/web/vendor/katex/katex.min.js +1 -0
- data/lib/octo/web/version.js +449 -0
- data/lib/octo/web/weixin-qr.html +209 -0
- data/lib/octo/web/ws-dispatcher.js +357 -0
- data/lib/octo/web/ws.js +128 -0
- data/lib/octo.rb +145 -0
- data/scripts/build/build.sh +329 -0
- data/scripts/build/lib/apt.sh +56 -0
- data/scripts/build/lib/brew.sh +89 -0
- data/scripts/build/lib/colors.sh +17 -0
- data/scripts/build/lib/gem.sh +95 -0
- data/scripts/build/lib/mise.sh +125 -0
- data/scripts/build/lib/network.sh +157 -0
- data/scripts/build/lib/os.sh +57 -0
- data/scripts/build/lib/shell.sh +37 -0
- data/scripts/build/src/install.sh.cc +174 -0
- data/scripts/build/src/install_browser.sh.cc +101 -0
- data/scripts/build/src/install_full.sh.cc +290 -0
- data/scripts/build/src/install_rails_deps.sh.cc +145 -0
- data/scripts/build/src/install_system_deps.sh.cc +123 -0
- data/scripts/build/src/uninstall.sh.cc +101 -0
- data/scripts/install.ps1 +532 -0
- data/scripts/install.sh +567 -0
- data/scripts/install_browser.sh +479 -0
- data/scripts/install_full.sh +838 -0
- data/scripts/install_rails_deps.sh +746 -0
- data/scripts/install_system_deps.sh +518 -0
- data/scripts/uninstall.sh +287 -0
- data/sig/octo.rbs +4 -0
- metadata +614 -0
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: channel-manager
|
|
3
|
+
description: |
|
|
4
|
+
Configure IM platform channels (Feishu, WeCom, Weixin, Discord, Telegram, DingTalk) for octo.
|
|
5
|
+
Uses browser automation for navigation; guides the user to paste credentials and perform UI steps.
|
|
6
|
+
Trigger on: "channel setup", "setup feishu", "setup wecom", "setup weixin", "setup wechat", "setup discord", "setup telegram", "setup dingtalk",
|
|
7
|
+
"channel config", "channel status", "channel enable", "channel disable", "channel reconfigure", "channel doctor",
|
|
8
|
+
"send message to weixin", "send message to feishu", "send message to wecom", "send message to discord", "send message to telegram", "send message to dingtalk".
|
|
9
|
+
Subcommands: setup, status, enable <platform>, disable <platform>, reconfigure, doctor, send.
|
|
10
|
+
argument-hint: "setup | status | enable <platform> | disable <platform> | reconfigure | doctor | send <platform> <message>"
|
|
11
|
+
allowed-tools:
|
|
12
|
+
- Bash
|
|
13
|
+
- Read
|
|
14
|
+
- Write
|
|
15
|
+
- Edit
|
|
16
|
+
- AskFollowupQuestion
|
|
17
|
+
- Glob
|
|
18
|
+
- Browser
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Channel Setup Skill
|
|
22
|
+
|
|
23
|
+
Configure IM platform channels for octo.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Command Parsing
|
|
28
|
+
|
|
29
|
+
| User says | Subcommand |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `channel setup`, `setup feishu`, `setup wecom`, `setup weixin`, `setup wechat`, `setup discord`, `setup telegram`, `setup dingtalk` | setup |
|
|
32
|
+
| `channel status` | status |
|
|
33
|
+
| `channel enable feishu/wecom/weixin/discord/telegram/dingtalk` | enable |
|
|
34
|
+
| `channel disable feishu/wecom/weixin/discord/telegram/dingtalk` | disable |
|
|
35
|
+
| `channel reconfigure` | reconfigure |
|
|
36
|
+
| `channel doctor` | doctor |
|
|
37
|
+
| `send <message> to weixin/feishu/wecom/discord/telegram/dingtalk` | send |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## `status`
|
|
42
|
+
|
|
43
|
+
Call the server API:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
curl -s http://${OCTO_SERVER_HOST}:${OCTO_SERVER_PORT}/api/channels
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Response shape (example):
|
|
50
|
+
```json
|
|
51
|
+
{"channels":[
|
|
52
|
+
{"platform":"feishu","enabled":true,"running":true,"has_config":true,"app_id":"cli_xxx","domain":"https://open.feishu.cn","allowed_users":[]},
|
|
53
|
+
{"platform":"wecom","enabled":false,"running":false,"has_config":false,"bot_id":""},
|
|
54
|
+
{"platform":"weixin","enabled":true,"running":true,"has_config":true,"has_token":true,"base_url":"https://ilinkai.weixin.qq.com","allowed_users":[]},
|
|
55
|
+
{"platform":"discord","enabled":true,"running":true,"has_config":true,"has_token":true,"allowed_users":[]}
|
|
56
|
+
{"platform":"telegram","enabled":true,"running":true,"has_config":true,"has_token":true,"base_url":"https://api.telegram.org","parse_mode":"Markdown","allowed_users":[]}
|
|
57
|
+
]}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Display the result:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
Channel Status
|
|
64
|
+
─────────────────────────────────────────────────────
|
|
65
|
+
Platform Enabled Running Details
|
|
66
|
+
feishu ✅ yes ✅ yes app_id: cli_xxx...
|
|
67
|
+
wecom ❌ no ❌ no (not configured)
|
|
68
|
+
weixin ✅ yes ✅ yes has_token: true
|
|
69
|
+
discord ✅ yes ✅ yes has_token: true
|
|
70
|
+
telegram ✅ yes ✅ yes has_token: true
|
|
71
|
+
dingtalk ✅ yes ✅ yes client_id: ding_xxx...
|
|
72
|
+
─────────────────────────────────────────────────────
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- Feishu: show `app_id` (truncated to 12 chars)
|
|
76
|
+
- WeCom: show `bot_id` if present
|
|
77
|
+
- Weixin: show `has_token: true/false` (token value is never displayed)
|
|
78
|
+
- Discord: show `has_token: true/false` (token value is never displayed)
|
|
79
|
+
- Telegram: show `has_token: true/false` (bot token is never displayed)
|
|
80
|
+
- DingTalk: show `client_id` (truncated to 12 chars)
|
|
81
|
+
|
|
82
|
+
If the API is unreachable or returns an empty list: "No channels configured yet. Run `/channel-manager setup` to get started."
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## `setup`
|
|
87
|
+
|
|
88
|
+
Ask:
|
|
89
|
+
> Which platform would you like to connect?
|
|
90
|
+
>
|
|
91
|
+
> 1. Feishu
|
|
92
|
+
> 2. WeCom (Enterprise WeChat)
|
|
93
|
+
> 3. Weixin (Personal WeChat via iLink QR login)
|
|
94
|
+
> 4. Discord
|
|
95
|
+
> 5. Telegram (Bot API)
|
|
96
|
+
> 6. DingTalk
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### Feishu setup
|
|
101
|
+
|
|
102
|
+
#### Step 1 — Try automated setup (script)
|
|
103
|
+
|
|
104
|
+
Run the setup script (full path is available in the supporting files list above):
|
|
105
|
+
```bash
|
|
106
|
+
ruby "SKILL_DIR/feishu_setup.rb"
|
|
107
|
+
```
|
|
108
|
+
**Important**: call `terminal` with `timeout: 180` — the script may wait up to 90s for a WebSocket connection in Phase 4.
|
|
109
|
+
|
|
110
|
+
**If exit code is 0:**
|
|
111
|
+
- The script completed successfully.
|
|
112
|
+
- Config is already written to `~/.octo/channels.yml`.
|
|
113
|
+
- Tell the user: "✅ Feishu channel configured automatically! The channel is ready."
|
|
114
|
+
- **Skip Step 2 (manual fallback) and continue to Step 3.**
|
|
115
|
+
|
|
116
|
+
**If exit code is non-0:**
|
|
117
|
+
- Check stdout for the error message.
|
|
118
|
+
- **If the error contains "Browser not configured" or "browser tool":**
|
|
119
|
+
- Tell the user: "The browser tool is not configured yet. Let me help you set it up first..."
|
|
120
|
+
- Invoke the `browser-setup` skill: `invoke_skill("browser-setup", "setup")`.
|
|
121
|
+
- After browser-setup completes, tell the user: "Browser is ready! Let me retry the Feishu setup..."
|
|
122
|
+
- **Retry the script** (same command, same timeout). If it succeeds this time, stop. If it fails again, check the new error and proceed accordingly.
|
|
123
|
+
- **If the error contains "No cookies found" or "Please log in":**
|
|
124
|
+
- Open Feishu login page using browser tool:
|
|
125
|
+
```
|
|
126
|
+
browser(action="navigate", url="https://open.feishu.cn/app")
|
|
127
|
+
```
|
|
128
|
+
- Tell the user: "I've opened Feishu in your browser. Please log in, then reply 'done'."
|
|
129
|
+
- Wait for "done".
|
|
130
|
+
- **Retry the script** (same command, same timeout). Repeat this login-wait-retry loop up to **3 times total**.
|
|
131
|
+
- If any attempt succeeds (exit code 0), stop — setup is complete.
|
|
132
|
+
- If an attempt fails with a **different** error (not a login error), break out of the loop and continue to Step 2.
|
|
133
|
+
- If all 3 attempts fail with login errors, tell the user: "Automated setup was unable to detect a Feishu login after 3 attempts. Switching to guided setup..." and continue to Step 2.
|
|
134
|
+
- **Otherwise (non-login, non-browser error):**
|
|
135
|
+
- Tell the user: "Automated setup encountered an issue: `<error message>`. Switching to guided setup..."
|
|
136
|
+
- Continue to Step 2 (manual flow) below.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
#### Step 2 — Manual guided setup (fallback)
|
|
141
|
+
|
|
142
|
+
Only reach here if the automated script failed.
|
|
143
|
+
|
|
144
|
+
##### Phase 1 — Open Feishu Open Platform
|
|
145
|
+
|
|
146
|
+
1. Navigate: `open https://open.feishu.cn/app`. Pass `isolated: true`.
|
|
147
|
+
2. If a login page or QR code is shown, tell the user to log in and wait for "done".
|
|
148
|
+
3. Confirm the app list is visible.
|
|
149
|
+
|
|
150
|
+
##### Phase 2 — Create a new app
|
|
151
|
+
|
|
152
|
+
4. **Always create a new app** — do NOT reuse existing apps. Guide the user: "Click 'Create Enterprise Self-Built App', fill in name (e.g. Open Octo) and description (e.g. AI assistant powered by octo), then submit. Reply done." Wait for "done".
|
|
153
|
+
|
|
154
|
+
##### Phase 3 — Enable Bot capability
|
|
155
|
+
|
|
156
|
+
5. Feishu opens Add App Capabilities by default after creating an app. Guide the user: "Find the Bot capability card and click the Add button next to it, then reply done." Wait for "done".
|
|
157
|
+
|
|
158
|
+
##### Phase 4 — Get credentials
|
|
159
|
+
|
|
160
|
+
6. Navigate to Credentials & Basic Info in the left menu.
|
|
161
|
+
7. Guide the user: "Copy App ID and App Secret, then paste here. Reply with: App ID: xxx, App Secret: xxx" Wait for the reply. Parse `app_id` and `app_secret`.
|
|
162
|
+
|
|
163
|
+
##### Phase 5 — Add message permissions
|
|
164
|
+
|
|
165
|
+
8. Navigate to Permission Management and open the bulk import dialog.
|
|
166
|
+
9. Guide the user: "In the bulk import dialog, clear the existing example first (select all, delete), then paste the following JSON. Reply done." Wait for "done". Do NOT try to clear or edit via browser — user does it.
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"scopes": {
|
|
171
|
+
"tenant": [
|
|
172
|
+
"im:message",
|
|
173
|
+
"im:message.p2p_msg:readonly",
|
|
174
|
+
"im:message:send_as_bot"
|
|
175
|
+
],
|
|
176
|
+
"user": []
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
##### Phase 6 — Configure event subscription (Long Connection)
|
|
182
|
+
|
|
183
|
+
**CRITICAL**: Feishu requires the long connection to be established *before* you can save the event config. The platform shows "No application connection detected" until `octo server` is running and connected.
|
|
184
|
+
|
|
185
|
+
10. **Apply config and establish connection** — Run:
|
|
186
|
+
```bash
|
|
187
|
+
curl -X POST http://${OCTO_SERVER_HOST}:${OCTO_SERVER_PORT}/api/channels/feishu \
|
|
188
|
+
-H "Content-Type: application/json" \
|
|
189
|
+
-d '{"app_id":"<APP_ID>","app_secret":"<APP_SECRET>","domain":"https://open.feishu.cn"}'
|
|
190
|
+
```
|
|
191
|
+
**CRITICAL: This curl call is the ONLY way to save credentials. NEVER write `~/.octo/channels.yml` or any file under `~/.octo/channels/` directly. The server API handles persistence and hot-reload.**
|
|
192
|
+
11. **Wait for connection** — Poll until log shows `[feishu-ws] WebSocket connected ✅`:
|
|
193
|
+
```bash
|
|
194
|
+
for i in $(seq 1 20); do
|
|
195
|
+
grep -q "\[feishu-ws\] WebSocket connected" ~/.octo/logger/octo-$(date +%Y-%m-%d).log 2>/dev/null && echo "CONNECTED" && break
|
|
196
|
+
sleep 1
|
|
197
|
+
done
|
|
198
|
+
```
|
|
199
|
+
12. **Configure events** — Guide the user: "In Events & Callbacks, select 'Long Connection' mode. Click Save. Then click Add Event, search `im.message.receive_v1`, select it, click Add. Reply done." Wait for "done".
|
|
200
|
+
|
|
201
|
+
##### Phase 7 — Publish the app
|
|
202
|
+
|
|
203
|
+
13. Navigate to Version Management & Release. Guide the user: "Create a new version (e.g. 1.0.0, note: Initial release for Open Octo) and publish it. Reply done." Wait for "done".
|
|
204
|
+
|
|
205
|
+
##### Phase 8 — Validate
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
curl -s -X POST "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" \
|
|
209
|
+
-H "Content-Type: application/json" \
|
|
210
|
+
-d '{"app_id":"<APP_ID>","app_secret":"<APP_SECRET>"}'
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Check for `"code":0`. On success: continue to Step 3 (below).
|
|
214
|
+
|
|
215
|
+
##### Phase 9 — done
|
|
216
|
+
|
|
217
|
+
Step 2 ends here. **Continue to Step 3.**
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
#### Step 3 — Optional: install Feishu CLI
|
|
222
|
+
|
|
223
|
+
Reach here from either Step 1 success or end of Step 2. Read `app_id` and `app_secret` from `~/.octo/channels.yml` (under `channels.feishu`) for the install commands below.
|
|
224
|
+
|
|
225
|
+
Call `request_user_feedback`:
|
|
226
|
+
|
|
227
|
+
zh:
|
|
228
|
+
```json
|
|
229
|
+
{
|
|
230
|
+
\"question\": \"是否要安装「飞书 CLI」?装好之后 AI 可以帮你操作飞书云文档等能力。不装也 OK。\",
|
|
231
|
+
"options": ["启用", "跳过"]
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
en:
|
|
236
|
+
```json
|
|
237
|
+
{
|
|
238
|
+
"question": "Install Feishu CLI? With it, the AI can help you work with Feishu Docs and more. Skipping is fine.",
|
|
239
|
+
"options": ["Enable", "Skip"]
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
If the user picks Skip, stop — setup is complete.
|
|
244
|
+
|
|
245
|
+
If the user picks Enable, run:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
lark-cli --version > /dev/null 2>&1 || npm install -g @larksuite/cli
|
|
249
|
+
echo -n "<APP_SECRET>" | lark-cli config init --app-id <APP_ID> --app-secret-stdin --brand feishu
|
|
250
|
+
ruby "SKILL_DIR/install_feishu_skills.rb"
|
|
251
|
+
lark-cli auth login --recommend
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
The last command blocks up to 10 minutes waiting for browser authorization — make sure the runner's timeout is ≥ 600s.
|
|
255
|
+
|
|
256
|
+
Once you see the authorization URL in the command's stdout, tell the user (do **not** wait for a reply — the CLI's blocking poll will return on its own when authorization completes):
|
|
257
|
+
- zh: "请在浏览器中打开下方链接完成授权:\n<URL>"
|
|
258
|
+
- en: "Open this URL in your browser to authorize:\n<URL>"
|
|
259
|
+
|
|
260
|
+
**Do not kill and restart this command** — restarting invalidates the device code and breaks the link the user already opened. The "hang" is just polling; wait it out.
|
|
261
|
+
|
|
262
|
+
When `lark-cli auth login` returns successfully, tell the user:
|
|
263
|
+
- zh: "✅ 飞书 CLI 已就绪。"
|
|
264
|
+
- en: "✅ Feishu CLI is ready."
|
|
265
|
+
|
|
266
|
+
**Stop — setup is fully complete.**
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
### WeCom setup
|
|
271
|
+
|
|
272
|
+
1. Navigate: `open https://work.weixin.qq.com/wework_admin/frame#/aiHelper/create`. Pass `isolated: true`.
|
|
273
|
+
2. If a login page or QR code is shown, tell the user to log in and wait for "done".
|
|
274
|
+
3. Guide the user: "Scroll to the bottom of the right panel and click 'API mode creation'. Reply done." Wait for "done".
|
|
275
|
+
4. Guide the user: "Click 'Add' next to 'Visible Range'. Select the top-level company node. Click Confirm. Reply done." Wait for "done".
|
|
276
|
+
5. Guide the user: "If Secret is not visible, click 'Get Secret'. Copy Bot ID and Secret **before** clicking Save. Paste here. Reply with: Bot ID: xxx, Secret: xxx" Wait for "done".
|
|
277
|
+
6. Guide the user: "Click Save. Enter name (e.g. Open Octo) and description. Click Confirm. Click Save again. Reply done." Wait for "done".
|
|
278
|
+
7. Parse credentials. Trim whitespace. Ensure bot_id (starts with `aib`) and secret are not swapped. Run:
|
|
279
|
+
```bash
|
|
280
|
+
curl -X POST http://${OCTO_SERVER_HOST}:${OCTO_SERVER_PORT}/api/channels/wecom \
|
|
281
|
+
-H "Content-Type: application/json" \
|
|
282
|
+
-d '{"bot_id":"<BOT_ID>","secret":"<SECRET>"}'
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
On success: "✅ WeCom channel configured. WeCom client → Contacts → Smart Bot to find it."
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
### Weixin setup (Personal WeChat via iLink QR login)
|
|
290
|
+
|
|
291
|
+
Weixin uses a QR code login — no app_id/app_secret needed. The token from the QR scan is saved directly in `channels.yml`.
|
|
292
|
+
|
|
293
|
+
#### Step 1 — Fetch QR code
|
|
294
|
+
|
|
295
|
+
Run the script in `--fetch-qr` mode to get the QR URL without blocking:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
QR_JSON=$(ruby "SKILL_DIR/weixin_setup.rb" --fetch-qr 2>/dev/null)
|
|
299
|
+
echo "$QR_JSON"
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Parse the JSON output:
|
|
303
|
+
- `qrcode_url` — the URL to open in browser (this IS the QR code content)
|
|
304
|
+
- `qrcode_id` — the session ID needed for polling
|
|
305
|
+
|
|
306
|
+
If the output contains `"error"`, show it and stop.
|
|
307
|
+
|
|
308
|
+
#### Step 2 — Show QR code to user (browser or manual fallback)
|
|
309
|
+
|
|
310
|
+
Build the local QR page URL (include current Unix timestamp as `since` to detect new logins only):
|
|
311
|
+
```
|
|
312
|
+
http://${OCTO_SERVER_HOST}:${OCTO_SERVER_PORT}/weixin-qr.html?url=<URL-encoded qrcode_url>&since=<current_unix_timestamp>
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Try browser first** — attempt to open the QR page using the browser tool:
|
|
316
|
+
```
|
|
317
|
+
browser(action="navigate", url="<qr_page_url>")
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**If browser succeeds:** Tell the user:
|
|
321
|
+
> I've opened the WeChat QR code in your browser. Please scan it with WeChat, then confirm in the app.
|
|
322
|
+
|
|
323
|
+
**If browser fails (not configured or unavailable):** Fall back to manual — tell the user:
|
|
324
|
+
> Please open the following link in your browser to scan the WeChat QR code:
|
|
325
|
+
>
|
|
326
|
+
> `http://${OCTO_SERVER_HOST}:${OCTO_SERVER_PORT}/weixin-qr.html?url=<URL-encoded qrcode_url>`
|
|
327
|
+
>
|
|
328
|
+
> Scan the QR code with WeChat, confirm in the app, then reply "done".
|
|
329
|
+
|
|
330
|
+
The page renders a proper scannable QR code image. Do NOT open the raw `qrcode_url` directly — that page shows "请使用微信扫码打开" with no actual QR image.
|
|
331
|
+
|
|
332
|
+
#### Step 3 — Wait for scan and save credentials
|
|
333
|
+
|
|
334
|
+
Once the browser shows the QR page, immediately run the polling script in the background:
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
ruby "SKILL_DIR/weixin_setup.rb" --qrcode-id "$QRCODE_ID"
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Where `$QRCODE_ID` is the `qrcode_id` from Step 2's JSON output.
|
|
341
|
+
|
|
342
|
+
Run this command with `timeout: 60`. If it doesn't succeed, **retry up to 3 times with the same `$QRCODE_ID`** — the QR code stays valid for 5 minutes. Only stop retrying if:
|
|
343
|
+
- Exit code is 0 → success
|
|
344
|
+
- Output contains "expired" → QR expired, offer to restart from Step 1
|
|
345
|
+
- Output contains "timed out" → offer to restart from Step 1
|
|
346
|
+
- 3 retries exhausted → show error and offer to restart from Step 1
|
|
347
|
+
|
|
348
|
+
Tell the user while waiting:
|
|
349
|
+
> Waiting for you to scan the QR code and confirm in WeChat... (this may take a moment)
|
|
350
|
+
|
|
351
|
+
**If exit code is 0:** "✅ Weixin channel configured! You can now message your bot on WeChat."
|
|
352
|
+
|
|
353
|
+
**If exit code is non-0 or times out:** Show the error and offer to retry from Step 2.
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
### Discord setup
|
|
358
|
+
|
|
359
|
+
Discord requires manual portal interaction (hCaptcha gates Application creation). The browser just navigates the user to the portal; the user clicks through and pastes the bot token + app id back.
|
|
360
|
+
|
|
361
|
+
#### Step 1 — Open the developer portal
|
|
362
|
+
|
|
363
|
+
Get the portal URL from the script and open it in the browser:
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
PORTAL_URL=$(ruby "SKILL_DIR/discord_setup.rb" --portal-url)
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Open it: `browser(action="navigate", url="<PORTAL_URL>")`. If the browser tool is not configured, invoke `browser-setup` first, then retry.
|
|
370
|
+
|
|
371
|
+
#### Step 2 — Guide the user through the portal (one round-trip)
|
|
372
|
+
|
|
373
|
+
Tell the user **all** of the following in a single message, then call `request_user_feedback` to collect the values in one reply:
|
|
374
|
+
|
|
375
|
+
> In the Discord Developer Portal I just opened:
|
|
376
|
+
>
|
|
377
|
+
> 1. Click **New Application** (top-right). Name it whatever you like (e.g. "Open Octo"), check the ToS box, click **Create**.
|
|
378
|
+
> 2. In the left nav click **Bot**.
|
|
379
|
+
> 3. Scroll down to **Privileged Gateway Intents** and turn on **MESSAGE CONTENT INTENT**, then click **Save Changes**.
|
|
380
|
+
> 4. Scroll up, click **Reset Token** → **Yes, do it!**. Click **Copy** to copy the bot token. (This is the only time the token is shown — don't navigate away before copying.)
|
|
381
|
+
> 5. In the left nav click **General Information**. Copy the **Application ID**.
|
|
382
|
+
>
|
|
383
|
+
> Paste both values back here in this format (one line):
|
|
384
|
+
>
|
|
385
|
+
> `token=YOUR_BOT_TOKEN app_id=YOUR_APPLICATION_ID`
|
|
386
|
+
|
|
387
|
+
If the user is chatting in a non-English language, append the localized label in parens after each bolded English button name (e.g. `**Bot**(机器人)`). The English label stays primary — it's what they physically click in the portal.
|
|
388
|
+
|
|
389
|
+
Use `request_user_feedback` to collect the reply. Parse with tolerant regex (`token=\S+`, `app_id=\d+`).
|
|
390
|
+
|
|
391
|
+
If the reply is malformed (missing either field), apologise briefly and ask again with the exact same format reminder. Up to 3 retries; after that, surface the original message and stop.
|
|
392
|
+
|
|
393
|
+
#### Step 3 — Validate, save, invite, wait
|
|
394
|
+
|
|
395
|
+
1. Validate the token and save credentials:
|
|
396
|
+
```bash
|
|
397
|
+
ruby "SKILL_DIR/discord_setup.rb" --validate "<BOT_TOKEN>"
|
|
398
|
+
```
|
|
399
|
+
On success the script prints `{"bot_id":"...","username":"..."}` and the adapter starts.
|
|
400
|
+
|
|
401
|
+
2. Generate the invite URL using the application id from Step 2:
|
|
402
|
+
```bash
|
|
403
|
+
ruby "SKILL_DIR/discord_setup.rb" --invite-url "<APP_ID>"
|
|
404
|
+
```
|
|
405
|
+
Open it: `browser(action="navigate", url="<INVITE_URL>")`. Tell the user:
|
|
406
|
+
> Pick your server from the dropdown → **Continue** → **Authorize**. I'll detect when the bot joins.
|
|
407
|
+
>
|
|
408
|
+
> If the dropdown is empty, you don't have a server yet — open <https://discord.com/channels/@me>, click **Add a Server** (the **+** button on the left sidebar) → **Create My Own** → **For me and my friends** → name it → **Create**, then re-open the invite link.
|
|
409
|
+
|
|
410
|
+
3. Wait for the bot to join a guild (long-poll, 10 min timeout). Run with `timeout: 620`:
|
|
411
|
+
```bash
|
|
412
|
+
ruby "SKILL_DIR/discord_setup.rb" --watch-guild
|
|
413
|
+
```
|
|
414
|
+
On exit 0: "✅ Discord channel configured! Bot is in `<guild_name>`. Mention it or DM it from any channel."
|
|
415
|
+
On timeout: offer to re-open the invite URL — the bot token stays valid.
|
|
416
|
+
|
|
417
|
+
### Telegram setup (Bot API)
|
|
418
|
+
|
|
419
|
+
Telegram setup is by far the simplest — no browser automation, no QR. The user creates a bot via @BotFather and pastes the token here.
|
|
420
|
+
|
|
421
|
+
#### Step 1 — Create a bot via @BotFather
|
|
422
|
+
|
|
423
|
+
Tell the user:
|
|
424
|
+
|
|
425
|
+
> Open Telegram and start a chat with **@BotFather** (https://t.me/BotFather). Send `/newbot`, choose a display name and a username ending in `bot`. BotFather will reply with an HTTP API token that looks like `123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ`. Paste the token here.
|
|
426
|
+
>
|
|
427
|
+
> Optional: if your network blocks `api.telegram.org`, also tell me the base URL of your self-hosted Bot API server (e.g. `https://my-tg-proxy.example.com`). Otherwise leave it blank.
|
|
428
|
+
|
|
429
|
+
Wait for the user's reply. Parse the token (matches `^\d+:[\w-]{30,}$`).
|
|
430
|
+
|
|
431
|
+
#### Step 2 — Save credentials and validate
|
|
432
|
+
|
|
433
|
+
Call the server API. It calls `getMe` against the Bot API to validate the token before persisting:
|
|
434
|
+
|
|
435
|
+
```bash
|
|
436
|
+
curl -s -X POST http://${OCTO_SERVER_HOST}:${OCTO_SERVER_PORT}/api/channels/telegram \
|
|
437
|
+
-H "Content-Type: application/json" \
|
|
438
|
+
-d '{"bot_token":"<TOKEN>","base_url":"<BASE_URL_OR_OMIT>"}'
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
- `200 { "ok": true }` — token validated and saved. The adapter starts long-polling immediately.
|
|
442
|
+
- `422 { "ok": false, "error": "..." }` — show the error (commonly "Unauthorized" → wrong token) and offer to retry.
|
|
443
|
+
|
|
444
|
+
On success:
|
|
445
|
+
|
|
446
|
+
> ✅ Telegram channel configured. Open your bot in Telegram and send any message to start chatting.
|
|
447
|
+
>
|
|
448
|
+
> **For group chats**: You must disable Privacy Mode in @BotFather first (`/mybots → Bot Settings → Group Privacy → Turn off`), then remove and re-add the bot to the group. Otherwise the bot cannot receive any messages — including @-mentions.
|
|
449
|
+
|
|
450
|
+
#### Notes
|
|
451
|
+
|
|
452
|
+
- **Group chats — Privacy Mode (IMPORTANT)**: By default Telegram enables Privacy Mode for all bots, which means the bot **cannot receive any group messages, including @-mentions**. To use the bot in a group you MUST disable Privacy Mode first:
|
|
453
|
+
1. Open @BotFather → `/mybots` → select your bot → `Bot Settings` → `Group Privacy` → **Turn off**
|
|
454
|
+
2. **Remove the bot from the group and re-add it** — the permission change does not apply to groups the bot is already in.
|
|
455
|
+
After that, the bot will respond whenever it is @-mentioned or directly replied to.
|
|
456
|
+
- **Self-hosted Bot API**: set `base_url` when `api.telegram.org` is unreachable. See https://github.com/tdlib/telegram-bot-api for the official self-hosted server.
|
|
457
|
+
- **`allowed_users`**: restrict which Telegram user IDs the bot will respond to. Find a user's numeric ID by messaging @userinfobot.
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
## `enable`
|
|
462
|
+
|
|
463
|
+
Call the server API to re-enable the platform (this reads from disk, sets enabled, saves, and hot-reloads):
|
|
464
|
+
|
|
465
|
+
```bash
|
|
466
|
+
curl -s -X POST http://${OCTO_SERVER_HOST}:${OCTO_SERVER_PORT}/api/channels/<platform> \
|
|
467
|
+
-H "Content-Type: application/json" \
|
|
468
|
+
-d '{"enabled": true}'
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
If the platform has no credentials (404 or error), redirect to `setup`.
|
|
472
|
+
|
|
473
|
+
Say: "✅ `<platform>` channel enabled."
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## `disable`
|
|
478
|
+
|
|
479
|
+
Call the server API to disable the platform:
|
|
480
|
+
|
|
481
|
+
```bash
|
|
482
|
+
curl -s -X DELETE http://${OCTO_SERVER_HOST}:${OCTO_SERVER_PORT}/api/channels/<platform>
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Say: "❌ `<platform>` channel disabled."
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
### DingTalk setup
|
|
490
|
+
|
|
491
|
+
#### Step 1 — Get QR code
|
|
492
|
+
|
|
493
|
+
```bash
|
|
494
|
+
ruby "SKILL_DIR/dingtalk_setup.rb" --print-qr
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
Parse the last line starting with `{` to get `qr_url` and `device_code`. On non-0 exit, show the error and abort.
|
|
498
|
+
|
|
499
|
+
#### Step 2 — Show QR and wait
|
|
500
|
+
|
|
501
|
+
Show `qr_url` to the user, ask them to scan with the DingTalk mobile app and tap "Create New Robot", then call `request_user_feedback`.
|
|
502
|
+
|
|
503
|
+
#### Step 3 — Poll for authorization
|
|
504
|
+
|
|
505
|
+
```bash
|
|
506
|
+
ruby "SKILL_DIR/dingtalk_setup.rb" --poll "<device_code>"
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
- **0** → "✅ DingTalk channel configured! Find your robot in DingTalk and send it a message." Stop.
|
|
510
|
+
- **2** → not scanned yet. Ask user to confirm, then re-poll. If output contains `WAITING_TIMEOUT` or `expired`, restart from Step 1.
|
|
511
|
+
- **1** → show the error and abort.
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
## `reconfigure`
|
|
516
|
+
|
|
517
|
+
1. Show current config via `GET /api/channels` (mask secrets — show last 4 chars only).
|
|
518
|
+
2. Ask: update credentials / change allowed users / add a new platform / enable or disable a platform.
|
|
519
|
+
3. For credential updates, re-run the relevant setup flow (which calls `POST /api/channels/<platform>`).
|
|
520
|
+
4. **NEVER write `~/.octo/channels.yml` directly** — always use the server API.
|
|
521
|
+
5. Say: "Channel reconfigured."
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## `doctor`
|
|
526
|
+
|
|
527
|
+
Check each item, report ✅ / ❌ with remediation:
|
|
528
|
+
|
|
529
|
+
1. **Config file** — does `~/.octo/channels.yml` exist and is it readable?
|
|
530
|
+
2. **Required keys** — for each enabled platform:
|
|
531
|
+
- Feishu: `app_id`, `app_secret` present and non-empty
|
|
532
|
+
- WeCom: `bot_id`, `secret` present and non-empty
|
|
533
|
+
- Weixin: `token` present and non-empty in `channels.yml`
|
|
534
|
+
- Discord: `bot_token` present and non-empty in `channels.yml`
|
|
535
|
+
- Telegram: `bot_token` present and non-empty
|
|
536
|
+
3. **Feishu credentials** (if enabled) — run the token API call, check `code=0`.
|
|
537
|
+
4. **Weixin token** (if enabled) — call `GET /api/channels` and check `has_token: true` for the weixin entry.
|
|
538
|
+
5. **Telegram credentials** (if enabled) — call `getMe` against the Bot API:
|
|
539
|
+
```bash
|
|
540
|
+
BOT_TOKEN=$(ruby -ryaml -e 'puts (YAML.load_file(File.expand_path("~/.octo/channels.yml"))["channels"]["telegram"]["bot_token"] rescue "")')
|
|
541
|
+
BASE_URL=$(ruby -ryaml -e 'puts (YAML.load_file(File.expand_path("~/.octo/channels.yml"))["channels"]["telegram"]["base_url"] || "https://api.telegram.org" rescue "https://api.telegram.org")')
|
|
542
|
+
curl -s "$BASE_URL/bot$BOT_TOKEN/getMe" | grep -q '"ok":true' && echo "✅ Telegram OK" || echo "❌ Telegram credentials rejected by getMe"
|
|
543
|
+
```
|
|
544
|
+
6. **WeCom credentials** (if enabled) — search today's log:
|
|
545
|
+
```bash
|
|
546
|
+
grep -iE "wecom adapter loop started|WeCom authentication failed|WeCom WS error response|WecomAdapter" \
|
|
547
|
+
~/.octo/logger/octo-$(date +%Y-%m-%d).log
|
|
548
|
+
```
|
|
549
|
+
- `WeCom authentication failed` or non-zero errcode → ❌ "WeCom credentials incorrect"
|
|
550
|
+
- `adapter loop started` with no auth error → ✅
|
|
551
|
+
6. **Discord credentials** (if enabled) — call `GET /api/channels` and check `has_token: true`. Search today's log:
|
|
552
|
+
```bash
|
|
553
|
+
grep -iE "DiscordAdapter|discord-gateway|/users/@me failed" \
|
|
554
|
+
~/.octo/logger/octo-$(date +%Y-%m-%d).log
|
|
555
|
+
```
|
|
556
|
+
- `/users/@me failed` → ❌ "Discord token invalid or revoked — re-run setup"
|
|
557
|
+
- `authenticated as` with no error → ✅
|
|
558
|
+
7. **DingTalk credentials** (if enabled) — search today's log:
|
|
559
|
+
```bash
|
|
560
|
+
grep -iE "dingtalk-ws|DingTalk.*error|stream.*error" \
|
|
561
|
+
~/.octo/logger/octo-$(date +%Y-%m-%d).log
|
|
562
|
+
```
|
|
563
|
+
- `WebSocket connected` → ✅
|
|
564
|
+
- `Stream endpoint error` or `token error` → ❌ "DingTalk credentials invalid — re-run setup"
|
|
565
|
+
|
|
566
|
+
---
|
|
567
|
+
|
|
568
|
+
## `send`
|
|
569
|
+
|
|
570
|
+
Proactively send a message to a user via an IM channel adapter.
|
|
571
|
+
|
|
572
|
+
### Parse the request
|
|
573
|
+
|
|
574
|
+
Extract two things from the user's instruction:
|
|
575
|
+
- **platform** — one of `weixin`, `feishu`, `wecom`, `discord`, `telegram`, `dingtalk`
|
|
576
|
+
- **message** — the text content to send
|
|
577
|
+
|
|
578
|
+
If the platform cannot be inferred, ask the user to clarify.
|
|
579
|
+
|
|
580
|
+
### Step 1 — Resolve target user (optional)
|
|
581
|
+
|
|
582
|
+
If the user specified a `user_id`, use it directly.
|
|
583
|
+
|
|
584
|
+
Otherwise, list known users first:
|
|
585
|
+
|
|
586
|
+
```bash
|
|
587
|
+
curl -s http://${OCTO_SERVER_HOST}:${OCTO_SERVER_PORT}/api/channels/<platform>/users
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
- If the list is **empty**: tell the user "No known users for `<platform>`. The target user must send at least one message to the bot before proactive messaging is possible." Stop here.
|
|
591
|
+
- If there is **exactly one** user: use it silently.
|
|
592
|
+
- If there are **multiple** users: show the list and ask which one to send to, unless the user already specified one.
|
|
593
|
+
|
|
594
|
+
### Step 2 — Send the message
|
|
595
|
+
|
|
596
|
+
```bash
|
|
597
|
+
curl -s -X POST http://${OCTO_SERVER_HOST}:${OCTO_SERVER_PORT}/api/channels/<platform>/send \
|
|
598
|
+
-H "Content-Type: application/json" \
|
|
599
|
+
-d '{"message": "<message>", "user_id": "<user_id>"}'
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
**Response handling:**
|
|
603
|
+
|
|
604
|
+
| HTTP status | Meaning | Action |
|
|
605
|
+
|---|---|---|
|
|
606
|
+
| `200 { ok: true }` | Delivered | Tell user: "✅ Message sent to `<platform>`." |
|
|
607
|
+
| `400` platform not running | Adapter is stopped | Tell user the platform is not running and suggest `channel enable <platform>`. |
|
|
608
|
+
| `400` no context_token | Token missing | Explain: "The bot has no active session token for this user. Ask the user to send any message to the bot first, then retry." |
|
|
609
|
+
| `503` no known users | Nobody has messaged the bot | Same guidance as empty user list above. |
|
|
610
|
+
| Other error | Unexpected | Show the error message from the response body. |
|
|
611
|
+
|
|
612
|
+
### Constraints & notes
|
|
613
|
+
|
|
614
|
+
- **Weixin (iLink protocol)**: Every outbound message requires a `context_token` that is obtained from the most recent inbound message from that user. The token is cached in memory and reset on server restart. If the server was restarted since the user last wrote, the token is gone and the send will fail — the user must message the bot again.
|
|
615
|
+
- **Feishu / WeCom / Discord / Telegram**: No per-message token required. As long as the adapter is running and the `user_id` / `chat_id` (or Discord channel/user id) is valid, the message will be delivered. For Telegram specifically, the `user_id` must be a Telegram chat_id that the bot can write to — the user must have sent at least one message to the bot first.
|
|
616
|
+
- This feature is intended for **proactive notifications** (e.g. task completion, reminders). It is not a replacement for the normal reply flow triggered by inbound messages.
|
|
617
|
+
|
|
618
|
+
---
|
|
619
|
+
|
|
620
|
+
## Security
|
|
621
|
+
|
|
622
|
+
- Always mask secrets in output (last 4 chars only).
|
|
623
|
+
- Config file must be `chmod 600`.
|