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,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Octo
4
+ module UI2
5
+ # TerminalDetector - Detect terminal background color before UI starts
6
+ class TerminalDetector
7
+ # Detect if terminal has dark background
8
+ # Uses multiple strategies to determine background color
9
+ # @return [Boolean] true if dark background, false if light background
10
+ def self.detect_dark_background
11
+ # Strategy 1: Check $COLORFGBG environment variable (fast, set by some terminals)
12
+ if ENV.key?('COLORFGBG')
13
+ # Format is like "15;0" where second number is background ANSI code
14
+ # 0-7 are dark, 8-15 are light
15
+ parts = ENV['COLORFGBG'].split(';')
16
+ if parts.size >= 2
17
+ bg_code = parts.last.to_i
18
+ if bg_code >= 0 && bg_code <= 15
19
+ return bg_code < 8
20
+ end
21
+ end
22
+ end
23
+
24
+ # Strategy 2: Query terminal background using OSC 11 sequence
25
+ begin
26
+ rgb = query_terminal_background_color
27
+ if rgb
28
+ # Calculate luma (perceived brightness): 0.0 (black) to 1.0 (white)
29
+ # Formula: 0.299*R + 0.587*G + 0.114*B
30
+ luma = (0.299 * rgb[:r] + 0.587 * rgb[:g] + 0.114 * rgb[:b]) / 255.0
31
+ return luma < 0.5
32
+ end
33
+ rescue => e
34
+ # Silently fall through to default
35
+ end
36
+
37
+ # Default: assume dark background (most common for terminals)
38
+ true
39
+ end
40
+
41
+ # Query terminal background color using OSC 11 sequence
42
+ # This should be called BEFORE UI starts to avoid interference
43
+ # @return [Hash, nil] RGB hash like {r: 26, g: 43, b: 60} or nil if failed
44
+ def self.query_terminal_background_color
45
+ require 'io/console'
46
+
47
+ # Only works on TTY
48
+ return nil unless $stdin.tty?
49
+ return nil unless $stdout.tty?
50
+
51
+ old_state = nil
52
+ begin
53
+ # Save current terminal state
54
+ old_state = $stdin.raw!
55
+
56
+ # Clear any pending input first
57
+ while IO.select([$stdin], nil, nil, 0)
58
+ $stdin.read_nonblock(1000) rescue break
59
+ end
60
+
61
+ # Send OSC 11 query: ESC ] 11 ; ? ST
62
+ # Use ST terminator (ESC \) instead of BEL for better compatibility
63
+ $stdout.print "\e]11;?\e\\\\"
64
+ $stdout.flush
65
+
66
+ # Read response with timeout (terminal should respond quickly)
67
+ response = String.new # Use String.new to create mutable string
68
+ timeout = 0.1 # 100ms timeout
69
+ start_time = Time.now
70
+
71
+ while Time.now - start_time < timeout
72
+ if IO.select([$stdin], nil, nil, 0.01)
73
+ char = $stdin.read_nonblock(1) rescue nil
74
+ break unless char
75
+ response << char
76
+
77
+ # Look for complete response pattern
78
+ # Response format: ESC ] 11 ; rgb:RRRR/GGGG/BBBB BEL or ST
79
+ if response.match?(/\e\]11;rgb:[0-9a-fA-F]+\/[0-9a-fA-F]+\/[0-9a-fA-F]+(\e\\|\a)/)
80
+ break
81
+ end
82
+
83
+ # Safety: stop if response gets too long (probably garbage)
84
+ break if response.length > 100
85
+ end
86
+ end
87
+
88
+ # Parse response: look for rgb:RRRR/GGGG/BBBB
89
+ # Example: ]11;rgb:1a2b/3c4d/5e6f or ]11;rgb:ffff/ffff/ffff
90
+ if response =~ /rgb:([0-9a-fA-F]+)\/([0-9a-fA-F]+)\/([0-9a-fA-F]+)/
91
+ r_hex, g_hex, b_hex = $1, $2, $3
92
+ # Take first 2 hex digits (terminals may return 4 or 2 hex digits per channel)
93
+ r = r_hex[0, 2].to_i(16)
94
+ g = g_hex[0, 2].to_i(16)
95
+ b = b_hex[0, 2].to_i(16)
96
+ return { r: r, g: g, b: b }
97
+ end
98
+
99
+ nil
100
+ rescue => e
101
+ # If anything goes wrong, return nil to fall back to default
102
+ nil
103
+ ensure
104
+ # Make sure we restore terminal state even if error occurs
105
+ old_state.restore if old_state rescue nil
106
+
107
+ # Clear any remaining input to prevent leakage
108
+ begin
109
+ while IO.select([$stdin], nil, nil, 0)
110
+ $stdin.read_nonblock(1000) rescue break
111
+ end
112
+ rescue
113
+ # Ignore cleanup errors
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "themes/base_theme"
4
+ require_relative "themes/hacker_theme"
5
+ require_relative "themes/minimal_theme"
6
+
7
+ module Octo
8
+ module UI2
9
+ # ThemeManager handles theme registration and switching
10
+ class ThemeManager
11
+ class << self
12
+ def instance
13
+ @instance ||= new
14
+ end
15
+
16
+ # Delegate methods to instance
17
+ def current_theme
18
+ instance.current_theme
19
+ end
20
+
21
+ def set_theme(name)
22
+ instance.set_theme(name)
23
+ end
24
+
25
+ def available_themes
26
+ instance.available_themes
27
+ end
28
+
29
+ def register_theme(name, theme_class)
30
+ instance.register_theme(name, theme_class)
31
+ end
32
+ end
33
+
34
+ def initialize
35
+ @themes = {}
36
+ @current_theme = nil
37
+ @is_dark_background = nil # Store detected background mode
38
+ register_default_themes
39
+ set_theme(:hacker)
40
+ end
41
+
42
+ # Set the detected terminal background mode
43
+ # This should be called BEFORE UI starts (from CLI)
44
+ # @param is_dark [Boolean] true if dark background, false if light
45
+ def set_background_mode(is_dark)
46
+ @is_dark_background = is_dark
47
+ # Pass to current theme if already initialized
48
+ @current_theme&.set_background_mode(is_dark)
49
+ end
50
+
51
+ # Get the detected background mode
52
+ # @return [Boolean, nil] true if dark, false if light, nil if not detected
53
+ def dark_background?
54
+ @is_dark_background
55
+ end
56
+
57
+ def current_theme
58
+ @current_theme
59
+ end
60
+
61
+ def set_theme(name)
62
+ name = name.to_sym
63
+ raise ArgumentError, "Unknown theme: #{name}" unless @themes.key?(name)
64
+
65
+ @current_theme = @themes[name].new
66
+ # Pass background mode to new theme if already detected
67
+ @current_theme.set_background_mode(@is_dark_background) unless @is_dark_background.nil?
68
+ end
69
+
70
+ def available_themes
71
+ @themes.keys
72
+ end
73
+
74
+ def register_theme(name, theme_class)
75
+ @themes[name.to_sym] = theme_class
76
+ end
77
+
78
+
79
+ def register_default_themes
80
+ register_theme(:hacker, Themes::HackerTheme)
81
+ register_theme(:minimal, Themes::MinimalTheme)
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pastel"
4
+
5
+ module Octo
6
+ module UI2
7
+ module Themes
8
+ # BaseTheme defines the abstract interface for all themes
9
+ # Subclasses MUST define SYMBOLS and COLORS constants
10
+ class BaseTheme
11
+ def initialize
12
+ @pastel = Pastel.new
13
+ @is_dark_background = nil # Will be set by ThemeManager
14
+ validate_theme_definition!
15
+ end
16
+
17
+ # Set background mode (called by ThemeManager after detection)
18
+ # @param is_dark [Boolean] true if dark background, false if light
19
+ def set_background_mode(is_dark)
20
+ @is_dark_background = is_dark
21
+ end
22
+
23
+ # Get all symbols defined by this theme
24
+ # @return [Hash] Symbol definitions
25
+ def symbols
26
+ self.class::SYMBOLS
27
+ end
28
+
29
+ # Get all colors defined by this theme
30
+ # @return [Hash] Color definitions
31
+ def colors
32
+ self.class::COLORS
33
+ end
34
+
35
+ # Get symbol for a specific key
36
+ # @param key [Symbol] Symbol key
37
+ # @return [String] Symbol string
38
+ def symbol(key)
39
+ symbols[key] || "[??]"
40
+ end
41
+
42
+ # Get symbol color for a specific key
43
+ # @param key [Symbol] Color key
44
+ # @return [Symbol] Pastel color method name
45
+ def symbol_color(key)
46
+ colors.dig(key, 0) || :white
47
+ end
48
+
49
+ # Get text color for a specific key
50
+ # Automatically selects appropriate color based on terminal background
51
+ # Color format: [symbol_color, dark_bg_text_color, light_bg_text_color]
52
+ # @param key [Symbol] Color key
53
+ # @return [Symbol] Pastel color method name
54
+ def text_color(key)
55
+ color_def = colors[key]
56
+ return :white unless color_def
57
+
58
+ # Use index 1 for dark background, index 2 for light background
59
+ dark_background? ? color_def[1] : color_def[2]
60
+ end
61
+
62
+ # Format symbol with its color
63
+ # @param key [Symbol] Symbol key (e.g., :user, :assistant)
64
+ # @return [String] Colored symbol
65
+ def format_symbol(key)
66
+ @pastel.public_send(symbol_color(key), symbol(key))
67
+ end
68
+
69
+ # Format text with color for given key
70
+ # @param text [String] Text to format
71
+ # @param key [Symbol] Color key (e.g., :user, :assistant)
72
+ # @return [String] Colored text
73
+ def format_text(text, key)
74
+ @pastel.public_send(text_color(key), text)
75
+ end
76
+
77
+ # Theme name for display (subclasses should override)
78
+ # @return [String] Theme name
79
+ def name
80
+ raise NotImplementedError, "Subclass must implement #name method"
81
+ end
82
+
83
+ # Check if terminal has dark background
84
+ # Uses pre-detected value from ThemeManager, or defaults to true
85
+ # @return [Boolean] true if dark background, false if light background
86
+ def dark_background?
87
+ # Use pre-detected value if available, otherwise default to dark
88
+ @is_dark_background.nil? ? true : @is_dark_background
89
+ end
90
+
91
+
92
+ # Validate that subclass has defined required constants
93
+ def validate_theme_definition!
94
+ unless self.class.const_defined?(:SYMBOLS)
95
+ raise NotImplementedError, "Theme #{self.class.name} must define SYMBOLS constant"
96
+ end
97
+
98
+ unless self.class.const_defined?(:COLORS)
99
+ raise NotImplementedError, "Theme #{self.class.name} must define COLORS constant"
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_theme"
4
+
5
+ module Octo
6
+ module UI2
7
+ module Themes
8
+ # HackerTheme - Matrix/hacker-style with bracket symbols
9
+ class HackerTheme < BaseTheme
10
+ SYMBOLS = {
11
+ user: "[>>]",
12
+ assistant: "[<<]",
13
+ tool_call: "[=>]",
14
+ tool_result: "[<=]",
15
+ tool_denied: "[!!]",
16
+ tool_planned: "[??]",
17
+ tool_error: "[XX]",
18
+ thinking: "[..]",
19
+ working: "[..]",
20
+ success: "[OK]",
21
+ error: "[ER]",
22
+ warning: "[!!]",
23
+ info: "[--]",
24
+ task: "[##]",
25
+ progress: "[>>]",
26
+ file: "[F]",
27
+ command: "[C]",
28
+ cached: "[*]"
29
+ }.freeze
30
+
31
+ COLORS = {
32
+ # Format: [symbol_color, dark_bg_text, light_bg_text]
33
+ user: [:bright_black, :white, :black], # User prompt and input
34
+ assistant: [:bright_green, :bright_black, :bright_black], # AI response - keep green hacker style
35
+ tool_call: [:bright_cyan, :cyan, :cyan], # Tool execution
36
+ tool_result: [:bright_cyan, :bright_black, :bright_black], # Tool output
37
+ tool_denied: [:bright_yellow, :yellow, :yellow], # Denied actions
38
+ tool_planned: [:bright_cyan, :cyan, :cyan], # Planned actions
39
+ tool_error: [:bright_red, :red, :red], # Errors
40
+ thinking: [:bright_black, :bright_black, :bright_black], # Thinking status
41
+ working: [:bright_yellow, :yellow, :yellow], # Working status
42
+ success: [:bright_green, :green, :green], # Success messages
43
+ error: [:bright_red, :red, :red], # Error messages
44
+ warning: [:bright_yellow, :yellow, :yellow], # Warnings
45
+ info: [:bright_black, :bright_black, :bright_black], # Info messages
46
+ task: [:bright_yellow, :bright_black, :bright_black], # Task items
47
+ progress: [:bright_cyan, :cyan, :cyan], # Progress indicators
48
+ file: [:cyan, :bright_black, :bright_black], # File references
49
+ command: [:cyan, :bright_black, :bright_black], # Command references
50
+ cached: [:cyan, :cyan, :cyan], # Cached indicators
51
+ # Status bar colors
52
+ statusbar_path: [:bright_black, :bright_black, :bright_black], # Path
53
+ statusbar_secondary: [:bright_black, :bright_black, :bright_black] # Model/tasks
54
+ }.freeze
55
+
56
+ def name
57
+ "hacker"
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_theme"
4
+
5
+ module Octo
6
+ module UI2
7
+ module Themes
8
+ # MinimalTheme - Clean, simple symbols
9
+ class MinimalTheme < BaseTheme
10
+ SYMBOLS = {
11
+ user: ">",
12
+ assistant: "<",
13
+ tool_call: "*",
14
+ tool_result: "-",
15
+ tool_denied: "!",
16
+ tool_planned: "?",
17
+ tool_error: "x",
18
+ thinking: ".",
19
+ working: ".",
20
+ success: "+",
21
+ error: "x",
22
+ warning: "!",
23
+ info: "-",
24
+ task: "#",
25
+ progress: ">"
26
+ }.freeze
27
+
28
+ COLORS = {
29
+ # Format: [symbol_color, dark_bg_text, light_bg_text]
30
+ user: [:bright_black, :bright_black, :black], # User prompt and input
31
+ assistant: [:green, :bright_black, :bright_black], # AI response
32
+ tool_call: [:cyan, :cyan, :cyan], # Tool execution
33
+ tool_result: [:cyan, :bright_black, :bright_black], # Tool output
34
+ tool_denied: [:yellow, :yellow, :yellow], # Denied actions
35
+ tool_planned: [:cyan, :cyan, :cyan], # Planned actions
36
+ tool_error: [:red, :red, :red], # Errors
37
+ thinking: [:bright_black, :bright_black, :bright_black], # Thinking status
38
+ working: [:bright_yellow, :yellow, :yellow], # Working status
39
+ success: [:green, :green, :green], # Success messages
40
+ error: [:red, :red, :red], # Error messages
41
+ warning: [:yellow, :yellow, :yellow], # Warnings
42
+ info: [:bright_black, :bright_black, :bright_black], # Info messages
43
+ task: [:yellow, :bright_black, :bright_black], # Task items
44
+ progress: [:cyan, :cyan, :cyan], # Progress indicators
45
+ # Status bar colors
46
+ statusbar_path: [:bright_black, :bright_black, :bright_black], # Path
47
+ statusbar_secondary: [:bright_black, :bright_black, :bright_black] # Model/tasks
48
+ }.freeze
49
+
50
+ def name
51
+ "minimal"
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Octo
4
+ THINKING_VERBS = [
5
+ "Cogitating",
6
+ "Pondering",
7
+ "Ruminating",
8
+ "Deliberating",
9
+ "Contemplating",
10
+ "Flibbertigibbeting",
11
+ "Percolating",
12
+ "Noodling",
13
+ "Brewing",
14
+ "Marinating",
15
+ "Stewing",
16
+ "Mulling",
17
+ "Processing",
18
+ "Computing",
19
+ "Calculating",
20
+ "Analyzing",
21
+ "Synthesizing",
22
+ "Ideating",
23
+ "Brainstorming",
24
+ "Reasoning"
25
+ ].freeze
26
+ end