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,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
require_relative '../skill-add/scripts/install_from_zip'
|
|
8
|
+
|
|
9
|
+
# Install Feishu-related skills from the octo platform.
|
|
10
|
+
#
|
|
11
|
+
# Calls GET /api/v1/skills/feishu — same payload shape as /api/v1/skills/builtin:
|
|
12
|
+
# { "skills": [{ "name": "lark-doc", "download_url": "https://..." }, ...] }
|
|
13
|
+
#
|
|
14
|
+
# Each skill is installed sequentially via ZipSkillInstaller into ~/.octo/skills/<name>/.
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# ruby install_feishu_skills.rb
|
|
18
|
+
#
|
|
19
|
+
# Output:
|
|
20
|
+
# Diagnostics → STDERR
|
|
21
|
+
# Last line → JSON: {"installed":N,"attempted":N}
|
|
22
|
+
# Exit code → always 0
|
|
23
|
+
|
|
24
|
+
class FeishuSkillsInstaller
|
|
25
|
+
PRIMARY_HOST = ENV.fetch('OCTO_LICENSE_SERVER', 'https://www.octo.com')
|
|
26
|
+
FALLBACK_HOST = 'https://octo.up.railway.app'
|
|
27
|
+
API_HOSTS = ENV['OCTO_LICENSE_SERVER'] ? [PRIMARY_HOST] : [PRIMARY_HOST, FALLBACK_HOST]
|
|
28
|
+
API_PATH = '/api/v1/skills/feishu'
|
|
29
|
+
API_OPEN_TIMEOUT = 5
|
|
30
|
+
API_READ_TIMEOUT = 10
|
|
31
|
+
|
|
32
|
+
def initialize
|
|
33
|
+
@target_dir = File.join(Dir.home, '.octo', 'skills')
|
|
34
|
+
@installed = 0
|
|
35
|
+
@attempted = 0
|
|
36
|
+
@errors = []
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def run
|
|
40
|
+
skills = fetch_skill_list
|
|
41
|
+
if skills.nil? || skills.empty?
|
|
42
|
+
emit_summary
|
|
43
|
+
return
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
skills.each { |skill| install_one(skill) }
|
|
47
|
+
ensure
|
|
48
|
+
emit_summary
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private def fetch_skill_list
|
|
52
|
+
API_HOSTS.each do |host|
|
|
53
|
+
begin
|
|
54
|
+
uri = URI.parse(host + API_PATH)
|
|
55
|
+
Net::HTTP.start(uri.host, uri.port,
|
|
56
|
+
use_ssl: uri.scheme == 'https',
|
|
57
|
+
open_timeout: API_OPEN_TIMEOUT,
|
|
58
|
+
read_timeout: API_READ_TIMEOUT) do |http|
|
|
59
|
+
response = http.request(Net::HTTP::Get.new(uri.request_uri))
|
|
60
|
+
if response.code.to_i == 200
|
|
61
|
+
payload = JSON.parse(response.body)
|
|
62
|
+
return Array(payload['skills'])
|
|
63
|
+
else
|
|
64
|
+
@errors << "API #{host}: HTTP #{response.code}"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
rescue StandardError => e
|
|
68
|
+
@errors << "API #{host}: #{e.class}: #{e.message}"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private def install_one(skill)
|
|
75
|
+
name = skill['name'].to_s
|
|
76
|
+
download_url = skill['download_url'].to_s
|
|
77
|
+
@attempted += 1
|
|
78
|
+
|
|
79
|
+
if name.empty? || download_url.empty?
|
|
80
|
+
@errors << "skill payload missing name or download_url: #{skill.inspect}"
|
|
81
|
+
return
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
result = ZipSkillInstaller.new(
|
|
85
|
+
download_url,
|
|
86
|
+
skill_name: name,
|
|
87
|
+
target_dir: @target_dir,
|
|
88
|
+
skip_if_exists: false
|
|
89
|
+
).perform
|
|
90
|
+
@installed += result[:installed].size
|
|
91
|
+
@errors.concat(result[:errors]) if result[:errors].any?
|
|
92
|
+
rescue StandardError => e
|
|
93
|
+
@errors << "#{name}: #{e.class}: #{e.message}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private def emit_summary
|
|
97
|
+
unless @errors.empty?
|
|
98
|
+
warn '[install-feishu-skills] non-fatal errors:'
|
|
99
|
+
@errors.each { |e| warn " - #{e}" }
|
|
100
|
+
end
|
|
101
|
+
puts JSON.generate(installed: @installed, attempted: @attempted)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
FeishuSkillsInstaller.new.run if __FILE__ == $0
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# weixin_setup.rb — Automated Weixin (WeChat iLink) channel setup.
|
|
5
|
+
#
|
|
6
|
+
# Modes:
|
|
7
|
+
# --fetch-qr Output JSON {qrcode_url, qrcode_id} then exit — used by Agent/browser flow
|
|
8
|
+
# --qrcode-id <id> Skip QR fetch, use existing qrcode_id, long-poll until confirmed, then save
|
|
9
|
+
# (default) Full flow: fetch QR, display ASCII/URL, long-poll, save
|
|
10
|
+
#
|
|
11
|
+
# Environment (injected by octo server when run via Skill):
|
|
12
|
+
# OCTO_SERVER_PORT — port octo server listens on (default: 8888)
|
|
13
|
+
# OCTO_SERVER_HOST — host (default: 127.0.0.1)
|
|
14
|
+
|
|
15
|
+
require "json"
|
|
16
|
+
require "net/http"
|
|
17
|
+
require "net/https"
|
|
18
|
+
require "uri"
|
|
19
|
+
require "base64"
|
|
20
|
+
require "securerandom"
|
|
21
|
+
require "cgi"
|
|
22
|
+
require "shellwords"
|
|
23
|
+
require "openssl"
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Config
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
ILINK_BASE_URL = "https://ilinkai.weixin.qq.com"
|
|
30
|
+
BOT_TYPE = "3"
|
|
31
|
+
QR_POLL_TIMEOUT_S = 37 # slightly above server's 35s long-poll
|
|
32
|
+
LOGIN_DEADLINE_S = 5 * 60
|
|
33
|
+
|
|
34
|
+
OCTO_SERVER_URL = begin
|
|
35
|
+
host = ENV.fetch("OCTO_SERVER_HOST", "127.0.0.1")
|
|
36
|
+
port = ENV.fetch("OCTO_SERVER_PORT", "8888")
|
|
37
|
+
"http://#{host}:#{port}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
# Mode parsing
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
FETCH_QR_MODE = ARGV.include?("--fetch-qr")
|
|
45
|
+
QRCODE_ID_IDX = ARGV.index("--qrcode-id")
|
|
46
|
+
GIVEN_QRCODE_ID = QRCODE_ID_IDX ? ARGV[QRCODE_ID_IDX + 1] : nil
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# Logging (suppress in --fetch-qr mode so stdout is clean JSON)
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
def step(msg); $stderr.puts("[weixin-setup] #{msg}") unless FETCH_QR_MODE; end
|
|
53
|
+
def ok(msg); $stderr.puts("[weixin-setup] ✅ #{msg}") unless FETCH_QR_MODE; end
|
|
54
|
+
|
|
55
|
+
# In fetch-qr mode, write to stderr so stdout stays clean JSON
|
|
56
|
+
def log(msg)
|
|
57
|
+
if FETCH_QR_MODE
|
|
58
|
+
$stderr.puts("[weixin-setup] #{msg}")
|
|
59
|
+
else
|
|
60
|
+
$stderr.puts("[weixin-setup] #{msg}")
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def fail!(msg)
|
|
65
|
+
if FETCH_QR_MODE
|
|
66
|
+
$stdout.puts(JSON.generate({ error: msg }))
|
|
67
|
+
else
|
|
68
|
+
$stderr.puts("[weixin-setup] ❌ #{msg}")
|
|
69
|
+
end
|
|
70
|
+
exit 1
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
# iLink HTTP helpers
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
def random_wechat_uin
|
|
78
|
+
uint32 = SecureRandom.random_bytes(4).unpack1("N")
|
|
79
|
+
Base64.strict_encode64(uint32.to_s)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def ilink_get(path, extra_headers: {}, timeout: 15)
|
|
83
|
+
uri = URI("#{ILINK_BASE_URL}/#{path}")
|
|
84
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
85
|
+
http.use_ssl = true
|
|
86
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
87
|
+
http.read_timeout = timeout
|
|
88
|
+
http.open_timeout = 10
|
|
89
|
+
|
|
90
|
+
req = Net::HTTP::Get.new(uri.request_uri)
|
|
91
|
+
req["AuthorizationType"] = "ilink_bot_token"
|
|
92
|
+
req["X-WECHAT-UIN"] = random_wechat_uin
|
|
93
|
+
extra_headers.each { |k, v| req[k] = v }
|
|
94
|
+
|
|
95
|
+
res = http.request(req)
|
|
96
|
+
fail!("HTTP #{res.code} from #{path}: #{res.body.slice(0, 200)}") unless res.is_a?(Net::HTTPSuccess)
|
|
97
|
+
JSON.parse(res.body)
|
|
98
|
+
rescue Net::ReadTimeout, Net::OpenTimeout
|
|
99
|
+
nil # caller handles timeout
|
|
100
|
+
rescue => e
|
|
101
|
+
fail!("iLink request failed (#{path}): #{e.message}")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# ---------------------------------------------------------------------------
|
|
105
|
+
# QR code display (non-fetch-qr mode only)
|
|
106
|
+
# ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
def display_qr(qrcode_url)
|
|
109
|
+
displayed = false
|
|
110
|
+
|
|
111
|
+
# 1. Try ASCII via qrencode CLI
|
|
112
|
+
if system("which qrencode > /dev/null 2>&1")
|
|
113
|
+
ascii = `qrencode -t ANSIUTF8 -o - #{Shellwords.shellescape(qrcode_url)} 2>/dev/null`
|
|
114
|
+
if $?.success? && !ascii.empty?
|
|
115
|
+
puts ascii
|
|
116
|
+
displayed = true
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# 2. Generate PNG and open in Preview
|
|
121
|
+
unless displayed
|
|
122
|
+
tmp_path = "/tmp/octo-weixin-qr-#{Process.pid}.png"
|
|
123
|
+
if system("which qrencode > /dev/null 2>&1") &&
|
|
124
|
+
system("qrencode", "-o", tmp_path, qrcode_url, exception: false)
|
|
125
|
+
step("QR code saved to: #{tmp_path}")
|
|
126
|
+
system("open", tmp_path, exception: false) if RUBY_PLATFORM.include?("darwin")
|
|
127
|
+
displayed = true
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# 3. Last resort: print URL
|
|
132
|
+
unless displayed
|
|
133
|
+
$stderr.puts("[weixin-setup] Open this URL with WeChat to login:")
|
|
134
|
+
puts " #{qrcode_url}"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# ---------------------------------------------------------------------------
|
|
139
|
+
# Octo server — save credentials
|
|
140
|
+
# ---------------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
def save_to_server(token:, base_url:)
|
|
143
|
+
uri = URI("#{OCTO_SERVER_URL}/api/channels/weixin")
|
|
144
|
+
body = JSON.generate({ token: token, base_url: base_url })
|
|
145
|
+
|
|
146
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
147
|
+
http.read_timeout = 15
|
|
148
|
+
http.open_timeout = 5
|
|
149
|
+
|
|
150
|
+
req = Net::HTTP::Post.new(uri.path, "Content-Type" => "application/json")
|
|
151
|
+
req.body = body
|
|
152
|
+
|
|
153
|
+
res = http.request(req)
|
|
154
|
+
data = JSON.parse(res.body) rescue {}
|
|
155
|
+
|
|
156
|
+
unless res.is_a?(Net::HTTPSuccess) && data["ok"]
|
|
157
|
+
fail!("Failed to save Weixin config: #{data["error"] || res.body.slice(0, 200)}")
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
ok("Credentials saved via octo server")
|
|
161
|
+
rescue => e
|
|
162
|
+
fail!("Could not reach octo server: #{e.message}")
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# ---------------------------------------------------------------------------
|
|
166
|
+
# Long-poll loop (shared by all modes)
|
|
167
|
+
# ---------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
def poll_until_confirmed(qrcode)
|
|
170
|
+
deadline = Time.now + LOGIN_DEADLINE_S
|
|
171
|
+
scanned_once = false
|
|
172
|
+
|
|
173
|
+
loop do
|
|
174
|
+
fail!("Login timed out. Please run setup again.") if Time.now > deadline
|
|
175
|
+
|
|
176
|
+
resp = ilink_get(
|
|
177
|
+
"ilink/bot/get_qrcode_status?qrcode=#{CGI.escape(qrcode)}",
|
|
178
|
+
extra_headers: { "iLink-App-ClientVersion" => "1" },
|
|
179
|
+
timeout: QR_POLL_TIMEOUT_S
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
next if resp.nil? # read timeout = server-side long-poll ended, retry
|
|
183
|
+
|
|
184
|
+
case resp["status"]
|
|
185
|
+
when "wait"
|
|
186
|
+
# still waiting
|
|
187
|
+
when "scaned"
|
|
188
|
+
unless scanned_once
|
|
189
|
+
$stderr.puts("[weixin-setup] WeChat scanned! Please confirm in the app...")
|
|
190
|
+
scanned_once = true
|
|
191
|
+
end
|
|
192
|
+
when "confirmed"
|
|
193
|
+
token = resp["bot_token"].to_s.strip
|
|
194
|
+
base_url = resp["baseurl"].to_s.strip
|
|
195
|
+
base_url = ILINK_BASE_URL if base_url.empty?
|
|
196
|
+
fail!("Login confirmed but no token received") if token.empty?
|
|
197
|
+
return { token: token, base_url: base_url }
|
|
198
|
+
when "expired"
|
|
199
|
+
fail!("QR code expired. Please run setup again.")
|
|
200
|
+
else
|
|
201
|
+
$stderr.puts("[weixin-setup] Unknown status: #{resp["status"]}, continuing...")
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# ===========================================================================
|
|
207
|
+
# Main
|
|
208
|
+
# ===========================================================================
|
|
209
|
+
|
|
210
|
+
# ---------------------------------------------------------------------------
|
|
211
|
+
# Mode 1: --fetch-qr → output JSON to stdout, exit
|
|
212
|
+
# ---------------------------------------------------------------------------
|
|
213
|
+
|
|
214
|
+
if FETCH_QR_MODE
|
|
215
|
+
$stderr.puts("[weixin-setup] Fetching QR code from iLink...")
|
|
216
|
+
qr_resp = ilink_get("ilink/bot/get_bot_qrcode?bot_type=#{CGI.escape(BOT_TYPE)}")
|
|
217
|
+
fail!("No qrcode in response: #{qr_resp.inspect}") unless qr_resp&.dig("qrcode")
|
|
218
|
+
|
|
219
|
+
qrcode = qr_resp["qrcode"]
|
|
220
|
+
# qrcode_img_content is the URL encoded in the QR (not a base64 image)
|
|
221
|
+
qrcode_url = qr_resp["qrcode_img_content"].to_s.strip
|
|
222
|
+
qrcode_url = "https://liteapp.weixin.qq.com/q/#{qrcode}" if qrcode_url.empty? || !qrcode_url.start_with?("http")
|
|
223
|
+
|
|
224
|
+
$stdout.puts(JSON.generate({ qrcode_id: qrcode, qrcode_url: qrcode_url }))
|
|
225
|
+
exit 0
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# ---------------------------------------------------------------------------
|
|
229
|
+
# Mode 2: --qrcode-id <id> → skip fetch, poll with existing id, save
|
|
230
|
+
# ---------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
if GIVEN_QRCODE_ID
|
|
233
|
+
$stderr.puts("[weixin-setup] Using existing QR session: #{GIVEN_QRCODE_ID}")
|
|
234
|
+
$stderr.puts("[weixin-setup] Waiting for scan confirmation...")
|
|
235
|
+
result = poll_until_confirmed(GIVEN_QRCODE_ID)
|
|
236
|
+
$stderr.puts("[weixin-setup] Confirmed! Saving credentials...")
|
|
237
|
+
save_to_server(token: result[:token], base_url: result[:base_url])
|
|
238
|
+
$stderr.puts("[weixin-setup] ✅ Weixin channel configured!")
|
|
239
|
+
exit 0
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# ---------------------------------------------------------------------------
|
|
243
|
+
# Mode 3: default — full flow (terminal: ASCII QR + long-poll)
|
|
244
|
+
# ---------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
$stderr.puts("[weixin-setup] Fetching QR code from iLink...")
|
|
247
|
+
qr_resp = ilink_get("ilink/bot/get_bot_qrcode?bot_type=#{CGI.escape(BOT_TYPE)}")
|
|
248
|
+
fail!("No qrcode in response: #{qr_resp.inspect}") unless qr_resp&.dig("qrcode")
|
|
249
|
+
|
|
250
|
+
qrcode = qr_resp["qrcode"]
|
|
251
|
+
qrcode_url = qr_resp["qrcode_img_content"].to_s.strip
|
|
252
|
+
qrcode_url = "https://liteapp.weixin.qq.com/q/#{qrcode}" if qrcode_url.empty? || !qrcode_url.start_with?("http")
|
|
253
|
+
|
|
254
|
+
puts
|
|
255
|
+
puts "━" * 60
|
|
256
|
+
puts " Scan the QR code below with WeChat, then confirm in the app."
|
|
257
|
+
puts "━" * 60
|
|
258
|
+
display_qr(qrcode_url)
|
|
259
|
+
puts
|
|
260
|
+
|
|
261
|
+
$stderr.puts("[weixin-setup] Waiting for scan... (timeout: #{LOGIN_DEADLINE_S / 60} minutes)")
|
|
262
|
+
result = poll_until_confirmed(qrcode)
|
|
263
|
+
|
|
264
|
+
$stderr.puts("[weixin-setup] Login confirmed! Saving credentials...")
|
|
265
|
+
save_to_server(token: result[:token], base_url: result[:base_url])
|
|
266
|
+
|
|
267
|
+
puts
|
|
268
|
+
puts "━" * 60
|
|
269
|
+
puts "[weixin-setup] ✅ Weixin channel configured!"
|
|
270
|
+
puts " The adapter will start receiving messages immediately."
|
|
271
|
+
puts "━" * 60
|
|
272
|
+
puts
|
|
273
|
+
|
|
274
|
+
exit 0
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-explorer
|
|
3
|
+
description: Use this skill when exploring, analyzing, or understanding project/code structure. Required for tasks like "analyze project", "explore codebase", "understand how X works".
|
|
4
|
+
agent: coding
|
|
5
|
+
fork_agent: true
|
|
6
|
+
model: lite
|
|
7
|
+
forbidden_tools:
|
|
8
|
+
- write
|
|
9
|
+
- edit
|
|
10
|
+
auto_summarize: true
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Code Explorer Subagent
|
|
14
|
+
|
|
15
|
+
You are now running in a **forked subagent** mode optimized for fast code exploration.
|
|
16
|
+
|
|
17
|
+
## Your Mission
|
|
18
|
+
Quickly explore and analyze the codebase to answer questions or gather information.
|
|
19
|
+
|
|
20
|
+
## Your Restrictions
|
|
21
|
+
- NO modifications: You CANNOT use `write` or `edit` tools
|
|
22
|
+
- Read-only: Your role is to ANALYZE, not to change
|
|
23
|
+
|
|
24
|
+
## Workflow — follow this order strictly
|
|
25
|
+
|
|
26
|
+
1. **List the file tree** — run `glob` with `**/*` to get an overview of the project structure
|
|
27
|
+
2. **Read README.md** — if it exists, read it to understand the project purpose and layout
|
|
28
|
+
3. **Find relevant files** — based on the task, use `grep` to locate key patterns or specific files
|
|
29
|
+
4. **Read only what's needed** — use `file_reader` only on the files directly relevant to the question
|
|
30
|
+
5. **Report clearly** — provide a concise, actionable summary
|
|
31
|
+
|
|
32
|
+
## Rules
|
|
33
|
+
- Do NOT read files blindly — always have a reason before opening a file
|
|
34
|
+
- Do NOT read every file in a directory — be selective
|
|
35
|
+
- Prefer `grep` over `file_reader` for finding specific patterns
|
|
36
|
+
- Stop as soon as you have enough information to answer the question
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cron-task-creator
|
|
3
|
+
description: 'Create, manage, and run scheduled automated tasks (cron jobs) in Octo. Use this skill whenever the user wants to create a new automated task or cron job, set up recurring automation, schedule something to run daily/weekly/hourly, view all scheduled tasks, edit an existing task prompt or cron schedule, enable or disable a task, delete a task, check task run history or logs, or run a task immediately via the WebUI. Trigger on phrases like 定时任务, 自动化任务, 每天自动, 创建任务, cron, 定时执行, scheduled task, automate this, run every day, set up automation, edit my task, list my tasks, what tasks do I have, disable task, run task now, task history, etc.'
|
|
4
|
+
disable-model-invocation: false
|
|
5
|
+
user-invocable: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Cron Task Creator
|
|
9
|
+
|
|
10
|
+
A skill for creating, managing, and running scheduled automated tasks in Octo.
|
|
11
|
+
|
|
12
|
+
## Architecture Overview
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Storage:
|
|
16
|
+
~/.octo/tasks/<name>.md # Task prompt file (self-contained AI instruction)
|
|
17
|
+
~/.octo/schedules.yml # All scheduled plans (YAML list)
|
|
18
|
+
~/.octo/logger/octo-*.log # Execution logs (daily rotation)
|
|
19
|
+
|
|
20
|
+
API Base: http://${OCTO_SERVER_HOST}:${OCTO_SERVER_PORT}
|
|
21
|
+
|
|
22
|
+
Cron-Tasks API (unified — manages task file + schedule together):
|
|
23
|
+
GET /api/cron-tasks → list all cron tasks with schedule info
|
|
24
|
+
POST /api/cron-tasks → create task + schedule {name, content, cron, enabled?}
|
|
25
|
+
PATCH /api/cron-tasks/:name → update {content?, cron?, enabled?}
|
|
26
|
+
DELETE /api/cron-tasks/:name → delete task file + schedule
|
|
27
|
+
POST /api/cron-tasks/:name/run → execute immediately (creates a new session)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Cron Expression Quick Reference
|
|
31
|
+
|
|
32
|
+
| Expression | Meaning |
|
|
33
|
+
|-----------------|---------------------------|
|
|
34
|
+
| `0 9 * * 1-5` | Weekdays at 09:00 |
|
|
35
|
+
| `0 9 * * *` | Every day at 09:00 |
|
|
36
|
+
| `0 */2 * * *` | Every 2 hours |
|
|
37
|
+
| `*/30 * * * *` | Every 30 minutes |
|
|
38
|
+
| `0 19 * * *` | Every day at 19:00 |
|
|
39
|
+
| `0 8 * * 1` | Every Monday at 08:00 |
|
|
40
|
+
| `0 0 1 * *` | First day of every month |
|
|
41
|
+
|
|
42
|
+
Field order: `minute hour day-of-month month day-of-week`
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Operations
|
|
47
|
+
|
|
48
|
+
### 1. LIST — Show all tasks
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
curl -s http://${OCTO_SERVER_HOST}:${OCTO_SERVER_PORT}/api/cron-tasks
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Display each task: name, cron schedule, enabled status, content preview.
|
|
55
|
+
|
|
56
|
+
If no tasks exist, inform the user and offer to create one or show templates.
|
|
57
|
+
|
|
58
|
+
**Key tip**: Remind the user that the Octo WebUI Task Panel (sidebar → Tasks) also shows all tasks and supports direct management.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
### 2. CREATE — New task
|
|
63
|
+
|
|
64
|
+
**Step 1: Gather required info** (only ask for what's missing)
|
|
65
|
+
- What should the task DO? (goal, behavior, output format)
|
|
66
|
+
- How often should it run? (or is it manual-only without a schedule?)
|
|
67
|
+
- Any specific parameters? (URLs, file paths, output location, language)
|
|
68
|
+
|
|
69
|
+
**Step 2: Generate task name**
|
|
70
|
+
- Rule: only `[a-z0-9_-]`, lowercase, no spaces
|
|
71
|
+
- Examples: `daily_report`, `price_monitor`, `weekly_summary`
|
|
72
|
+
|
|
73
|
+
**Step 3: Write the task prompt**
|
|
74
|
+
|
|
75
|
+
The prompt must be:
|
|
76
|
+
- **Self-contained**: the agent running it has zero prior context — include everything needed
|
|
77
|
+
- **Written as direct instructions** to an AI agent (imperative, not conversational)
|
|
78
|
+
- **Detailed**: include URLs, file paths, output format, language, expected output location
|
|
79
|
+
|
|
80
|
+
Good task prompt example:
|
|
81
|
+
```
|
|
82
|
+
You are a price monitoring assistant. Complete the following task:
|
|
83
|
+
|
|
84
|
+
## Goal
|
|
85
|
+
Check the current BTC price on CoinGecko, compare with yesterday's price, and log an alert if the change exceeds 5%.
|
|
86
|
+
|
|
87
|
+
## Steps
|
|
88
|
+
1. Fetch https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd&include_24hr_change=true
|
|
89
|
+
2. Parse the JSON response to get current price and 24h change
|
|
90
|
+
3. If |change| > 5%, write an alert to ~/price_alerts/alert_YYYY-MM-DD.txt
|
|
91
|
+
4. Print the current price and change percentage
|
|
92
|
+
|
|
93
|
+
Execute immediately.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Step 4: Create via API**
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
curl -s -X POST http://${OCTO_SERVER_HOST}:${OCTO_SERVER_PORT}/api/cron-tasks \
|
|
100
|
+
-H "Content-Type: application/json" \
|
|
101
|
+
-d '{
|
|
102
|
+
"name": "task_name",
|
|
103
|
+
"content": "task prompt content...",
|
|
104
|
+
"cron": "0 9 * * *",
|
|
105
|
+
"enabled": true
|
|
106
|
+
}'
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Step 5: Confirm creation**
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
✅ Task created successfully!
|
|
113
|
+
|
|
114
|
+
📋 Task name: daily_standup
|
|
115
|
+
⏰ Schedule: Weekdays at 09:00 (cron: 0 9 * * 1-5)
|
|
116
|
+
|
|
117
|
+
View and manage this task in the Octo WebUI → Tasks panel. Click ▶ Run to execute immediately.
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
### 3. EDIT — Modify an existing task
|
|
123
|
+
|
|
124
|
+
**Step 1**: Identify the task (if unclear, LIST first and ask)
|
|
125
|
+
|
|
126
|
+
**Step 2**: Show current state via LIST or ask user to confirm
|
|
127
|
+
|
|
128
|
+
**Step 3**: Update via API
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# Update content only
|
|
132
|
+
curl -s -X PATCH http://${OCTO_SERVER_HOST}:${OCTO_SERVER_PORT}/api/cron-tasks/task_name \
|
|
133
|
+
-H "Content-Type: application/json" \
|
|
134
|
+
-d '{"content": "new prompt content..."}'
|
|
135
|
+
|
|
136
|
+
# Update cron schedule only
|
|
137
|
+
curl -s -X PATCH http://${OCTO_SERVER_HOST}:${OCTO_SERVER_PORT}/api/cron-tasks/task_name \
|
|
138
|
+
-H "Content-Type: application/json" \
|
|
139
|
+
-d '{"cron": "0 8 * * 1-5"}'
|
|
140
|
+
|
|
141
|
+
# Update both
|
|
142
|
+
curl -s -X PATCH http://${OCTO_SERVER_HOST}:${OCTO_SERVER_PORT}/api/cron-tasks/task_name \
|
|
143
|
+
-H "Content-Type: application/json" \
|
|
144
|
+
-d '{"content": "...", "cron": "0 8 * * 1-5"}'
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Step 4**: Confirm changes
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
✅ Task updated!
|
|
151
|
+
📋 daily_standup
|
|
152
|
+
Schedule: 0 9 * * 1-5 → 0 8 * * 1-5 (now weekdays at 08:00)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
### 4. ENABLE / DISABLE — Toggle a task
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# Disable
|
|
161
|
+
curl -s -X PATCH http://${OCTO_SERVER_HOST}:${OCTO_SERVER_PORT}/api/cron-tasks/task_name \
|
|
162
|
+
-H "Content-Type: application/json" \
|
|
163
|
+
-d '{"enabled": false}'
|
|
164
|
+
|
|
165
|
+
# Enable
|
|
166
|
+
curl -s -X PATCH http://${OCTO_SERVER_HOST}:${OCTO_SERVER_PORT}/api/cron-tasks/task_name \
|
|
167
|
+
-H "Content-Type: application/json" \
|
|
168
|
+
-d '{"enabled": true}'
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Confirm:
|
|
172
|
+
```
|
|
173
|
+
✅ daily_standup has been disabled.
|
|
174
|
+
To re-enable: say "enable daily_standup"
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
### 5. DELETE — Remove a task
|
|
180
|
+
|
|
181
|
+
Always confirm before deleting (unless the user has explicitly said to delete):
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
⚠️ Are you sure you want to delete daily_standup? This cannot be undone.
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
curl -s -X DELETE http://${OCTO_SERVER_HOST}:${OCTO_SERVER_PORT}/api/cron-tasks/task_name
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
### 6. HISTORY — View run history
|
|
194
|
+
|
|
195
|
+
Read the daily log files directly:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
grep "task_name" ~/.octo/logger/octo-$(date +%Y-%m-%d).log | tail -20
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Or search across recent days:
|
|
202
|
+
```bash
|
|
203
|
+
grep -h "task_name" ~/.octo/logger/octo-*.log | tail -30
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Display format:
|
|
207
|
+
```
|
|
208
|
+
📊 Run History: ai_news_x_daily
|
|
209
|
+
|
|
210
|
+
Mar 10 19:00 ❌ Failed — JSON::ParserError: unexpected end of input
|
|
211
|
+
Mar 09 19:00 ✅ Success — took 1m 42s
|
|
212
|
+
Mar 08 19:00 ✅ Success — took 2m 10s
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
### 7. RUN NOW — Execute immediately
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
curl -s -X POST http://${OCTO_SERVER_HOST}:${OCTO_SERVER_PORT}/api/cron-tasks/task_name/run
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
This creates a new session. Tell the user:
|
|
224
|
+
```
|
|
225
|
+
▶️ Task started in a new session.
|
|
226
|
+
View it in the Octo WebUI → Sessions panel.
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
### 8. TEMPLATES — Browse common task templates
|
|
232
|
+
|
|
233
|
+
When user says "what templates are there" or "what can I automate":
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
📚 Common Task Templates — pick one to get started:
|
|
237
|
+
|
|
238
|
+
1. 📰 AI News Digest — Daily fetch of AI news from X/RSS, generate Markdown report
|
|
239
|
+
2. 💰 Price Monitor — Check crypto/stock prices on a schedule, log alerts on anomalies
|
|
240
|
+
3. 📊 Weekly Work Summary — Every Monday, summarize last week's work into a report
|
|
241
|
+
4. 🌤 Weather Reminder — Fetch weather every morning and save to file
|
|
242
|
+
5. 🔍 Competitor Monitor — Periodically scrape competitor sites for changes
|
|
243
|
+
6. 📝 Journal Prompt — Evening reminder to journal with daily reflection questions
|
|
244
|
+
7. 🔗 Link Health Check — Periodically verify specified URLs are accessible
|
|
245
|
+
8. 📂 File Backup — Regularly back up a specified directory to another location
|
|
246
|
+
|
|
247
|
+
Tell me which one interests you, or describe your own use case!
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Important Notes
|
|
253
|
+
|
|
254
|
+
- Task names: only `[a-z0-9_-]`, no spaces, no uppercase
|
|
255
|
+
- Task prompt files must be **self-contained** — the executing agent has no prior memory
|
|
256
|
+
- Octo server must be running for cron to trigger automatically (checked every minute)
|
|
257
|
+
- The WebUI Task Panel is the preferred interface for managing tasks — always remind the user to check it after changes
|