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,290 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
require_relative "../theme_manager"
|
|
5
|
+
|
|
6
|
+
module Octo
|
|
7
|
+
module UI2
|
|
8
|
+
module Components
|
|
9
|
+
# CommandSuggestions displays a dropdown menu of available commands
|
|
10
|
+
# Supports keyboard navigation and filtering
|
|
11
|
+
class CommandSuggestions
|
|
12
|
+
attr_reader :selected_index, :visible
|
|
13
|
+
|
|
14
|
+
# System commands available by default
|
|
15
|
+
SYSTEM_COMMANDS = [
|
|
16
|
+
{ command: "/clear", description: "Clear chat history and restart session" },
|
|
17
|
+
{ command: "/config", description: "Open configuration (models, API keys, settings)" },
|
|
18
|
+
{ command: "/undo", description: "Undo the last task and restore previous state" },
|
|
19
|
+
{ command: "/help", description: "Show help information" },
|
|
20
|
+
{ command: "/exit", description: "Exit the chat session" },
|
|
21
|
+
{ command: "/quit", description: "Quit the application" }
|
|
22
|
+
].freeze
|
|
23
|
+
|
|
24
|
+
def initialize
|
|
25
|
+
@pastel = Pastel.new
|
|
26
|
+
@commands = []
|
|
27
|
+
@filtered_commands = []
|
|
28
|
+
@selected_index = 0
|
|
29
|
+
@visible = false
|
|
30
|
+
@filter_text = ""
|
|
31
|
+
@skill_commands = []
|
|
32
|
+
|
|
33
|
+
# Initialize with system commands
|
|
34
|
+
update_commands
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Get current theme from ThemeManager
|
|
38
|
+
def theme
|
|
39
|
+
UI2::ThemeManager.current_theme
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Load skill commands from skill loader, filtered by agent profile whitelist
|
|
43
|
+
# @param skill_loader [Octo::SkillLoader] The skill loader instance
|
|
44
|
+
# @param agent_profile [Octo::AgentProfile, nil] Current agent profile (nil = allow all)
|
|
45
|
+
def load_skill_commands(skill_loader, agent_profile = nil)
|
|
46
|
+
return unless skill_loader
|
|
47
|
+
|
|
48
|
+
skills = skill_loader.user_invocable_skills
|
|
49
|
+
skills = skills.select { |s| s.allowed_for_agent?(agent_profile.name) } if agent_profile
|
|
50
|
+
|
|
51
|
+
@skill_commands = skills.map do |skill|
|
|
52
|
+
{
|
|
53
|
+
command: skill.slash_command,
|
|
54
|
+
description: skill.description || "No description available",
|
|
55
|
+
type: :skill,
|
|
56
|
+
argument_hint: skill.argument_hint
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
update_commands
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Show the suggestions dropdown
|
|
64
|
+
# @param filter_text [String] Initial filter text (everything after the /)
|
|
65
|
+
def show(filter_text = "")
|
|
66
|
+
@filter_text = filter_text
|
|
67
|
+
@visible = true
|
|
68
|
+
update_filtered_commands
|
|
69
|
+
@selected_index = 0
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Hide the suggestions dropdown
|
|
73
|
+
def hide
|
|
74
|
+
@visible = false
|
|
75
|
+
@filter_text = ""
|
|
76
|
+
@filtered_commands = []
|
|
77
|
+
@selected_index = 0
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Update filter text and refresh filtered commands
|
|
81
|
+
# @param text [String] Filter text (everything after the /)
|
|
82
|
+
def update_filter(text)
|
|
83
|
+
@filter_text = text
|
|
84
|
+
update_filtered_commands
|
|
85
|
+
@selected_index = 0 # Reset selection when filter changes
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Move selection up
|
|
89
|
+
def select_previous
|
|
90
|
+
return if @filtered_commands.empty?
|
|
91
|
+
@selected_index = (@selected_index - 1) % @filtered_commands.size
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Move selection down
|
|
95
|
+
def select_next
|
|
96
|
+
return if @filtered_commands.empty?
|
|
97
|
+
@selected_index = (@selected_index + 1) % @filtered_commands.size
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Get the currently selected command
|
|
101
|
+
# @return [Hash, nil] Selected command hash or nil if none selected
|
|
102
|
+
def selected_command
|
|
103
|
+
return nil if @filtered_commands.empty?
|
|
104
|
+
@filtered_commands[@selected_index]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Get the currently selected command text
|
|
108
|
+
# @return [String, nil] Selected command text or nil if none selected
|
|
109
|
+
def selected_command_text
|
|
110
|
+
cmd = selected_command
|
|
111
|
+
cmd ? cmd[:command] : nil
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Get the argument hint for the currently selected command
|
|
115
|
+
# @return [String, nil] Argument hint string or nil if none
|
|
116
|
+
def selected_argument_hint
|
|
117
|
+
cmd = selected_command
|
|
118
|
+
cmd ? cmd[:argument_hint] : nil
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Check if there are any suggestions to show
|
|
122
|
+
# @return [Boolean]
|
|
123
|
+
def has_suggestions?
|
|
124
|
+
@visible && !@filtered_commands.empty?
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Calculate required height for rendering
|
|
128
|
+
# @return [Integer] Number of lines needed
|
|
129
|
+
def required_height
|
|
130
|
+
return 0 unless @visible
|
|
131
|
+
return 0 if @filtered_commands.empty?
|
|
132
|
+
|
|
133
|
+
# Header + commands + footer
|
|
134
|
+
1 + [@filtered_commands.size, 5].min + 1 # Max 5 visible items
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Render the suggestions dropdown
|
|
138
|
+
# @param row [Integer] Starting row position
|
|
139
|
+
# @param col [Integer] Starting column position
|
|
140
|
+
# @param width [Integer] Maximum width for the dropdown
|
|
141
|
+
# @return [String] Rendered output
|
|
142
|
+
def render(row:, col:, width: 60)
|
|
143
|
+
return "" unless @visible
|
|
144
|
+
return "" if @filtered_commands.empty?
|
|
145
|
+
|
|
146
|
+
output = []
|
|
147
|
+
max_items = 5 # Maximum visible items
|
|
148
|
+
|
|
149
|
+
# Sliding window: keep selected item visible
|
|
150
|
+
start_idx = [@selected_index - max_items + 1, 0].max
|
|
151
|
+
start_idx = [start_idx, [@filtered_commands.size - max_items, 0].max].min
|
|
152
|
+
visible_commands = @filtered_commands[start_idx, max_items] || []
|
|
153
|
+
|
|
154
|
+
# Header
|
|
155
|
+
header = @pastel.dim("┌─ Commands ") + @pastel.dim("─" * (width - 13)) + @pastel.dim("┐")
|
|
156
|
+
output << position_cursor(row, col) + header
|
|
157
|
+
|
|
158
|
+
# Items
|
|
159
|
+
visible_commands.each_with_index do |cmd, idx|
|
|
160
|
+
is_selected = (start_idx + idx == @selected_index)
|
|
161
|
+
line = render_command_item(cmd, is_selected, width)
|
|
162
|
+
output << position_cursor(row + 1 + idx, col) + line
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Footer with navigation hint
|
|
166
|
+
footer_row = row + 1 + visible_commands.size
|
|
167
|
+
total = @filtered_commands.size
|
|
168
|
+
hint = total > max_items ? " (#{total - max_items} more...)" : ""
|
|
169
|
+
footer = @pastel.dim("└") + @pastel.dim("─" * (width - 2)) + @pastel.dim("┘")
|
|
170
|
+
output << position_cursor(footer_row, col) + footer
|
|
171
|
+
|
|
172
|
+
output.join
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Clear the rendered dropdown from screen
|
|
176
|
+
# @param row [Integer] Starting row position
|
|
177
|
+
# @param col [Integer] Starting column position
|
|
178
|
+
def clear_from_screen(row:, col:)
|
|
179
|
+
return unless @visible
|
|
180
|
+
|
|
181
|
+
height = required_height
|
|
182
|
+
output = []
|
|
183
|
+
|
|
184
|
+
height.times do |i|
|
|
185
|
+
output << position_cursor(row + i, col) + clear_line
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
print output.join
|
|
189
|
+
flush
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# Update the complete commands list (system + skills)
|
|
194
|
+
private def update_commands
|
|
195
|
+
system_cmds = SYSTEM_COMMANDS.map { |c| c.merge(type: :system) }
|
|
196
|
+
@commands = system_cmds + @skill_commands
|
|
197
|
+
update_filtered_commands if @visible
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Update filtered commands based on current filter text
|
|
201
|
+
private def update_filtered_commands
|
|
202
|
+
if @filter_text.empty?
|
|
203
|
+
@filtered_commands = @commands
|
|
204
|
+
else
|
|
205
|
+
filter_lower = @filter_text.downcase
|
|
206
|
+
@filtered_commands = @commands.select do |cmd|
|
|
207
|
+
# Remove leading / for comparison
|
|
208
|
+
cmd_name = cmd[:command].sub(/^\//, "")
|
|
209
|
+
# Only match command name, not description
|
|
210
|
+
cmd_name.downcase.start_with?(filter_lower)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Render a single command item
|
|
216
|
+
# @param cmd [Hash] Command hash with :command and :description
|
|
217
|
+
# @param selected [Boolean] Whether this item is selected
|
|
218
|
+
# @param width [Integer] Maximum width
|
|
219
|
+
# @return [String] Rendered item
|
|
220
|
+
private def render_command_item(cmd, selected, width)
|
|
221
|
+
# Calculate available space
|
|
222
|
+
available = width - 4 # Account for borders and padding
|
|
223
|
+
|
|
224
|
+
# Format command (e.g., "/clear")
|
|
225
|
+
command_text = cmd[:command]
|
|
226
|
+
|
|
227
|
+
# Format description
|
|
228
|
+
max_desc_length = available - command_text.length - 3 # 3 for spacing
|
|
229
|
+
description = truncate_text(cmd[:description], max_desc_length)
|
|
230
|
+
|
|
231
|
+
# Build line
|
|
232
|
+
if selected
|
|
233
|
+
# Highlighted selection
|
|
234
|
+
line = @pastel.on_blue(@pastel.white(" #{command_text} "))
|
|
235
|
+
line += @pastel.on_blue(@pastel.dim(" #{description}"))
|
|
236
|
+
# Pad to full width
|
|
237
|
+
content_length = command_text.length + description.length + 2
|
|
238
|
+
padding = " " * [available - content_length, 0].max
|
|
239
|
+
line += @pastel.on_blue(padding)
|
|
240
|
+
@pastel.dim("│") + line + @pastel.dim("│")
|
|
241
|
+
else
|
|
242
|
+
# Normal item
|
|
243
|
+
line = " #{@pastel.cyan(command_text)} #{@pastel.dim(description)}"
|
|
244
|
+
# Pad to full width
|
|
245
|
+
content_length = strip_ansi(line).length
|
|
246
|
+
padding = " " * [available - content_length, 0].max
|
|
247
|
+
@pastel.dim("│") + line + padding + @pastel.dim("│")
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Truncate text to maximum length
|
|
252
|
+
# @param text [String] Text to truncate
|
|
253
|
+
# @param max_length [Integer] Maximum length
|
|
254
|
+
# @return [String] Truncated text
|
|
255
|
+
private def truncate_text(text, max_length)
|
|
256
|
+
return "" if max_length <= 3
|
|
257
|
+
return text if text.length <= max_length
|
|
258
|
+
|
|
259
|
+
text[0...(max_length - 3)] + "..."
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Strip ANSI codes from text
|
|
263
|
+
# @param text [String] Text with ANSI codes
|
|
264
|
+
# @return [String] Plain text
|
|
265
|
+
private def strip_ansi(text)
|
|
266
|
+
text.gsub(/\e\[[0-9;]*m/, '')
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Position cursor at specific row and column
|
|
270
|
+
# @param row [Integer] Row position (0-indexed)
|
|
271
|
+
# @param col [Integer] Column position (0-indexed)
|
|
272
|
+
# @return [String] ANSI escape sequence
|
|
273
|
+
private def position_cursor(row, col)
|
|
274
|
+
"\e[#{row + 1};#{col + 1}H"
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Clear current line
|
|
278
|
+
# @return [String] ANSI escape sequence
|
|
279
|
+
private def clear_line
|
|
280
|
+
"\e[2K"
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Flush output to terminal
|
|
284
|
+
private def flush
|
|
285
|
+
$stdout.flush
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_component"
|
|
4
|
+
|
|
5
|
+
module Octo
|
|
6
|
+
module UI2
|
|
7
|
+
module Components
|
|
8
|
+
# CommonComponent renders common UI elements (progress, success, error, warning)
|
|
9
|
+
class CommonComponent < BaseComponent
|
|
10
|
+
# Render thinking indicator
|
|
11
|
+
# @return [String] Thinking indicator
|
|
12
|
+
def render_thinking
|
|
13
|
+
symbol = format_symbol(:thinking)
|
|
14
|
+
text = format_text("Thinking...", :thinking)
|
|
15
|
+
"#{symbol} #{text}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Render progress indicator (stopped state, gray)
|
|
19
|
+
# @param message [String] Progress message
|
|
20
|
+
# @return [String] Progress indicator
|
|
21
|
+
def render_progress(message)
|
|
22
|
+
symbol = format_symbol(:thinking)
|
|
23
|
+
text = format_text(message, :thinking)
|
|
24
|
+
"#{symbol} #{text}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Render working indicator (active state, yellow)
|
|
28
|
+
# @param message [String] Progress message
|
|
29
|
+
# @return [String] Working indicator
|
|
30
|
+
def render_working(message)
|
|
31
|
+
symbol = format_symbol(:working)
|
|
32
|
+
text = format_text(message, :working)
|
|
33
|
+
"#{symbol} #{text}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Render success message
|
|
37
|
+
# @param message [String] Success message
|
|
38
|
+
# @return [String] Success message
|
|
39
|
+
def render_success(message)
|
|
40
|
+
symbol = format_symbol(:success)
|
|
41
|
+
text = format_text(message, :success)
|
|
42
|
+
"#{symbol} #{text}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Render error message
|
|
46
|
+
# @param message [String] Error message
|
|
47
|
+
# @return [String] Error message
|
|
48
|
+
def render_error(message)
|
|
49
|
+
symbol = format_symbol(:error)
|
|
50
|
+
text = format_text(message, :error)
|
|
51
|
+
"#{symbol} #{text}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Render warning message
|
|
55
|
+
# @param message [String] Warning message
|
|
56
|
+
# @return [String] Warning message
|
|
57
|
+
def render_warning(message)
|
|
58
|
+
symbol = format_symbol(:warning)
|
|
59
|
+
text = format_text(message, :warning)
|
|
60
|
+
"#{symbol} #{text}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Render task completion summary
|
|
64
|
+
# @param iterations [Integer] Number of iterations
|
|
65
|
+
# @param duration [Float] Duration in seconds
|
|
66
|
+
# @param cache_tokens [Integer] Cache read tokens
|
|
67
|
+
# @param cache_requests [Integer] Total cache requests count
|
|
68
|
+
# @param cache_hits [Integer] Cache hit requests count
|
|
69
|
+
# @return [String] Formatted completion summary
|
|
70
|
+
def render_task_complete(iterations:, duration: nil, cache_tokens: nil, cache_requests: nil, cache_hits: nil)
|
|
71
|
+
lines = []
|
|
72
|
+
lines << ""
|
|
73
|
+
lines << @pastel.dim("─" * 60)
|
|
74
|
+
lines << render_success("Task Complete")
|
|
75
|
+
lines << ""
|
|
76
|
+
|
|
77
|
+
# Display each stat on a separate line
|
|
78
|
+
lines << " Iterations: #{iterations}"
|
|
79
|
+
lines << " Duration: #{duration.round(1)}s" if duration
|
|
80
|
+
|
|
81
|
+
# Display cache information if available
|
|
82
|
+
if cache_tokens && cache_tokens > 0
|
|
83
|
+
lines << " Cache Tokens: #{cache_tokens} tokens"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
if cache_requests && cache_requests > 0
|
|
87
|
+
hit_rate = cache_hits > 0 ? ((cache_hits.to_f / cache_requests) * 100).round(1) : 0
|
|
88
|
+
lines << " Cache Requests: #{cache_requests} (#{cache_hits} hits, #{hit_rate}% hit rate)"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
lines.join("\n")
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../line_editor"
|
|
4
|
+
|
|
5
|
+
module Octo
|
|
6
|
+
module UI2
|
|
7
|
+
module Components
|
|
8
|
+
# InlineInput provides inline input for confirmations and simple prompts
|
|
9
|
+
# Renders at the end of output area, not at fixed bottom position
|
|
10
|
+
class InlineInput
|
|
11
|
+
include LineEditor
|
|
12
|
+
|
|
13
|
+
attr_reader :prompt, :default_value
|
|
14
|
+
|
|
15
|
+
def initialize(prompt: "", default: nil)
|
|
16
|
+
initialize_line_editor
|
|
17
|
+
@prompt = prompt
|
|
18
|
+
@default_value = default
|
|
19
|
+
@active = false
|
|
20
|
+
@result_queue = nil
|
|
21
|
+
@paste_counter = 0
|
|
22
|
+
@paste_placeholders = {}
|
|
23
|
+
@continuation_prompt = "> " # Continuation prompt for wrapped lines
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Activate inline input and wait for user input
|
|
27
|
+
# @return [String] User input
|
|
28
|
+
def collect
|
|
29
|
+
@active = true
|
|
30
|
+
@result_queue = Queue.new
|
|
31
|
+
# Don't set default as initial text - start empty
|
|
32
|
+
@result_queue.pop
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Check if active
|
|
36
|
+
def active?
|
|
37
|
+
@active
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Handle keyboard input
|
|
41
|
+
# @param key [Symbol, String] Key input
|
|
42
|
+
# @return [Hash] Result with action
|
|
43
|
+
def handle_key(key)
|
|
44
|
+
return { action: nil } unless @active
|
|
45
|
+
|
|
46
|
+
case key
|
|
47
|
+
when Hash
|
|
48
|
+
if key[:type] == :rapid_input
|
|
49
|
+
# Handle multi-line paste with placeholder
|
|
50
|
+
pasted_text = key[:text]
|
|
51
|
+
pasted_lines = pasted_text.split(/\r\n|\r|\n/)
|
|
52
|
+
|
|
53
|
+
if pasted_lines.size > 1
|
|
54
|
+
# Multi-line paste - use placeholder
|
|
55
|
+
@paste_counter += 1
|
|
56
|
+
placeholder = "[##{@paste_counter} Paste Text]"
|
|
57
|
+
@paste_placeholders[placeholder] = pasted_text
|
|
58
|
+
insert_text(placeholder)
|
|
59
|
+
else
|
|
60
|
+
# Single line - insert directly
|
|
61
|
+
insert_text(pasted_text)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
{ action: :update }
|
|
65
|
+
when :enter
|
|
66
|
+
handle_enter
|
|
67
|
+
when :backspace
|
|
68
|
+
backspace
|
|
69
|
+
{ action: :update }
|
|
70
|
+
when :delete
|
|
71
|
+
delete_char
|
|
72
|
+
{ action: :update }
|
|
73
|
+
when :left_arrow, :ctrl_b
|
|
74
|
+
cursor_left
|
|
75
|
+
{ action: :update }
|
|
76
|
+
when :right_arrow, :ctrl_f
|
|
77
|
+
cursor_right
|
|
78
|
+
{ action: :update }
|
|
79
|
+
when :home, :ctrl_a
|
|
80
|
+
cursor_home
|
|
81
|
+
{ action: :update }
|
|
82
|
+
when :end, :ctrl_e
|
|
83
|
+
cursor_end
|
|
84
|
+
{ action: :update }
|
|
85
|
+
when :ctrl_k
|
|
86
|
+
kill_to_end
|
|
87
|
+
{ action: :update }
|
|
88
|
+
when :ctrl_u
|
|
89
|
+
kill_to_start
|
|
90
|
+
{ action: :update }
|
|
91
|
+
when :ctrl_w
|
|
92
|
+
kill_word
|
|
93
|
+
{ action: :update }
|
|
94
|
+
when :shift_tab
|
|
95
|
+
handle_shift_tab
|
|
96
|
+
when :ctrl_o
|
|
97
|
+
handle_toggle_expand
|
|
98
|
+
when :ctrl_c
|
|
99
|
+
handle_cancel
|
|
100
|
+
when :escape
|
|
101
|
+
handle_cancel
|
|
102
|
+
else
|
|
103
|
+
if key.is_a?(String) && key.length >= 1 && key.ord >= 32
|
|
104
|
+
insert_char(key)
|
|
105
|
+
{ action: :update }
|
|
106
|
+
else
|
|
107
|
+
{ action: nil }
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Render inline input with prompt and cursor
|
|
113
|
+
# @return [String] Rendered line (may wrap to multiple lines)
|
|
114
|
+
def render
|
|
115
|
+
width = TTY::Screen.width
|
|
116
|
+
# Use effective content width (respecting MAX_CONTENT_WIDTH_RATIO)
|
|
117
|
+
content_width = effective_content_width(width)
|
|
118
|
+
prompt_width = calculate_display_width(strip_ansi_codes(@prompt))
|
|
119
|
+
available_width = content_width - prompt_width
|
|
120
|
+
|
|
121
|
+
# Get wrapped segments
|
|
122
|
+
wrapped_segments = wrap_line(@line, available_width)
|
|
123
|
+
|
|
124
|
+
# Build rendered output with cursor
|
|
125
|
+
output = ""
|
|
126
|
+
|
|
127
|
+
wrapped_segments.each_with_index do |segment, idx|
|
|
128
|
+
prefix = if idx == 0
|
|
129
|
+
@prompt
|
|
130
|
+
else
|
|
131
|
+
"> " # Continuation prompt indicator
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Render segment with cursor if needed
|
|
135
|
+
segment_text = render_line_segment_with_cursor(@line, segment[:start], segment[:end])
|
|
136
|
+
|
|
137
|
+
output += "#{prefix}#{segment_text}"
|
|
138
|
+
output += "\n" unless idx == wrapped_segments.size - 1
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
output
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Get cursor column position
|
|
145
|
+
# @return [Integer] Column position
|
|
146
|
+
def cursor_col
|
|
147
|
+
cursor_column(@prompt)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Get cursor position for display (considering line wrapping and continuation prompt)
|
|
151
|
+
# @param width [Integer] Terminal width
|
|
152
|
+
# @return [Array<Integer>] Row and column position (0-indexed)
|
|
153
|
+
def cursor_position_for_display(width = TTY::Screen.width)
|
|
154
|
+
# Use effective content width (respecting MAX_CONTENT_WIDTH_RATIO)
|
|
155
|
+
content_width = effective_content_width(width)
|
|
156
|
+
cursor_position_with_wrap(@prompt, content_width, @continuation_prompt)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Get the number of lines this input will occupy when rendered
|
|
160
|
+
# @param width [Integer] Terminal width
|
|
161
|
+
# @return [Integer] Number of lines
|
|
162
|
+
def line_count(width = TTY::Screen.width)
|
|
163
|
+
# Use effective content width (respecting MAX_CONTENT_WIDTH_RATIO)
|
|
164
|
+
content_width = effective_content_width(width)
|
|
165
|
+
prompt_width = calculate_display_width(strip_ansi_codes(@prompt))
|
|
166
|
+
available_width = content_width - prompt_width
|
|
167
|
+
return 1 if available_width <= 0
|
|
168
|
+
|
|
169
|
+
segments = wrap_line(@line, available_width)
|
|
170
|
+
segments.size
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Deactivate inline input
|
|
174
|
+
def deactivate
|
|
175
|
+
@active = false
|
|
176
|
+
@result_queue = nil
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def handle_enter
|
|
181
|
+
result = expand_placeholders(current_line)
|
|
182
|
+
# If empty and has default, use default
|
|
183
|
+
result = @default_value.to_s if result.empty? && @default_value
|
|
184
|
+
|
|
185
|
+
queue = @result_queue
|
|
186
|
+
deactivate
|
|
187
|
+
queue&.push(result)
|
|
188
|
+
|
|
189
|
+
{ action: :submit, result: result }
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def expand_placeholders(text)
|
|
193
|
+
super(text, @paste_placeholders)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def handle_cancel
|
|
197
|
+
queue = @result_queue
|
|
198
|
+
deactivate
|
|
199
|
+
queue&.push(nil)
|
|
200
|
+
|
|
201
|
+
{ action: :cancel }
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def handle_shift_tab
|
|
205
|
+
# Auto-confirm as yes (or use default if it's true)
|
|
206
|
+
result = if @default_value == true || @default_value.to_s.downcase == "yes"
|
|
207
|
+
@default_value.to_s
|
|
208
|
+
else
|
|
209
|
+
"yes"
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
queue = @result_queue
|
|
213
|
+
deactivate
|
|
214
|
+
queue&.push(result)
|
|
215
|
+
|
|
216
|
+
{ action: :toggle_mode }
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
private def handle_toggle_expand
|
|
220
|
+
# Toggle expansion of diff display
|
|
221
|
+
{ action: :toggle_expand }
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|