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,53 @@
|
|
|
1
|
+
# Skill Writing Tips
|
|
2
|
+
|
|
3
|
+
## Describe the Trigger Well
|
|
4
|
+
|
|
5
|
+
The `description` field is how the agent decides when to use your skill. Write it as a condition:
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
description: 'Use this skill when the user wants to set up a new Ruby on Rails project'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Be Specific
|
|
12
|
+
|
|
13
|
+
Bad:
|
|
14
|
+
```
|
|
15
|
+
Help with coding.
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Good:
|
|
19
|
+
```
|
|
20
|
+
Analyze the current codebase and suggest refactoring opportunities. Focus on performance bottlenecks and code duplication.
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Use Examples
|
|
24
|
+
|
|
25
|
+
Include concrete examples in your instructions:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
When the user says "make it faster", check:
|
|
29
|
+
1. Database queries (look for N+1)
|
|
30
|
+
2. Background job opportunities
|
|
31
|
+
3. Caching candidates
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Set Boundaries with forbidden_tools
|
|
35
|
+
|
|
36
|
+
If your skill should never modify files or run shell commands:
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
forbidden_tools:
|
|
40
|
+
- write
|
|
41
|
+
- edit
|
|
42
|
+
- terminal
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Fork for Complex Tasks
|
|
46
|
+
|
|
47
|
+
Set `fork_agent: true` when the skill performs multi-step work. This isolates the skill's context from the main session.
|
|
48
|
+
|
|
49
|
+
## Keep It Maintainable
|
|
50
|
+
|
|
51
|
+
- One skill = one responsibility
|
|
52
|
+
- Update skills based on execution results
|
|
53
|
+
- Version your skills by renaming the directory
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: recall-memory
|
|
3
|
+
description: Recall relevant long-term memories on demand. Given a topic or question, judges relevance from pre-loaded metadata, loads only relevant files, and returns a concise summary to the main agent.
|
|
4
|
+
fork_agent: true
|
|
5
|
+
user-invocable: false
|
|
6
|
+
auto_summarize: true
|
|
7
|
+
forbidden_tools:
|
|
8
|
+
- write
|
|
9
|
+
- edit
|
|
10
|
+
- web_search
|
|
11
|
+
- web_fetch
|
|
12
|
+
- browser
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Recall Memory Subagent
|
|
16
|
+
|
|
17
|
+
You are a **Memory Recall Subagent**. Your sole job is to find and return relevant long-term memories for the main agent.
|
|
18
|
+
|
|
19
|
+
## Available Memory Files
|
|
20
|
+
|
|
21
|
+
The following memory files exist in `~/.octo/memories/`. This list was pre-loaded for you — **do NOT re-scan the directory**.
|
|
22
|
+
|
|
23
|
+
<%= memories_meta %>
|
|
24
|
+
|
|
25
|
+
## Your Workflow — follow strictly
|
|
26
|
+
|
|
27
|
+
### Step 1: Judge relevance
|
|
28
|
+
|
|
29
|
+
From the list above, decide which files are relevant to the task/topic passed to you.
|
|
30
|
+
|
|
31
|
+
**Rules:**
|
|
32
|
+
- Match by `topic` and `description` against the requested task
|
|
33
|
+
- If nothing matches, immediately return: "No relevant memories found for: <task>"
|
|
34
|
+
- Do NOT load files that are clearly irrelevant
|
|
35
|
+
|
|
36
|
+
### Step 2: Load relevant files and return
|
|
37
|
+
|
|
38
|
+
For each relevant file:
|
|
39
|
+
|
|
40
|
+
1. Read the full content:
|
|
41
|
+
```
|
|
42
|
+
file_reader(path: "~/.octo/memories/<filename>")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
2. Touch the file to update its mtime (LRU signal — keeps it surfaced in future recalls):
|
|
46
|
+
```
|
|
47
|
+
terminal(command: "touch ~/.octo/memories/<filename>")
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Return ONLY the memory content, structured as:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
## Recalled Memories: <task>
|
|
54
|
+
|
|
55
|
+
### <Topic Name>
|
|
56
|
+
<content verbatim or lightly summarized if very long>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Rules
|
|
60
|
+
|
|
61
|
+
- NEVER modify any files
|
|
62
|
+
- NEVER load irrelevant files — keep output minimal and focused
|
|
63
|
+
- NEVER add commentary beyond the memory content itself
|
|
64
|
+
- If a file exceeds 1000 tokens of content, summarize the least important parts
|
|
65
|
+
- Stop immediately after returning the summary
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skill-add
|
|
3
|
+
description: 'Install skills from a zip URL or local zip file path. Use this skill whenever the user wants to install a skill from a zip link or a local file, or uses commands like /skill-add with a URL or file path. Trigger on phrases like: install skill, install from zip, skill from zip, skill from url, add skill from zip, 安装skill, 从zip安装skill, 从本地安装skill.'
|
|
4
|
+
disable-model-invocation: false
|
|
5
|
+
user-invocable: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Skill Add — Zip Installer
|
|
9
|
+
|
|
10
|
+
Installs a skill from a zip URL **or a local zip file path** using the bundled `install_from_zip.rb` script.
|
|
11
|
+
|
|
12
|
+
## How to Install
|
|
13
|
+
|
|
14
|
+
The script path is listed in the Supporting Files above (`scripts/install_from_zip.rb`) — use the full path directly, no `find` needed.
|
|
15
|
+
|
|
16
|
+
The script accepts **either a remote URL or a local file path**:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
ruby "SKILL_DIR/scripts/install_from_zip.rb" <zip_url_or_path> [slug]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- `<zip_url_or_path>` — a remote `https://` URL **or** an absolute/relative local path to a `.zip` file
|
|
23
|
+
- `[slug]` — optional; the skill's directory name. If omitted, inferred from the filename (e.g. `canvas-design-1.2.0.zip` → `canvas-design`)
|
|
24
|
+
|
|
25
|
+
The script handles everything automatically:
|
|
26
|
+
- For URLs: downloads the zip (follows HTTP redirects)
|
|
27
|
+
- For local paths: reads the file directly (no download needed)
|
|
28
|
+
- Extracts and locates all `SKILL.md` files inside
|
|
29
|
+
- Copies skill directories to `~/.octo/skills/`
|
|
30
|
+
- Reports installed skills with their descriptions
|
|
31
|
+
|
|
32
|
+
**Do NOT manually download or unzip — the script handles everything.**
|
|
33
|
+
|
|
34
|
+
## Examples
|
|
35
|
+
|
|
36
|
+
**From a remote URL:**
|
|
37
|
+
```
|
|
38
|
+
/skill-add https://store.octo.ai/skills/canvas-design-1.2.0.zip
|
|
39
|
+
```
|
|
40
|
+
```bash
|
|
41
|
+
ruby "SKILL_DIR/scripts/install_from_zip.rb" \
|
|
42
|
+
"https://store.octo.ai/skills/canvas-design-1.2.0.zip" \
|
|
43
|
+
"canvas-design"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**From a local file:**
|
|
47
|
+
```
|
|
48
|
+
/skill-add /Users/alice/Downloads/my-skill-1.0.0.zip
|
|
49
|
+
```
|
|
50
|
+
```bash
|
|
51
|
+
ruby "SKILL_DIR/scripts/install_from_zip.rb" \
|
|
52
|
+
"/Users/alice/Downloads/my-skill-1.0.0.zip"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Notes
|
|
56
|
+
|
|
57
|
+
- Skills install to `~/.octo/skills/` by default
|
|
58
|
+
- Local paths may be absolute (`/path/to/skill.zip`) or use `~` (`~/Downloads/skill.zip`)
|
|
59
|
+
- If the user doesn't provide a URL or path, ask them for the zip source
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'tmpdir'
|
|
6
|
+
require 'uri'
|
|
7
|
+
require 'net/http'
|
|
8
|
+
require 'find'
|
|
9
|
+
|
|
10
|
+
# Install a skill from a remote zip archive URL.
|
|
11
|
+
# Usage: ruby install_from_zip.rb <zip_url>
|
|
12
|
+
#
|
|
13
|
+
# The zip archive is expected to contain a skill directory at its root, e.g.:
|
|
14
|
+
# my-skill/
|
|
15
|
+
# SKILL.md
|
|
16
|
+
# scripts/
|
|
17
|
+
#
|
|
18
|
+
# Or the archive may contain multiple skill directories (each with a SKILL.md).
|
|
19
|
+
class ZipSkillInstaller
|
|
20
|
+
ZIP_URL_PATTERN = %r{^https?://.+\.zip(\?.*)?$}i
|
|
21
|
+
|
|
22
|
+
def initialize(zip_source, skill_name: nil, target_dir: nil, skip_if_exists: false)
|
|
23
|
+
@zip_source = zip_source
|
|
24
|
+
@local_path = local_zip_path?(zip_source)
|
|
25
|
+
# skill_name can be provided explicitly (e.g. slug from the store API).
|
|
26
|
+
# If not provided, we try to infer it from the filename in the URL/path, e.g.
|
|
27
|
+
# "ui-ux-pro-max-1.0.0.zip" → "ui-ux-pro-max".
|
|
28
|
+
@skill_name = skill_name || infer_skill_name(zip_source)
|
|
29
|
+
@target_dir = target_dir || File.join(Dir.home, '.octo', 'skills')
|
|
30
|
+
# When true, existing skill directories are preserved and the install for
|
|
31
|
+
# that specific skill is skipped (recorded in @skipped_skills).
|
|
32
|
+
# Default false keeps the legacy "overwrite" behaviour for `install`.
|
|
33
|
+
@skip_if_exists = skip_if_exists
|
|
34
|
+
# Suppresses user-facing puts for programmatic callers (set by `perform`).
|
|
35
|
+
@silent = false
|
|
36
|
+
@installed_skills = []
|
|
37
|
+
@skipped_skills = []
|
|
38
|
+
@errors = []
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Programmatic entry point for library-style callers (e.g. onboard pre-install).
|
|
42
|
+
#
|
|
43
|
+
# Unlike `install`, this method:
|
|
44
|
+
# - does NOT print user-facing output
|
|
45
|
+
# - does NOT call `exit` on failure (raises instead)
|
|
46
|
+
# - returns a result hash: { installed: [...], skipped: [...], errors: [...] }
|
|
47
|
+
#
|
|
48
|
+
# The caller is responsible for rendering feedback and deciding whether any
|
|
49
|
+
# error is fatal.
|
|
50
|
+
def perform
|
|
51
|
+
@silent = true
|
|
52
|
+
do_install
|
|
53
|
+
{ installed: @installed_skills, skipped: @skipped_skills, errors: @errors }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Main installation entry point (CLI). Prints progress, prints a final
|
|
57
|
+
# report, and calls `exit` on failure. Use `perform` for programmatic use.
|
|
58
|
+
def install
|
|
59
|
+
do_install
|
|
60
|
+
report_results
|
|
61
|
+
rescue ArgumentError => e
|
|
62
|
+
puts "Error: #{e.message}"
|
|
63
|
+
exit 1
|
|
64
|
+
rescue StandardError => e
|
|
65
|
+
puts "Error: Installation failed: #{e.message}"
|
|
66
|
+
exit 1
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Shared core used by both `install` (CLI) and `perform` (library).
|
|
70
|
+
# Raises on invalid input; the caller decides how to surface errors.
|
|
71
|
+
private def do_install
|
|
72
|
+
if @local_path
|
|
73
|
+
# Install directly from a local zip file — no download needed.
|
|
74
|
+
# Expand tilde in path (e.g. ~/Downloads/skill.zip).
|
|
75
|
+
expanded = File.expand_path(@zip_source)
|
|
76
|
+
raise ArgumentError, "File not found: #{@zip_source}" unless File.exist?(expanded)
|
|
77
|
+
raise ArgumentError, "Not a zip file: #{@zip_source}" unless expanded.end_with?('.zip')
|
|
78
|
+
|
|
79
|
+
Dir.mktmpdir('octo-zip-') do |tmpdir|
|
|
80
|
+
extract_zip(expanded, tmpdir)
|
|
81
|
+
discover_and_install_skills(File.join(tmpdir, 'extracted'))
|
|
82
|
+
end
|
|
83
|
+
else
|
|
84
|
+
# Install from a remote URL.
|
|
85
|
+
unless valid_zip_url?
|
|
86
|
+
raise ArgumentError, "Invalid zip source: #{@zip_source}\n" \
|
|
87
|
+
"Provide an http(s) URL ending with .zip, or an absolute path to a local zip file."
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
Dir.mktmpdir('octo-zip-') do |tmpdir|
|
|
91
|
+
zip_path = download_zip(tmpdir)
|
|
92
|
+
extract_zip(zip_path, tmpdir)
|
|
93
|
+
discover_and_install_skills(File.join(tmpdir, 'extracted'))
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Return true if the source looks like a local file path (absolute or relative ending in .zip).
|
|
99
|
+
private def local_zip_path?(source)
|
|
100
|
+
source.start_with?('/') || source.start_with?('~') || source.start_with?('./') ||
|
|
101
|
+
(source.end_with?('.zip') && !source.start_with?('http'))
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Infer a skill name from the zip filename, stripping version suffixes.
|
|
105
|
+
# Works for both URLs and local paths.
|
|
106
|
+
# e.g. "ui-ux-pro-max-1.0.0.zip" → "ui-ux-pro-max"
|
|
107
|
+
private def infer_skill_name(source)
|
|
108
|
+
filename = if source.start_with?('http')
|
|
109
|
+
File.basename(URI.parse(source).path, '.zip') rescue File.basename(source, '.zip')
|
|
110
|
+
else
|
|
111
|
+
File.basename(source, '.zip')
|
|
112
|
+
end
|
|
113
|
+
# Strip trailing version segment like "-1.0.0" or "-2.3"
|
|
114
|
+
filename.sub(/-\d+(\.\d+)+$/, '')
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private def valid_zip_url?
|
|
118
|
+
@zip_source.match?(ZIP_URL_PATTERN)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Download the zip file to tmpdir and return its local path.
|
|
122
|
+
private def download_zip(tmpdir)
|
|
123
|
+
unless @silent
|
|
124
|
+
puts "Downloading skill package..."
|
|
125
|
+
puts " #{@zip_source}"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
zip_path = File.join(tmpdir, 'skill.zip')
|
|
129
|
+
uri = URI.parse(@zip_source)
|
|
130
|
+
|
|
131
|
+
# Follow redirects up to 5 times (ActiveStorage often redirects).
|
|
132
|
+
max_redirects = 5
|
|
133
|
+
current_uri = uri
|
|
134
|
+
|
|
135
|
+
max_redirects.times do
|
|
136
|
+
Net::HTTP.start(current_uri.host, current_uri.port,
|
|
137
|
+
use_ssl: current_uri.scheme == 'https',
|
|
138
|
+
open_timeout: 15, read_timeout: 60) do |http|
|
|
139
|
+
request = Net::HTTP::Get.new(current_uri.request_uri)
|
|
140
|
+
http.request(request) do |response|
|
|
141
|
+
case response.code.to_i
|
|
142
|
+
when 200
|
|
143
|
+
File.open(zip_path, 'wb') { |f| response.read_body { |chunk| f.write(chunk) } }
|
|
144
|
+
return zip_path
|
|
145
|
+
when 301, 302, 303, 307, 308
|
|
146
|
+
location = response['location']
|
|
147
|
+
raise "Redirect loop or missing Location header" if location.nil? || location == current_uri.to_s
|
|
148
|
+
current_uri = URI.parse(location)
|
|
149
|
+
else
|
|
150
|
+
raise "HTTP #{response.code} while downloading #{@zip_source}"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
raise "Too many redirects downloading #{@zip_source}"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Extract the zip archive into <tmpdir>/extracted/.
|
|
160
|
+
private def extract_zip(zip_path, tmpdir)
|
|
161
|
+
puts "Extracting package..." unless @silent
|
|
162
|
+
extracted_dir = File.join(tmpdir, 'extracted')
|
|
163
|
+
FileUtils.mkdir_p(extracted_dir)
|
|
164
|
+
|
|
165
|
+
# Prefer the 'unzip' system command; fall back to Ruby's built-in zip support via ZipFile.
|
|
166
|
+
if system('which', 'unzip', out: File::NULL, err: File::NULL)
|
|
167
|
+
result = system('unzip', '-q', zip_path, '-d', extracted_dir)
|
|
168
|
+
raise "unzip failed (exit code #{$?.exitstatus})" unless result
|
|
169
|
+
else
|
|
170
|
+
# Attempt to use the 'zip' gem if available, otherwise raise a clear error.
|
|
171
|
+
begin
|
|
172
|
+
require 'zip'
|
|
173
|
+
Zip::File.open(zip_path) do |zip|
|
|
174
|
+
zip.each do |entry|
|
|
175
|
+
dest = File.join(extracted_dir, entry.name)
|
|
176
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
|
177
|
+
entry.extract(dest)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
rescue LoadError
|
|
181
|
+
raise "Cannot extract zip: 'unzip' command not found and 'zip' gem is not installed.\n" \
|
|
182
|
+
"Install unzip (e.g. brew install unzip) and try again."
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Walk the extracted directory and install every skill (directory containing SKILL.md).
|
|
188
|
+
# Supports two zip layouts:
|
|
189
|
+
# Layout A — SKILL.md at zip root (flat): extracted/SKILL.md
|
|
190
|
+
# Layout B — skill in subdirectory: extracted/my-skill/SKILL.md
|
|
191
|
+
private def discover_and_install_skills(extracted_dir)
|
|
192
|
+
# Layout A: SKILL.md directly under the extraction root.
|
|
193
|
+
if File.exist?(File.join(extracted_dir, 'SKILL.md'))
|
|
194
|
+
install_skill(extracted_dir, skill_name: @skill_name)
|
|
195
|
+
return
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Layout B: one or more skill subdirectories each containing SKILL.md.
|
|
199
|
+
skill_dirs = Dir.glob(File.join(extracted_dir, '*/SKILL.md')).map { |f| File.dirname(f) }
|
|
200
|
+
|
|
201
|
+
if skill_dirs.empty?
|
|
202
|
+
raise "No SKILL.md found in the zip archive. " \
|
|
203
|
+
"Make sure the package contains a valid skill directory."
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
skill_dirs.each { |dir| install_skill(dir) }
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Copy a single skill directory into the target skills folder.
|
|
210
|
+
# skill_name overrides the directory basename (used for Layout A flat zips).
|
|
211
|
+
private def install_skill(skill_src_dir, skill_name: nil)
|
|
212
|
+
name = skill_name || File.basename(skill_src_dir)
|
|
213
|
+
target_path = File.join(@target_dir, name)
|
|
214
|
+
|
|
215
|
+
if File.exist?(target_path)
|
|
216
|
+
if @skip_if_exists
|
|
217
|
+
@skipped_skills << { name: name, path: target_path, reason: 'already exists' }
|
|
218
|
+
return
|
|
219
|
+
end
|
|
220
|
+
puts "Skill '#{name}' already exists — overwriting..." unless @silent
|
|
221
|
+
FileUtils.rm_rf(target_path)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
FileUtils.mkdir_p(target_path)
|
|
225
|
+
# Copy contents of skill_src_dir into target_path (not the dir itself).
|
|
226
|
+
FileUtils.cp_r(Dir.glob("#{skill_src_dir}/*"), target_path)
|
|
227
|
+
|
|
228
|
+
description = extract_description(File.join(target_path, 'SKILL.md'))
|
|
229
|
+
@installed_skills << { name: name, path: target_path, description: description }
|
|
230
|
+
rescue StandardError => e
|
|
231
|
+
@errors << "Failed to install '#{name}': #{e.message}"
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Parse the description field from SKILL.md YAML frontmatter.
|
|
235
|
+
private def extract_description(skill_file)
|
|
236
|
+
return "No description" unless File.exist?(skill_file)
|
|
237
|
+
|
|
238
|
+
content = File.read(skill_file)
|
|
239
|
+
if content =~ /\A---\s*\n(.*?)\n---/m
|
|
240
|
+
frontmatter = $1
|
|
241
|
+
return $1.strip if frontmatter =~ /^description:\s*(.+)$/
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
"No description"
|
|
245
|
+
rescue StandardError
|
|
246
|
+
"No description"
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Print a human-readable summary of what was installed.
|
|
250
|
+
private def report_results
|
|
251
|
+
puts "\n" + "=" * 60
|
|
252
|
+
|
|
253
|
+
if @installed_skills.empty?
|
|
254
|
+
puts "No skills were installed."
|
|
255
|
+
if @errors.any?
|
|
256
|
+
puts "\nErrors:"
|
|
257
|
+
@errors.each { |e| puts " • #{e}" }
|
|
258
|
+
end
|
|
259
|
+
exit 1
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
puts "Installation complete!"
|
|
263
|
+
puts "\nInstalled #{@installed_skills.size} skill(s):\n\n"
|
|
264
|
+
@installed_skills.each do |skill|
|
|
265
|
+
puts " ✓ #{skill[:name]}"
|
|
266
|
+
puts " #{skill[:description]}"
|
|
267
|
+
puts " → #{skill[:path]}"
|
|
268
|
+
puts
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
if @errors.any?
|
|
272
|
+
puts "Warnings:"
|
|
273
|
+
@errors.each { |e| puts " • #{e}" }
|
|
274
|
+
puts
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
puts "You can now use these skills with /skill-name"
|
|
278
|
+
puts "=" * 60
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# ── Entry point ────────────────────────────────────────────────────────────────
|
|
283
|
+
if __FILE__ == $0
|
|
284
|
+
if ARGV.empty?
|
|
285
|
+
puts "Usage: ruby install_from_zip.rb <zip_url_or_path> [skill_name]"
|
|
286
|
+
puts "\nExamples:"
|
|
287
|
+
puts " ruby install_from_zip.rb https://example.com/my-skill-1.0.0.zip"
|
|
288
|
+
puts " ruby install_from_zip.rb https://example.com/my-skill-1.0.0.zip my-skill"
|
|
289
|
+
puts " ruby install_from_zip.rb /path/to/my-skill.zip"
|
|
290
|
+
puts " ruby install_from_zip.rb ~/Downloads/my-skill-1.0.0.zip my-skill"
|
|
291
|
+
exit 1
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
ZipSkillInstaller.new(ARGV[0], skill_name: ARGV[1]).install
|
|
295
|
+
end
|