octo-agent 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.clacky/skills/commit/SKILL.md +423 -0
- data/.clacky/skills/gem-release/SKILL.md +199 -0
- data/.clacky/skills/gem-release/scripts/release.sh +304 -0
- data/.clacky/skills/oss-upload/SKILL.md +47 -0
- data/.octorules +106 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +76 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/CONTRIBUTING.md +92 -0
- data/Dockerfile +28 -0
- data/LICENSE.txt +22 -0
- data/POSITIONING.md +46 -0
- data/README.md +134 -0
- data/README_CN.md +134 -0
- data/Rakefile +34 -0
- data/benchmark/fixtures/sample_project/Gemfile +3 -0
- data/benchmark/fixtures/sample_project/lib/api_handler.rb +32 -0
- data/benchmark/fixtures/sample_project/lib/order_calculator.rb +23 -0
- data/benchmark/fixtures/sample_project/lib/user_renderer.rb +20 -0
- data/benchmark/fixtures/sample_project/spec/order_calculator_spec.rb +20 -0
- data/benchmark/results/EVALUATION_REPORT.md +165 -0
- data/benchmark/results/baseline_20260511_174424.json +128 -0
- data/benchmark/results/report_20260511_175256.json +271 -0
- data/benchmark/results/report_20260511_175444.json +271 -0
- data/benchmark/results/treatment_20260511_175103.json +130 -0
- data/benchmark/runner.rb +441 -0
- data/bin/octo +7 -0
- data/docs/agent-first-ui-design.md +77 -0
- data/docs/billing-system.md +318 -0
- data/docs/channel-architecture.md +235 -0
- data/docs/engineering-article.md +343 -0
- data/docs/session-skill-invocation.md +69 -0
- data/docs/time_machine_design.md +247 -0
- data/docs/ui2-architecture.md +124 -0
- data/homebrew/README.md +96 -0
- data/homebrew/openocto.rb +24 -0
- data/lib/octo/agent/hook_manager.rb +61 -0
- data/lib/octo/agent/llm_caller.rb +800 -0
- data/lib/octo/agent/memory_updater.rb +246 -0
- data/lib/octo/agent/message_compressor.rb +225 -0
- data/lib/octo/agent/message_compressor_helper.rb +869 -0
- data/lib/octo/agent/next_message_suggester.rb +215 -0
- data/lib/octo/agent/session_serializer.rb +685 -0
- data/lib/octo/agent/skill_auto_creator.rb +114 -0
- data/lib/octo/agent/skill_evolution.rb +61 -0
- data/lib/octo/agent/skill_manager.rb +466 -0
- data/lib/octo/agent/skill_reflector.rb +89 -0
- data/lib/octo/agent/system_prompt_builder.rb +101 -0
- data/lib/octo/agent/time_machine.rb +214 -0
- data/lib/octo/agent/tool_executor.rb +454 -0
- data/lib/octo/agent/tool_registry.rb +150 -0
- data/lib/octo/agent.rb +2180 -0
- data/lib/octo/agent_config.rb +989 -0
- data/lib/octo/agent_profile.rb +112 -0
- data/lib/octo/anthropic_stream_aggregator.rb +137 -0
- data/lib/octo/background_task_registry.rb +324 -0
- data/lib/octo/banner.rb +34 -0
- data/lib/octo/bedrock_stream_aggregator.rb +137 -0
- data/lib/octo/block_font.rb +331 -0
- data/lib/octo/cli.rb +968 -0
- data/lib/octo/client.rb +623 -0
- data/lib/octo/default_agents/SOUL.md +3 -0
- data/lib/octo/default_agents/USER.md +1 -0
- data/lib/octo/default_agents/base_prompt.md +66 -0
- data/lib/octo/default_agents/coding/profile.yml +2 -0
- data/lib/octo/default_agents/coding/system_prompt.md +67 -0
- data/lib/octo/default_agents/general/profile.yml +2 -0
- data/lib/octo/default_agents/general/system_prompt.md +16 -0
- data/lib/octo/default_parsers/doc_parser.rb +69 -0
- data/lib/octo/default_parsers/docx_parser.rb +188 -0
- data/lib/octo/default_parsers/pdf_parser.rb +120 -0
- data/lib/octo/default_parsers/pdf_parser_ocr.py +103 -0
- data/lib/octo/default_parsers/pdf_parser_plumber.py +62 -0
- data/lib/octo/default_parsers/pptx_parser.rb +140 -0
- data/lib/octo/default_parsers/xlsx_parser.rb +121 -0
- data/lib/octo/default_skills/browser-setup/SKILL.md +426 -0
- data/lib/octo/default_skills/channel-manager/SKILL.md +623 -0
- data/lib/octo/default_skills/channel-manager/dingtalk_setup.rb +191 -0
- data/lib/octo/default_skills/channel-manager/discord_setup.rb +199 -0
- data/lib/octo/default_skills/channel-manager/feishu_setup.rb +574 -0
- data/lib/octo/default_skills/channel-manager/import_lark_skills.rb +97 -0
- data/lib/octo/default_skills/channel-manager/install_feishu_skills.rb +105 -0
- data/lib/octo/default_skills/channel-manager/weixin_setup.rb +274 -0
- data/lib/octo/default_skills/code-explorer/SKILL.md +36 -0
- data/lib/octo/default_skills/cron-task-creator/SKILL.md +257 -0
- data/lib/octo/default_skills/cron-task-creator/evals/evals.json +38 -0
- data/lib/octo/default_skills/onboard/SKILL.md +578 -0
- data/lib/octo/default_skills/onboard/scripts/import_external_skills.rb +413 -0
- data/lib/octo/default_skills/onboard/scripts/install_builtin_skills.rb +97 -0
- data/lib/octo/default_skills/persist-memory/SKILL.md +59 -0
- data/lib/octo/default_skills/personal-website/SKILL.md +113 -0
- data/lib/octo/default_skills/personal-website/publish.rb +235 -0
- data/lib/octo/default_skills/product-help/SKILL.md +123 -0
- data/lib/octo/default_skills/product-help/docs/agent-config.md +74 -0
- data/lib/octo/default_skills/product-help/docs/best-practices.md +49 -0
- data/lib/octo/default_skills/product-help/docs/browser-tool.md +53 -0
- data/lib/octo/default_skills/product-help/docs/built-in-skills.md +43 -0
- data/lib/octo/default_skills/product-help/docs/cli-reference.md +82 -0
- data/lib/octo/default_skills/product-help/docs/create-your-first-skill.md +47 -0
- data/lib/octo/default_skills/product-help/docs/faq.md +98 -0
- data/lib/octo/default_skills/product-help/docs/how-to-use-a-skill.md +58 -0
- data/lib/octo/default_skills/product-help/docs/installation.md +59 -0
- data/lib/octo/default_skills/product-help/docs/memory-system.md +61 -0
- data/lib/octo/default_skills/product-help/docs/octorules.md +62 -0
- data/lib/octo/default_skills/product-help/docs/session-management.md +63 -0
- data/lib/octo/default_skills/product-help/docs/skill-basics.md +55 -0
- data/lib/octo/default_skills/product-help/docs/skill-frontmatter.md +61 -0
- data/lib/octo/default_skills/product-help/docs/web-server.md +49 -0
- data/lib/octo/default_skills/product-help/docs/what-is-octo.md +37 -0
- data/lib/octo/default_skills/product-help/docs/windows-installation.md +36 -0
- data/lib/octo/default_skills/product-help/docs/writing-tips.md +53 -0
- data/lib/octo/default_skills/recall-memory/SKILL.md +65 -0
- data/lib/octo/default_skills/skill-add/SKILL.md +59 -0
- data/lib/octo/default_skills/skill-add/scripts/install_from_zip.rb +295 -0
- data/lib/octo/default_skills/skill-creator/SKILL.md +602 -0
- data/lib/octo/default_skills/skill-creator/agents/analyzer.md +274 -0
- data/lib/octo/default_skills/skill-creator/agents/comparator.md +202 -0
- data/lib/octo/default_skills/skill-creator/agents/grader.md +223 -0
- data/lib/octo/default_skills/skill-creator/eval-viewer/generate_review.py +471 -0
- data/lib/octo/default_skills/skill-creator/eval-viewer/viewer.html +1325 -0
- data/lib/octo/default_skills/skill-creator/references/schemas.md +430 -0
- data/lib/octo/default_skills/skill-creator/scripts/__init__.py +0 -0
- data/lib/octo/default_skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- data/lib/octo/default_skills/skill-creator/scripts/generate_report.py +326 -0
- data/lib/octo/default_skills/skill-creator/scripts/improve_description.py +310 -0
- data/lib/octo/default_skills/skill-creator/scripts/quick_validate.py +103 -0
- data/lib/octo/default_skills/skill-creator/scripts/run_eval.py +317 -0
- data/lib/octo/default_skills/skill-creator/scripts/run_loop.py +331 -0
- data/lib/octo/default_skills/skill-creator/scripts/utils.py +47 -0
- data/lib/octo/default_skills/skill-creator/scripts/validate_skill_frontmatter.rb +143 -0
- data/lib/octo/idle_compression_timer.rb +115 -0
- data/lib/octo/json_ui_controller.rb +204 -0
- data/lib/octo/message_format/anthropic.rb +409 -0
- data/lib/octo/message_format/bedrock.rb +361 -0
- data/lib/octo/message_format/open_ai.rb +222 -0
- data/lib/octo/message_history.rb +373 -0
- data/lib/octo/openai_stream_aggregator.rb +130 -0
- data/lib/octo/plain_ui_controller.rb +166 -0
- data/lib/octo/providers.rb +534 -0
- data/lib/octo/server/browser_manager.rb +397 -0
- data/lib/octo/server/channel/adapters/base.rb +82 -0
- data/lib/octo/server/channel/adapters/dingtalk/adapter.rb +314 -0
- data/lib/octo/server/channel/adapters/dingtalk/api_client.rb +391 -0
- data/lib/octo/server/channel/adapters/dingtalk/stream_client.rb +203 -0
- data/lib/octo/server/channel/adapters/discord/adapter.rb +229 -0
- data/lib/octo/server/channel/adapters/discord/api_client.rb +107 -0
- data/lib/octo/server/channel/adapters/discord/gateway_client.rb +270 -0
- data/lib/octo/server/channel/adapters/feishu/adapter.rb +320 -0
- data/lib/octo/server/channel/adapters/feishu/bot.rb +478 -0
- data/lib/octo/server/channel/adapters/feishu/file_processor.rb +36 -0
- data/lib/octo/server/channel/adapters/feishu/message_parser.rb +129 -0
- data/lib/octo/server/channel/adapters/feishu/ws_client.rb +423 -0
- data/lib/octo/server/channel/adapters/telegram/adapter.rb +375 -0
- data/lib/octo/server/channel/adapters/telegram/api_client.rb +205 -0
- data/lib/octo/server/channel/adapters/wecom/adapter.rb +148 -0
- data/lib/octo/server/channel/adapters/wecom/media_downloader.rb +115 -0
- data/lib/octo/server/channel/adapters/wecom/ws_client.rb +395 -0
- data/lib/octo/server/channel/adapters/weixin/adapter.rb +692 -0
- data/lib/octo/server/channel/adapters/weixin/api_client.rb +402 -0
- data/lib/octo/server/channel/channel_config.rb +178 -0
- data/lib/octo/server/channel/channel_manager.rb +468 -0
- data/lib/octo/server/channel/channel_ui_controller.rb +224 -0
- data/lib/octo/server/channel.rb +33 -0
- data/lib/octo/server/discover.rb +77 -0
- data/lib/octo/server/epipe_safe_io.rb +105 -0
- data/lib/octo/server/http_server.rb +3554 -0
- data/lib/octo/server/scheduler.rb +317 -0
- data/lib/octo/server/server_master.rb +325 -0
- data/lib/octo/server/session_registry.rb +431 -0
- data/lib/octo/server/web_ui_controller.rb +487 -0
- data/lib/octo/session_manager.rb +385 -0
- data/lib/octo/skill.rb +466 -0
- data/lib/octo/skill_loader.rb +328 -0
- data/lib/octo/tools/base.rb +118 -0
- data/lib/octo/tools/browser.rb +625 -0
- data/lib/octo/tools/edit.rb +165 -0
- data/lib/octo/tools/file_reader.rb +549 -0
- data/lib/octo/tools/glob.rb +162 -0
- data/lib/octo/tools/grep.rb +356 -0
- data/lib/octo/tools/invoke_skill.rb +96 -0
- data/lib/octo/tools/list_tasks.rb +54 -0
- data/lib/octo/tools/redo_task.rb +41 -0
- data/lib/octo/tools/request_user_feedback.rb +84 -0
- data/lib/octo/tools/security.rb +333 -0
- data/lib/octo/tools/terminal/output_cleaner.rb +63 -0
- data/lib/octo/tools/terminal/persistent_session.rb +268 -0
- data/lib/octo/tools/terminal/safe_rm.sh +106 -0
- data/lib/octo/tools/terminal/session_manager.rb +213 -0
- data/lib/octo/tools/terminal.rb +1828 -0
- data/lib/octo/tools/todo_manager.rb +374 -0
- data/lib/octo/tools/trash_manager.rb +388 -0
- data/lib/octo/tools/undo_task.rb +35 -0
- data/lib/octo/tools/web_fetch.rb +242 -0
- data/lib/octo/tools/web_search.rb +260 -0
- data/lib/octo/tools/write.rb +77 -0
- data/lib/octo/ui2/block_font.rb +10 -0
- data/lib/octo/ui2/components/base_component.rb +163 -0
- data/lib/octo/ui2/components/command_suggestions.rb +290 -0
- data/lib/octo/ui2/components/common_component.rb +96 -0
- data/lib/octo/ui2/components/inline_input.rb +226 -0
- data/lib/octo/ui2/components/input_area.rb +1338 -0
- data/lib/octo/ui2/components/message_component.rb +99 -0
- data/lib/octo/ui2/components/modal_component.rb +419 -0
- data/lib/octo/ui2/components/todo_area.rb +149 -0
- data/lib/octo/ui2/components/tool_component.rb +107 -0
- data/lib/octo/ui2/components/welcome_banner.rb +139 -0
- data/lib/octo/ui2/layout_manager.rb +807 -0
- data/lib/octo/ui2/line_editor.rb +363 -0
- data/lib/octo/ui2/markdown_renderer.rb +100 -0
- data/lib/octo/ui2/output_buffer.rb +370 -0
- data/lib/octo/ui2/progress_handle.rb +362 -0
- data/lib/octo/ui2/progress_indicator.rb +55 -0
- data/lib/octo/ui2/screen_buffer.rb +273 -0
- data/lib/octo/ui2/terminal_detector.rb +119 -0
- data/lib/octo/ui2/theme_manager.rb +85 -0
- data/lib/octo/ui2/themes/base_theme.rb +105 -0
- data/lib/octo/ui2/themes/hacker_theme.rb +62 -0
- data/lib/octo/ui2/themes/minimal_theme.rb +56 -0
- data/lib/octo/ui2/thinking_verbs.rb +26 -0
- data/lib/octo/ui2/ui_controller.rb +1625 -0
- data/lib/octo/ui2/view_renderer.rb +177 -0
- data/lib/octo/ui2.rb +40 -0
- data/lib/octo/ui_interface.rb +154 -0
- data/lib/octo/utils/arguments_parser.rb +191 -0
- data/lib/octo/utils/browser_detector.rb +195 -0
- data/lib/octo/utils/encoding.rb +92 -0
- data/lib/octo/utils/environment_detector.rb +140 -0
- data/lib/octo/utils/file_ignore_helper.rb +170 -0
- data/lib/octo/utils/file_processor.rb +601 -0
- data/lib/octo/utils/gitignore_parser.rb +154 -0
- data/lib/octo/utils/limit_stack.rb +152 -0
- data/lib/octo/utils/logger.rb +124 -0
- data/lib/octo/utils/login_shell.rb +72 -0
- data/lib/octo/utils/model_pricing.rb +646 -0
- data/lib/octo/utils/parser_manager.rb +165 -0
- data/lib/octo/utils/path_helper.rb +15 -0
- data/lib/octo/utils/scripts_manager.rb +59 -0
- data/lib/octo/utils/string_matcher.rb +158 -0
- data/lib/octo/utils/trash_directory.rb +112 -0
- data/lib/octo/utils/workspace_rules.rb +46 -0
- data/lib/octo/version.rb +5 -0
- data/lib/octo/web/app.css +7141 -0
- data/lib/octo/web/app.js +543 -0
- data/lib/octo/web/apple-touch-icon.png +0 -0
- data/lib/octo/web/auth.js +150 -0
- data/lib/octo/web/channels.js +276 -0
- data/lib/octo/web/datepicker.js +205 -0
- data/lib/octo/web/favicon.png +0 -0
- data/lib/octo/web/i18n.js +1073 -0
- data/lib/octo/web/icon-512.png +0 -0
- data/lib/octo/web/icon-dark.svg +25 -0
- data/lib/octo/web/icon.svg +29 -0
- data/lib/octo/web/index.html +871 -0
- data/lib/octo/web/marked.min.js +69 -0
- data/lib/octo/web/onboard.js +491 -0
- data/lib/octo/web/profile.js +442 -0
- data/lib/octo/web/sessions.js +4421 -0
- data/lib/octo/web/settings.js +913 -0
- data/lib/octo/web/sidebar.js +32 -0
- data/lib/octo/web/skills.js +885 -0
- data/lib/octo/web/tasks.js +297 -0
- data/lib/octo/web/theme.js +105 -0
- data/lib/octo/web/trash.js +343 -0
- data/lib/octo/web/vendor/hljs/highlight.min.js +1244 -0
- data/lib/octo/web/vendor/hljs/hljs-theme.css +95 -0
- data/lib/octo/web/vendor/katex/auto-render.min.js +1 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/katex.min.css +1 -0
- data/lib/octo/web/vendor/katex/katex.min.js +1 -0
- data/lib/octo/web/version.js +449 -0
- data/lib/octo/web/weixin-qr.html +209 -0
- data/lib/octo/web/ws-dispatcher.js +357 -0
- data/lib/octo/web/ws.js +128 -0
- data/lib/octo.rb +145 -0
- data/scripts/build/build.sh +329 -0
- data/scripts/build/lib/apt.sh +56 -0
- data/scripts/build/lib/brew.sh +89 -0
- data/scripts/build/lib/colors.sh +17 -0
- data/scripts/build/lib/gem.sh +95 -0
- data/scripts/build/lib/mise.sh +125 -0
- data/scripts/build/lib/network.sh +157 -0
- data/scripts/build/lib/os.sh +57 -0
- data/scripts/build/lib/shell.sh +37 -0
- data/scripts/build/src/install.sh.cc +174 -0
- data/scripts/build/src/install_browser.sh.cc +101 -0
- data/scripts/build/src/install_full.sh.cc +290 -0
- data/scripts/build/src/install_rails_deps.sh.cc +145 -0
- data/scripts/build/src/install_system_deps.sh.cc +123 -0
- data/scripts/build/src/uninstall.sh.cc +101 -0
- data/scripts/install.ps1 +532 -0
- data/scripts/install.sh +567 -0
- data/scripts/install_browser.sh +479 -0
- data/scripts/install_full.sh +838 -0
- data/scripts/install_rails_deps.sh +746 -0
- data/scripts/install_system_deps.sh +518 -0
- data/scripts/uninstall.sh +287 -0
- data/sig/octo.rbs +4 -0
- metadata +614 -0
|
@@ -0,0 +1,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
|