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.
Files changed (319) hide show
  1. checksums.yaml +7 -0
  2. data/.clacky/skills/commit/SKILL.md +423 -0
  3. data/.clacky/skills/gem-release/SKILL.md +199 -0
  4. data/.clacky/skills/gem-release/scripts/release.sh +304 -0
  5. data/.clacky/skills/oss-upload/SKILL.md +47 -0
  6. data/.octorules +106 -0
  7. data/.rspec +3 -0
  8. data/.rubocop.yml +8 -0
  9. data/CHANGELOG.md +76 -0
  10. data/CODE_OF_CONDUCT.md +132 -0
  11. data/CONTRIBUTING.md +92 -0
  12. data/Dockerfile +28 -0
  13. data/LICENSE.txt +22 -0
  14. data/POSITIONING.md +46 -0
  15. data/README.md +134 -0
  16. data/README_CN.md +134 -0
  17. data/Rakefile +34 -0
  18. data/benchmark/fixtures/sample_project/Gemfile +3 -0
  19. data/benchmark/fixtures/sample_project/lib/api_handler.rb +32 -0
  20. data/benchmark/fixtures/sample_project/lib/order_calculator.rb +23 -0
  21. data/benchmark/fixtures/sample_project/lib/user_renderer.rb +20 -0
  22. data/benchmark/fixtures/sample_project/spec/order_calculator_spec.rb +20 -0
  23. data/benchmark/results/EVALUATION_REPORT.md +165 -0
  24. data/benchmark/results/baseline_20260511_174424.json +128 -0
  25. data/benchmark/results/report_20260511_175256.json +271 -0
  26. data/benchmark/results/report_20260511_175444.json +271 -0
  27. data/benchmark/results/treatment_20260511_175103.json +130 -0
  28. data/benchmark/runner.rb +441 -0
  29. data/bin/octo +7 -0
  30. data/docs/agent-first-ui-design.md +77 -0
  31. data/docs/billing-system.md +318 -0
  32. data/docs/channel-architecture.md +235 -0
  33. data/docs/engineering-article.md +343 -0
  34. data/docs/session-skill-invocation.md +69 -0
  35. data/docs/time_machine_design.md +247 -0
  36. data/docs/ui2-architecture.md +124 -0
  37. data/homebrew/README.md +96 -0
  38. data/homebrew/openocto.rb +24 -0
  39. data/lib/octo/agent/hook_manager.rb +61 -0
  40. data/lib/octo/agent/llm_caller.rb +800 -0
  41. data/lib/octo/agent/memory_updater.rb +246 -0
  42. data/lib/octo/agent/message_compressor.rb +225 -0
  43. data/lib/octo/agent/message_compressor_helper.rb +869 -0
  44. data/lib/octo/agent/next_message_suggester.rb +215 -0
  45. data/lib/octo/agent/session_serializer.rb +685 -0
  46. data/lib/octo/agent/skill_auto_creator.rb +114 -0
  47. data/lib/octo/agent/skill_evolution.rb +61 -0
  48. data/lib/octo/agent/skill_manager.rb +466 -0
  49. data/lib/octo/agent/skill_reflector.rb +89 -0
  50. data/lib/octo/agent/system_prompt_builder.rb +101 -0
  51. data/lib/octo/agent/time_machine.rb +214 -0
  52. data/lib/octo/agent/tool_executor.rb +454 -0
  53. data/lib/octo/agent/tool_registry.rb +150 -0
  54. data/lib/octo/agent.rb +2180 -0
  55. data/lib/octo/agent_config.rb +989 -0
  56. data/lib/octo/agent_profile.rb +112 -0
  57. data/lib/octo/anthropic_stream_aggregator.rb +137 -0
  58. data/lib/octo/background_task_registry.rb +324 -0
  59. data/lib/octo/banner.rb +34 -0
  60. data/lib/octo/bedrock_stream_aggregator.rb +137 -0
  61. data/lib/octo/block_font.rb +331 -0
  62. data/lib/octo/cli.rb +968 -0
  63. data/lib/octo/client.rb +623 -0
  64. data/lib/octo/default_agents/SOUL.md +3 -0
  65. data/lib/octo/default_agents/USER.md +1 -0
  66. data/lib/octo/default_agents/base_prompt.md +66 -0
  67. data/lib/octo/default_agents/coding/profile.yml +2 -0
  68. data/lib/octo/default_agents/coding/system_prompt.md +67 -0
  69. data/lib/octo/default_agents/general/profile.yml +2 -0
  70. data/lib/octo/default_agents/general/system_prompt.md +16 -0
  71. data/lib/octo/default_parsers/doc_parser.rb +69 -0
  72. data/lib/octo/default_parsers/docx_parser.rb +188 -0
  73. data/lib/octo/default_parsers/pdf_parser.rb +120 -0
  74. data/lib/octo/default_parsers/pdf_parser_ocr.py +103 -0
  75. data/lib/octo/default_parsers/pdf_parser_plumber.py +62 -0
  76. data/lib/octo/default_parsers/pptx_parser.rb +140 -0
  77. data/lib/octo/default_parsers/xlsx_parser.rb +121 -0
  78. data/lib/octo/default_skills/browser-setup/SKILL.md +426 -0
  79. data/lib/octo/default_skills/channel-manager/SKILL.md +623 -0
  80. data/lib/octo/default_skills/channel-manager/dingtalk_setup.rb +191 -0
  81. data/lib/octo/default_skills/channel-manager/discord_setup.rb +199 -0
  82. data/lib/octo/default_skills/channel-manager/feishu_setup.rb +574 -0
  83. data/lib/octo/default_skills/channel-manager/import_lark_skills.rb +97 -0
  84. data/lib/octo/default_skills/channel-manager/install_feishu_skills.rb +105 -0
  85. data/lib/octo/default_skills/channel-manager/weixin_setup.rb +274 -0
  86. data/lib/octo/default_skills/code-explorer/SKILL.md +36 -0
  87. data/lib/octo/default_skills/cron-task-creator/SKILL.md +257 -0
  88. data/lib/octo/default_skills/cron-task-creator/evals/evals.json +38 -0
  89. data/lib/octo/default_skills/onboard/SKILL.md +578 -0
  90. data/lib/octo/default_skills/onboard/scripts/import_external_skills.rb +413 -0
  91. data/lib/octo/default_skills/onboard/scripts/install_builtin_skills.rb +97 -0
  92. data/lib/octo/default_skills/persist-memory/SKILL.md +59 -0
  93. data/lib/octo/default_skills/personal-website/SKILL.md +113 -0
  94. data/lib/octo/default_skills/personal-website/publish.rb +235 -0
  95. data/lib/octo/default_skills/product-help/SKILL.md +123 -0
  96. data/lib/octo/default_skills/product-help/docs/agent-config.md +74 -0
  97. data/lib/octo/default_skills/product-help/docs/best-practices.md +49 -0
  98. data/lib/octo/default_skills/product-help/docs/browser-tool.md +53 -0
  99. data/lib/octo/default_skills/product-help/docs/built-in-skills.md +43 -0
  100. data/lib/octo/default_skills/product-help/docs/cli-reference.md +82 -0
  101. data/lib/octo/default_skills/product-help/docs/create-your-first-skill.md +47 -0
  102. data/lib/octo/default_skills/product-help/docs/faq.md +98 -0
  103. data/lib/octo/default_skills/product-help/docs/how-to-use-a-skill.md +58 -0
  104. data/lib/octo/default_skills/product-help/docs/installation.md +59 -0
  105. data/lib/octo/default_skills/product-help/docs/memory-system.md +61 -0
  106. data/lib/octo/default_skills/product-help/docs/octorules.md +62 -0
  107. data/lib/octo/default_skills/product-help/docs/session-management.md +63 -0
  108. data/lib/octo/default_skills/product-help/docs/skill-basics.md +55 -0
  109. data/lib/octo/default_skills/product-help/docs/skill-frontmatter.md +61 -0
  110. data/lib/octo/default_skills/product-help/docs/web-server.md +49 -0
  111. data/lib/octo/default_skills/product-help/docs/what-is-octo.md +37 -0
  112. data/lib/octo/default_skills/product-help/docs/windows-installation.md +36 -0
  113. data/lib/octo/default_skills/product-help/docs/writing-tips.md +53 -0
  114. data/lib/octo/default_skills/recall-memory/SKILL.md +65 -0
  115. data/lib/octo/default_skills/skill-add/SKILL.md +59 -0
  116. data/lib/octo/default_skills/skill-add/scripts/install_from_zip.rb +295 -0
  117. data/lib/octo/default_skills/skill-creator/SKILL.md +602 -0
  118. data/lib/octo/default_skills/skill-creator/agents/analyzer.md +274 -0
  119. data/lib/octo/default_skills/skill-creator/agents/comparator.md +202 -0
  120. data/lib/octo/default_skills/skill-creator/agents/grader.md +223 -0
  121. data/lib/octo/default_skills/skill-creator/eval-viewer/generate_review.py +471 -0
  122. data/lib/octo/default_skills/skill-creator/eval-viewer/viewer.html +1325 -0
  123. data/lib/octo/default_skills/skill-creator/references/schemas.md +430 -0
  124. data/lib/octo/default_skills/skill-creator/scripts/__init__.py +0 -0
  125. data/lib/octo/default_skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  126. data/lib/octo/default_skills/skill-creator/scripts/generate_report.py +326 -0
  127. data/lib/octo/default_skills/skill-creator/scripts/improve_description.py +310 -0
  128. data/lib/octo/default_skills/skill-creator/scripts/quick_validate.py +103 -0
  129. data/lib/octo/default_skills/skill-creator/scripts/run_eval.py +317 -0
  130. data/lib/octo/default_skills/skill-creator/scripts/run_loop.py +331 -0
  131. data/lib/octo/default_skills/skill-creator/scripts/utils.py +47 -0
  132. data/lib/octo/default_skills/skill-creator/scripts/validate_skill_frontmatter.rb +143 -0
  133. data/lib/octo/idle_compression_timer.rb +115 -0
  134. data/lib/octo/json_ui_controller.rb +204 -0
  135. data/lib/octo/message_format/anthropic.rb +409 -0
  136. data/lib/octo/message_format/bedrock.rb +361 -0
  137. data/lib/octo/message_format/open_ai.rb +222 -0
  138. data/lib/octo/message_history.rb +373 -0
  139. data/lib/octo/openai_stream_aggregator.rb +130 -0
  140. data/lib/octo/plain_ui_controller.rb +166 -0
  141. data/lib/octo/providers.rb +534 -0
  142. data/lib/octo/server/browser_manager.rb +397 -0
  143. data/lib/octo/server/channel/adapters/base.rb +82 -0
  144. data/lib/octo/server/channel/adapters/dingtalk/adapter.rb +314 -0
  145. data/lib/octo/server/channel/adapters/dingtalk/api_client.rb +391 -0
  146. data/lib/octo/server/channel/adapters/dingtalk/stream_client.rb +203 -0
  147. data/lib/octo/server/channel/adapters/discord/adapter.rb +229 -0
  148. data/lib/octo/server/channel/adapters/discord/api_client.rb +107 -0
  149. data/lib/octo/server/channel/adapters/discord/gateway_client.rb +270 -0
  150. data/lib/octo/server/channel/adapters/feishu/adapter.rb +320 -0
  151. data/lib/octo/server/channel/adapters/feishu/bot.rb +478 -0
  152. data/lib/octo/server/channel/adapters/feishu/file_processor.rb +36 -0
  153. data/lib/octo/server/channel/adapters/feishu/message_parser.rb +129 -0
  154. data/lib/octo/server/channel/adapters/feishu/ws_client.rb +423 -0
  155. data/lib/octo/server/channel/adapters/telegram/adapter.rb +375 -0
  156. data/lib/octo/server/channel/adapters/telegram/api_client.rb +205 -0
  157. data/lib/octo/server/channel/adapters/wecom/adapter.rb +148 -0
  158. data/lib/octo/server/channel/adapters/wecom/media_downloader.rb +115 -0
  159. data/lib/octo/server/channel/adapters/wecom/ws_client.rb +395 -0
  160. data/lib/octo/server/channel/adapters/weixin/adapter.rb +692 -0
  161. data/lib/octo/server/channel/adapters/weixin/api_client.rb +402 -0
  162. data/lib/octo/server/channel/channel_config.rb +178 -0
  163. data/lib/octo/server/channel/channel_manager.rb +468 -0
  164. data/lib/octo/server/channel/channel_ui_controller.rb +224 -0
  165. data/lib/octo/server/channel.rb +33 -0
  166. data/lib/octo/server/discover.rb +77 -0
  167. data/lib/octo/server/epipe_safe_io.rb +105 -0
  168. data/lib/octo/server/http_server.rb +3554 -0
  169. data/lib/octo/server/scheduler.rb +317 -0
  170. data/lib/octo/server/server_master.rb +325 -0
  171. data/lib/octo/server/session_registry.rb +431 -0
  172. data/lib/octo/server/web_ui_controller.rb +487 -0
  173. data/lib/octo/session_manager.rb +385 -0
  174. data/lib/octo/skill.rb +466 -0
  175. data/lib/octo/skill_loader.rb +328 -0
  176. data/lib/octo/tools/base.rb +118 -0
  177. data/lib/octo/tools/browser.rb +625 -0
  178. data/lib/octo/tools/edit.rb +165 -0
  179. data/lib/octo/tools/file_reader.rb +549 -0
  180. data/lib/octo/tools/glob.rb +162 -0
  181. data/lib/octo/tools/grep.rb +356 -0
  182. data/lib/octo/tools/invoke_skill.rb +96 -0
  183. data/lib/octo/tools/list_tasks.rb +54 -0
  184. data/lib/octo/tools/redo_task.rb +41 -0
  185. data/lib/octo/tools/request_user_feedback.rb +84 -0
  186. data/lib/octo/tools/security.rb +333 -0
  187. data/lib/octo/tools/terminal/output_cleaner.rb +63 -0
  188. data/lib/octo/tools/terminal/persistent_session.rb +268 -0
  189. data/lib/octo/tools/terminal/safe_rm.sh +106 -0
  190. data/lib/octo/tools/terminal/session_manager.rb +213 -0
  191. data/lib/octo/tools/terminal.rb +1828 -0
  192. data/lib/octo/tools/todo_manager.rb +374 -0
  193. data/lib/octo/tools/trash_manager.rb +388 -0
  194. data/lib/octo/tools/undo_task.rb +35 -0
  195. data/lib/octo/tools/web_fetch.rb +242 -0
  196. data/lib/octo/tools/web_search.rb +260 -0
  197. data/lib/octo/tools/write.rb +77 -0
  198. data/lib/octo/ui2/block_font.rb +10 -0
  199. data/lib/octo/ui2/components/base_component.rb +163 -0
  200. data/lib/octo/ui2/components/command_suggestions.rb +290 -0
  201. data/lib/octo/ui2/components/common_component.rb +96 -0
  202. data/lib/octo/ui2/components/inline_input.rb +226 -0
  203. data/lib/octo/ui2/components/input_area.rb +1338 -0
  204. data/lib/octo/ui2/components/message_component.rb +99 -0
  205. data/lib/octo/ui2/components/modal_component.rb +419 -0
  206. data/lib/octo/ui2/components/todo_area.rb +149 -0
  207. data/lib/octo/ui2/components/tool_component.rb +107 -0
  208. data/lib/octo/ui2/components/welcome_banner.rb +139 -0
  209. data/lib/octo/ui2/layout_manager.rb +807 -0
  210. data/lib/octo/ui2/line_editor.rb +363 -0
  211. data/lib/octo/ui2/markdown_renderer.rb +100 -0
  212. data/lib/octo/ui2/output_buffer.rb +370 -0
  213. data/lib/octo/ui2/progress_handle.rb +362 -0
  214. data/lib/octo/ui2/progress_indicator.rb +55 -0
  215. data/lib/octo/ui2/screen_buffer.rb +273 -0
  216. data/lib/octo/ui2/terminal_detector.rb +119 -0
  217. data/lib/octo/ui2/theme_manager.rb +85 -0
  218. data/lib/octo/ui2/themes/base_theme.rb +105 -0
  219. data/lib/octo/ui2/themes/hacker_theme.rb +62 -0
  220. data/lib/octo/ui2/themes/minimal_theme.rb +56 -0
  221. data/lib/octo/ui2/thinking_verbs.rb +26 -0
  222. data/lib/octo/ui2/ui_controller.rb +1625 -0
  223. data/lib/octo/ui2/view_renderer.rb +177 -0
  224. data/lib/octo/ui2.rb +40 -0
  225. data/lib/octo/ui_interface.rb +154 -0
  226. data/lib/octo/utils/arguments_parser.rb +191 -0
  227. data/lib/octo/utils/browser_detector.rb +195 -0
  228. data/lib/octo/utils/encoding.rb +92 -0
  229. data/lib/octo/utils/environment_detector.rb +140 -0
  230. data/lib/octo/utils/file_ignore_helper.rb +170 -0
  231. data/lib/octo/utils/file_processor.rb +601 -0
  232. data/lib/octo/utils/gitignore_parser.rb +154 -0
  233. data/lib/octo/utils/limit_stack.rb +152 -0
  234. data/lib/octo/utils/logger.rb +124 -0
  235. data/lib/octo/utils/login_shell.rb +72 -0
  236. data/lib/octo/utils/model_pricing.rb +646 -0
  237. data/lib/octo/utils/parser_manager.rb +165 -0
  238. data/lib/octo/utils/path_helper.rb +15 -0
  239. data/lib/octo/utils/scripts_manager.rb +59 -0
  240. data/lib/octo/utils/string_matcher.rb +158 -0
  241. data/lib/octo/utils/trash_directory.rb +112 -0
  242. data/lib/octo/utils/workspace_rules.rb +46 -0
  243. data/lib/octo/version.rb +5 -0
  244. data/lib/octo/web/app.css +7141 -0
  245. data/lib/octo/web/app.js +543 -0
  246. data/lib/octo/web/apple-touch-icon.png +0 -0
  247. data/lib/octo/web/auth.js +150 -0
  248. data/lib/octo/web/channels.js +276 -0
  249. data/lib/octo/web/datepicker.js +205 -0
  250. data/lib/octo/web/favicon.png +0 -0
  251. data/lib/octo/web/i18n.js +1073 -0
  252. data/lib/octo/web/icon-512.png +0 -0
  253. data/lib/octo/web/icon-dark.svg +25 -0
  254. data/lib/octo/web/icon.svg +29 -0
  255. data/lib/octo/web/index.html +871 -0
  256. data/lib/octo/web/marked.min.js +69 -0
  257. data/lib/octo/web/onboard.js +491 -0
  258. data/lib/octo/web/profile.js +442 -0
  259. data/lib/octo/web/sessions.js +4421 -0
  260. data/lib/octo/web/settings.js +913 -0
  261. data/lib/octo/web/sidebar.js +32 -0
  262. data/lib/octo/web/skills.js +885 -0
  263. data/lib/octo/web/tasks.js +297 -0
  264. data/lib/octo/web/theme.js +105 -0
  265. data/lib/octo/web/trash.js +343 -0
  266. data/lib/octo/web/vendor/hljs/highlight.min.js +1244 -0
  267. data/lib/octo/web/vendor/hljs/hljs-theme.css +95 -0
  268. data/lib/octo/web/vendor/katex/auto-render.min.js +1 -0
  269. data/lib/octo/web/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  270. data/lib/octo/web/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  271. data/lib/octo/web/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  272. data/lib/octo/web/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  273. data/lib/octo/web/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  274. data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  275. data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  276. data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  277. data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  278. data/lib/octo/web/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  279. data/lib/octo/web/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  280. data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  281. data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  282. data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  283. data/lib/octo/web/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  284. data/lib/octo/web/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  285. data/lib/octo/web/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  286. data/lib/octo/web/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  287. data/lib/octo/web/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  288. data/lib/octo/web/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  289. data/lib/octo/web/vendor/katex/katex.min.css +1 -0
  290. data/lib/octo/web/vendor/katex/katex.min.js +1 -0
  291. data/lib/octo/web/version.js +449 -0
  292. data/lib/octo/web/weixin-qr.html +209 -0
  293. data/lib/octo/web/ws-dispatcher.js +357 -0
  294. data/lib/octo/web/ws.js +128 -0
  295. data/lib/octo.rb +145 -0
  296. data/scripts/build/build.sh +329 -0
  297. data/scripts/build/lib/apt.sh +56 -0
  298. data/scripts/build/lib/brew.sh +89 -0
  299. data/scripts/build/lib/colors.sh +17 -0
  300. data/scripts/build/lib/gem.sh +95 -0
  301. data/scripts/build/lib/mise.sh +125 -0
  302. data/scripts/build/lib/network.sh +157 -0
  303. data/scripts/build/lib/os.sh +57 -0
  304. data/scripts/build/lib/shell.sh +37 -0
  305. data/scripts/build/src/install.sh.cc +174 -0
  306. data/scripts/build/src/install_browser.sh.cc +101 -0
  307. data/scripts/build/src/install_full.sh.cc +290 -0
  308. data/scripts/build/src/install_rails_deps.sh.cc +145 -0
  309. data/scripts/build/src/install_system_deps.sh.cc +123 -0
  310. data/scripts/build/src/uninstall.sh.cc +101 -0
  311. data/scripts/install.ps1 +532 -0
  312. data/scripts/install.sh +567 -0
  313. data/scripts/install_browser.sh +479 -0
  314. data/scripts/install_full.sh +838 -0
  315. data/scripts/install_rails_deps.sh +746 -0
  316. data/scripts/install_system_deps.sh +518 -0
  317. data/scripts/uninstall.sh +287 -0
  318. data/sig/octo.rbs +4 -0
  319. 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