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,479 @@
1
+ #!/bin/bash
2
+ # install_browser.sh — install Node.js + chrome-devtools-mcp for browser automation
3
+ # Generated from scripts/build/src/install_browser.sh.cc — DO NOT EDIT DIRECTLY
4
+
5
+ set -e
6
+
7
+
8
+ # ---[ @include lib/colors.sh ]---
9
+
10
+ RED='\033[0;31m'
11
+ GREEN='\033[0;32m'
12
+ YELLOW='\033[1;33m'
13
+ BLUE='\033[0;34m'
14
+ NC='\033[0m'
15
+
16
+ print_info() { echo -e "${BLUE}ℹ${NC} $1"; }
17
+ print_success() { echo -e "${GREEN}✓${NC} $1"; }
18
+ print_warning() { echo -e "${YELLOW}⚠${NC} $1"; }
19
+ print_error() { echo -e "${RED}✗${NC} $1"; }
20
+ print_step() { echo -e "\n${BLUE}==>${NC} $1"; }
21
+
22
+
23
+ # ---[ @include lib/os.sh ]---
24
+
25
+ # Sets OS (macOS | Linux | Windows | Unknown) and DISTRO (ubuntu | debian | …)
26
+ detect_os() {
27
+ case "$(uname -s)" in
28
+ Linux*) OS=Linux ;;
29
+ Darwin*) OS=macOS ;;
30
+ CYGWIN*) OS=Windows ;;
31
+ MINGW*) OS=Windows ;;
32
+ *) OS=Unknown ;;
33
+ esac
34
+ print_info "Detected OS: $OS"
35
+
36
+ if [ "$OS" = "Linux" ] && [ -f /etc/os-release ]; then
37
+ # shellcheck source=/dev/null
38
+ . /etc/os-release
39
+ DISTRO=$ID
40
+ print_info "Detected Linux distribution: $DISTRO"
41
+ else
42
+ DISTRO=unknown
43
+ fi
44
+ }
45
+
46
+ # Returns 0 if the given command is on PATH
47
+ command_exists() { command -v "$1" >/dev/null 2>&1; }
48
+
49
+ # Boolean helpers — use these in business logic instead of inline string comparisons
50
+ is_macos() { [ "$OS" = "macOS" ]; }
51
+ is_linux_apt() { [ "$OS" = "Linux" ] && { [ "$DISTRO" = "ubuntu" ] || [ "$DISTRO" = "debian" ]; }; }
52
+
53
+ # Returns 0 (true) if $1 >= $2 (semantic version comparison)
54
+ version_ge() { printf '%s\n%s\n' "$2" "$1" | sort -V -C; }
55
+
56
+ # Assert that the current OS/distro is supported (macOS or Ubuntu/Debian).
57
+ # Optional $1: hint message printed on failure (e.g. manual install instructions).
58
+ # Exits with code 1 on unsupported OS or distro.
59
+ assert_supported_os() {
60
+ local hint="${1:-}"
61
+ if [ "$OS" = "Linux" ]; then
62
+ if [ "$DISTRO" = "ubuntu" ] || [ "$DISTRO" = "debian" ]; then
63
+ return 0
64
+ fi
65
+ print_error "Unsupported Linux distribution: $DISTRO"
66
+ [ -n "$hint" ] && print_info "$hint"
67
+ exit 1
68
+ elif [ "$OS" = "macOS" ]; then
69
+ return 0
70
+ else
71
+ print_error "Unsupported OS: $OS"
72
+ [ -n "$hint" ] && print_info "$hint"
73
+ exit 1
74
+ fi
75
+ }
76
+
77
+
78
+ # ---[ @include lib/shell.sh ]---
79
+
80
+ # Sets CURRENT_SHELL (zsh | bash | fish) and SHELL_RC (path to rc file)
81
+ detect_shell() {
82
+ local shell_name
83
+ shell_name=$(basename "$SHELL")
84
+
85
+ case "$shell_name" in
86
+ zsh)
87
+ CURRENT_SHELL="zsh"
88
+ SHELL_RC="$HOME/.zshrc"
89
+ ;;
90
+ fish)
91
+ CURRENT_SHELL="fish"
92
+ SHELL_RC="$HOME/.config/fish/config.fish"
93
+ ;;
94
+ bash)
95
+ CURRENT_SHELL="bash"
96
+ # macOS uses ~/.bash_profile; Linux uses ~/.bashrc
97
+ if is_macos; then
98
+ SHELL_RC="$HOME/.bash_profile"
99
+ else
100
+ SHELL_RC="$HOME/.bashrc"
101
+ fi
102
+ ;;
103
+ *)
104
+ CURRENT_SHELL="bash"
105
+ SHELL_RC="$HOME/.bashrc"
106
+ ;;
107
+ esac
108
+
109
+ print_info "Detected shell: $CURRENT_SHELL (rc file: $SHELL_RC)"
110
+ }
111
+
112
+
113
+ # ---[ @include lib/network.sh ]---
114
+
115
+ # --------------------------------------------------------------------------
116
+ # Mirror variables — overridden by detect_network_region()
117
+ # --------------------------------------------------------------------------
118
+ SLOW_THRESHOLD_MS=5000
119
+ NETWORK_REGION="global" # china | global | unknown
120
+ USE_CN_MIRRORS=false
121
+
122
+ GITHUB_RAW_BASE_URL="https://raw.githubusercontent.com"
123
+ DEFAULT_RUBYGEMS_URL="https://rubygems.org"
124
+ DEFAULT_NPM_REGISTRY="https://registry.npmjs.org"
125
+ DEFAULT_MISE_INSTALL_URL="https://mise.run"
126
+
127
+ CN_CDN_BASE_URL="https://oss.1024code.com"
128
+ CN_ALIYUN_MIRROR="https://mirrors.aliyun.com"
129
+ CN_MISE_INSTALL_URL="${CN_CDN_BASE_URL}/mise.sh"
130
+ CN_RUBY_PRECOMPILED_URL="${CN_CDN_BASE_URL}/ruby/ruby-{version}.{platform}.tar.gz"
131
+ CN_RUBYGEMS_URL="${CN_ALIYUN_MIRROR}/rubygems/"
132
+ CN_NPM_REGISTRY="https://registry.npmmirror.com"
133
+ CN_NODE_MIRROR_URL="https://cdn.npmmirror.com/binaries/node/"
134
+ CN_GEM_BASE_URL="${CN_CDN_BASE_URL}/octo"
135
+ CN_GEM_LATEST_URL="${CN_GEM_BASE_URL}/latest.txt"
136
+
137
+ # Active values (set by detect_network_region)
138
+ MISE_INSTALL_URL="$DEFAULT_MISE_INSTALL_URL"
139
+ RUBYGEMS_INSTALL_URL="$DEFAULT_RUBYGEMS_URL"
140
+ NPM_REGISTRY_URL="$DEFAULT_NPM_REGISTRY"
141
+ NODE_MIRROR_URL="" # empty = mise default (nodejs.org)
142
+ RUBY_VERSION_SPEC="ruby@3" # CN mode pins to a specific precompiled build
143
+
144
+ # --------------------------------------------------------------------------
145
+ # Internal probe helpers
146
+ # --------------------------------------------------------------------------
147
+
148
+ # Probe a single URL; echoes round-trip time in ms, or "timeout"
149
+ _probe_url() {
150
+ local url="$1"
151
+ local out http_code total_time
152
+ out=$(curl -s -o /dev/null -w "%{http_code} %{time_total}" \
153
+ --connect-timeout 5 --max-time 5 "$url" 2>/dev/null) || true
154
+ http_code="${out%% *}"
155
+ total_time="${out#* }"
156
+ if [ -z "$http_code" ] || [ "$http_code" = "000" ] || [ "$http_code" = "$out" ]; then
157
+ echo "timeout"; return
158
+ fi
159
+ awk -v s="$total_time" 'BEGIN { printf "%d", s * 1000 }'
160
+ }
161
+
162
+ # Returns 0 (true) if result is slow or unreachable
163
+ _is_slow_or_unreachable() {
164
+ local r="$1"
165
+ [ "$r" = "timeout" ] && return 0
166
+ [ "${r:-9999}" -ge "$SLOW_THRESHOLD_MS" ] 2>/dev/null
167
+ }
168
+
169
+ _format_probe_time() {
170
+ local r="$1"
171
+ [ "$r" = "timeout" ] && echo "timeout" && return
172
+ awk -v ms="$r" 'BEGIN { printf "%.1fs", ms / 1000 }'
173
+ }
174
+
175
+ _print_probe_result() {
176
+ local label="$1" result="$2"
177
+ if [ "$result" = "timeout" ]; then
178
+ print_warning "UNREACHABLE ${label}"
179
+ elif _is_slow_or_unreachable "$result"; then
180
+ print_warning "SLOW ($(_format_probe_time "$result")) ${label}"
181
+ else
182
+ print_success "OK ($(_format_probe_time "$result")) ${label}"
183
+ fi
184
+ }
185
+
186
+ # Probe URL up to max_retries times; returns first fast result or last result
187
+ _probe_url_with_retry() {
188
+ local url="$1" max="${2:-2}" result
189
+ for _ in $(seq 1 "$max"); do
190
+ result=$(_probe_url "$url")
191
+ ! _is_slow_or_unreachable "$result" && { echo "$result"; return 0; }
192
+ done
193
+ echo "$result"
194
+ }
195
+
196
+ # --------------------------------------------------------------------------
197
+ # detect_network_region — sets USE_CN_MIRRORS and active mirror variables
198
+ # --------------------------------------------------------------------------
199
+ detect_network_region() {
200
+ print_step "Network pre-flight check..."
201
+ echo ""
202
+
203
+ local google_result baidu_result
204
+ google_result=$(_probe_url "https://www.google.com")
205
+ baidu_result=$(_probe_url "https://www.baidu.com")
206
+
207
+ _print_probe_result "google.com" "$google_result"
208
+ _print_probe_result "baidu.com" "$baidu_result"
209
+
210
+ local google_ok=false baidu_ok=false
211
+ ! _is_slow_or_unreachable "$google_result" && google_ok=true
212
+ ! _is_slow_or_unreachable "$baidu_result" && baidu_ok=true
213
+
214
+ if [ "$google_ok" = true ]; then
215
+ NETWORK_REGION="global"
216
+ print_success "Region: global"
217
+ elif [ "$baidu_ok" = true ]; then
218
+ NETWORK_REGION="china"
219
+ print_success "Region: china"
220
+ else
221
+ NETWORK_REGION="unknown"
222
+ print_warning "Region: unknown (both unreachable)"
223
+ fi
224
+ echo ""
225
+
226
+ if [ "$NETWORK_REGION" = "china" ]; then
227
+ local cdn_result mirror_result
228
+ cdn_result=$(_probe_url_with_retry "$CN_MISE_INSTALL_URL")
229
+ mirror_result=$(_probe_url_with_retry "$CN_RUBYGEMS_URL")
230
+
231
+ _print_probe_result "CN CDN (mise/Ruby)" "$cdn_result"
232
+ _print_probe_result "Aliyun (gem)" "$mirror_result"
233
+
234
+ local cdn_ok=false mirror_ok=false
235
+ ! _is_slow_or_unreachable "$cdn_result" && cdn_ok=true
236
+ ! _is_slow_or_unreachable "$mirror_result" && mirror_ok=true
237
+
238
+ if [ "$cdn_ok" = true ] || [ "$mirror_ok" = true ]; then
239
+ USE_CN_MIRRORS=true
240
+ MISE_INSTALL_URL="$CN_MISE_INSTALL_URL"
241
+ RUBYGEMS_INSTALL_URL="$CN_RUBYGEMS_URL"
242
+ NPM_REGISTRY_URL="$CN_NPM_REGISTRY"
243
+ NODE_MIRROR_URL="$CN_NODE_MIRROR_URL"
244
+ RUBY_VERSION_SPEC="ruby@3.4.8"
245
+ print_info "CN mirrors applied"
246
+ else
247
+ print_warning "CN mirrors unreachable — falling back to global sources"
248
+ fi
249
+ else
250
+ local rubygems_result mise_result
251
+ rubygems_result=$(_probe_url_with_retry "$DEFAULT_RUBYGEMS_URL")
252
+ mise_result=$(_probe_url_with_retry "$DEFAULT_MISE_INSTALL_URL")
253
+
254
+ _print_probe_result "RubyGems" "$rubygems_result"
255
+ _print_probe_result "mise.run" "$mise_result"
256
+
257
+ _is_slow_or_unreachable "$rubygems_result" && print_warning "RubyGems is slow/unreachable."
258
+ _is_slow_or_unreachable "$mise_result" && print_warning "mise.run is slow/unreachable."
259
+
260
+ USE_CN_MIRRORS=false
261
+ RUBY_VERSION_SPEC="ruby@3"
262
+ fi
263
+
264
+ echo ""
265
+ }
266
+
267
+
268
+ # ---[ @include lib/mise.sh ]---
269
+
270
+ # --------------------------------------------------------------------------
271
+ # Internal helper — locate the mise binary
272
+ # --------------------------------------------------------------------------
273
+ _mise_bin() {
274
+ if [ -x "$HOME/.local/bin/mise" ]; then
275
+ echo "$HOME/.local/bin/mise"
276
+ elif command_exists mise; then
277
+ command -v mise
278
+ else
279
+ echo ""
280
+ fi
281
+ }
282
+
283
+ # --------------------------------------------------------------------------
284
+ # ensure_mise — install mise if missing, activate it in the current session
285
+ # --------------------------------------------------------------------------
286
+ ensure_mise() {
287
+ local mise
288
+ mise=$(_mise_bin)
289
+
290
+ if [ -n "$mise" ]; then
291
+ print_success "mise already installed: $($mise --version 2>/dev/null || echo 'n/a')"
292
+ export PATH="$HOME/.local/bin:$PATH"
293
+ eval "$($mise activate bash 2>/dev/null)" 2>/dev/null || true
294
+ MISE_BIN="$mise"
295
+ return 0
296
+ fi
297
+
298
+ print_step "Installing mise..."
299
+ if curl -fsSL "$MISE_INSTALL_URL" | sh; then
300
+ export PATH="$HOME/.local/bin:$PATH"
301
+ eval "$(~/.local/bin/mise activate bash 2>/dev/null)" 2>/dev/null || true
302
+
303
+ # Persist mise activation to shell rc
304
+ local init_line='eval "$(~/.local/bin/mise activate '"$CURRENT_SHELL"')"'
305
+ if ! grep -q "mise activate" "$SHELL_RC" 2>/dev/null; then
306
+ echo "$init_line" >> "$SHELL_RC"
307
+ print_info "Added mise activation to $SHELL_RC"
308
+ fi
309
+
310
+ MISE_BIN="$HOME/.local/bin/mise"
311
+ print_success "mise installed"
312
+ else
313
+ print_error "Failed to install mise"
314
+ return 1
315
+ fi
316
+ }
317
+
318
+ # --------------------------------------------------------------------------
319
+ # install_ruby_via_mise — install Ruby via mise
320
+ # CN mode: precompiled binary from oss.1024code.com
321
+ # Global: mise default (precompiled where available)
322
+ # --------------------------------------------------------------------------
323
+ install_ruby_via_mise() {
324
+ local mise="${MISE_BIN:-$(_mise_bin)}"
325
+ if [ -z "$mise" ]; then
326
+ print_error "mise not found — call ensure_mise first"
327
+ return 1
328
+ fi
329
+
330
+ print_info "Installing Ruby via mise ($RUBY_VERSION_SPEC)..."
331
+
332
+ if [ "$USE_CN_MIRRORS" = true ]; then
333
+ "$mise" settings ruby.compile=false 2>/dev/null || true
334
+ "$mise" settings ruby.precompiled_url="$CN_RUBY_PRECOMPILED_URL" 2>/dev/null || true
335
+ print_info "Using precompiled Ruby from CN CDN"
336
+ else
337
+ # Enable precompiled binaries globally (mise supports this on common platforms)
338
+ "$mise" settings ruby.compile=false 2>/dev/null || true
339
+ "$mise" settings unset ruby.precompiled_url 2>/dev/null || true
340
+ fi
341
+
342
+ if "$mise" use -g "$RUBY_VERSION_SPEC"; then
343
+ eval "$($mise activate bash 2>/dev/null)" 2>/dev/null || true
344
+ local installed
345
+ installed=$(ruby -e 'puts RUBY_VERSION' 2>/dev/null || echo "unknown")
346
+ print_success "Ruby $installed installed"
347
+ else
348
+ print_error "Failed to install Ruby via mise"
349
+ return 1
350
+ fi
351
+ }
352
+
353
+ # --------------------------------------------------------------------------
354
+ # install_node_via_mise — install Node.js 22 via mise
355
+ # --------------------------------------------------------------------------
356
+ install_node_via_mise() {
357
+ local mise="${MISE_BIN:-$(_mise_bin)}"
358
+ if [ -z "$mise" ]; then
359
+ print_error "mise not found — call ensure_mise first"
360
+ return 1
361
+ fi
362
+
363
+ # Skip if compatible Node is already active
364
+ if command_exists node; then
365
+ local ver major
366
+ ver=$(node --version 2>/dev/null | sed 's/v//')
367
+ major="${ver%%.*}"
368
+ if [ "${major:-0}" -ge 22 ] 2>/dev/null; then
369
+ print_success "Node.js v${ver} already satisfies >= 22 — skipping"
370
+ return 0
371
+ fi
372
+ fi
373
+
374
+ # Apply CN Node mirror if needed
375
+ if [ "$USE_CN_MIRRORS" = true ] && [ -n "$NODE_MIRROR_URL" ]; then
376
+ "$mise" settings node.mirror_url="$NODE_MIRROR_URL" 2>/dev/null || true
377
+ print_info "Node mirror → ${NODE_MIRROR_URL}"
378
+ fi
379
+
380
+ print_info "Installing Node.js 22 via mise..."
381
+ if "$mise" use -g node@22; then
382
+ eval "$($mise activate bash 2>/dev/null)" 2>/dev/null || true
383
+ print_success "Node.js $(node --version 2>/dev/null) installed"
384
+ else
385
+ print_error "Failed to install Node.js via mise"
386
+ return 1
387
+ fi
388
+ }
389
+
390
+
391
+ # Chrome DMG — update CHROME_VERSION when re-uploading a newer build to OSS
392
+ CHROME_VERSION="134"
393
+ DEFAULT_CHROME_DMG_URL="https://dl.google.com/chrome/mac/universal/stable/GGRO/googlechrome.dmg"
394
+ CN_CHROME_DMG_URL="${CN_CDN_BASE_URL}/browsers/googlechrome-mac-${CHROME_VERSION}.dmg"
395
+
396
+ # --------------------------------------------------------------------------
397
+ # Ensure Chrome is installed (macOS only)
398
+ # Downloads DMG to Desktop, opens it, then exits with instructions.
399
+ # Re-run the script after Chrome is installed.
400
+ # --------------------------------------------------------------------------
401
+ ensure_chrome_macos() {
402
+ print_step "Checking Google Chrome..."
403
+
404
+ if [ -d "/Applications/Google Chrome.app" ] || [ -d "$HOME/Applications/Google Chrome.app" ]; then
405
+ print_success "Google Chrome already installed"
406
+ return 0
407
+ fi
408
+
409
+ print_warning "Google Chrome not found — downloading..."
410
+ local dmg_url="$DEFAULT_CHROME_DMG_URL"
411
+ if [ "$USE_CN_MIRRORS" = true ]; then
412
+ dmg_url="$CN_CHROME_DMG_URL"
413
+ print_info "Using OSS mirror (Chrome ${CHROME_VERSION})"
414
+ else
415
+ print_info "Using official Google download"
416
+ fi
417
+
418
+ local dmg_path="$HOME/Desktop/googlechrome.dmg"
419
+ print_info "Downloading Chrome (~238 MB) to Desktop..."
420
+ curl -L --progress-bar "$dmg_url" -o "$dmg_path" || {
421
+ print_error "Download failed"
422
+ print_info "Please download manually: ${dmg_url}"
423
+ exit 1
424
+ }
425
+
426
+ print_success "Downloaded: ${dmg_path}"
427
+ print_info "Opening DMG installer..."
428
+ open "$dmg_path"
429
+ echo ""
430
+ print_info "Drag 'Google Chrome' to Applications, then re-run this script."
431
+ exit 0
432
+ }
433
+
434
+ # --------------------------------------------------------------------------
435
+ # install_chrome_devtools_mcp — npm global install
436
+ # --------------------------------------------------------------------------
437
+ install_chrome_devtools_mcp() {
438
+ print_step "Installing chrome-devtools-mcp..."
439
+
440
+ if [ "$USE_CN_MIRRORS" = true ]; then
441
+ npm config set registry "$NPM_REGISTRY_URL" 2>/dev/null || true
442
+ print_info "npm registry → ${NPM_REGISTRY_URL}"
443
+ fi
444
+
445
+ if npm install -g chrome-devtools-mcp@latest 2>/dev/null; then
446
+ print_success "chrome-devtools-mcp installed"
447
+ else
448
+ print_error "chrome-devtools-mcp installation failed"
449
+ print_info "Try manually: npm install -g chrome-devtools-mcp@latest"
450
+ return 1
451
+ fi
452
+ }
453
+
454
+ # --------------------------------------------------------------------------
455
+ # Main
456
+ # --------------------------------------------------------------------------
457
+ main() {
458
+ echo ""
459
+ echo "Browser Automation Setup"
460
+ echo "========================"
461
+
462
+ detect_os
463
+ detect_shell
464
+ detect_network_region
465
+
466
+ is_macos && ensure_chrome_macos
467
+
468
+ print_step "Checking Node.js..."
469
+ ensure_mise || exit 1
470
+ install_node_via_mise || exit 1
471
+
472
+ install_chrome_devtools_mcp || exit 1
473
+
474
+ echo ""
475
+ print_success "Done. Browser automation is ready."
476
+ echo ""
477
+ }
478
+
479
+ main "$@"