comate-cli 0.7.0a11__tar.gz → 0.7.1__tar.gz
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.
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/PKG-INFO +1 -1
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/animations.py +2 -4
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/app.py +5 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/logo.py +4 -1
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/preflight.py +24 -6
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/slash_commands.py +5 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/status_bar.py +210 -26
- comate_cli-0.7.1/comate_cli/terminal_agent/statusline/model.py +60 -0
- comate_cli-0.7.1/comate_cli/terminal_agent/statusline/picker.py +183 -0
- comate_cli-0.7.1/comate_cli/terminal_agent/statusline/picker_state.py +103 -0
- comate_cli-0.7.1/comate_cli/terminal_agent/statusline/store.py +90 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/tui.py +37 -3
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/tui_parts/commands.py +51 -1
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/tui_parts/key_bindings.py +39 -3
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/tui_parts/render_panels.py +7 -28
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/tui_parts/ui_mode.py +1 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/pyproject.toml +1 -1
- comate_cli-0.7.1/tests/config/__init__.py +0 -0
- comate_cli-0.7.1/tests/statusline/__init__.py +0 -0
- comate_cli-0.7.1/tests/statusline/test_model.py +43 -0
- comate_cli-0.7.1/tests/statusline/test_picker_state.py +114 -0
- comate_cli-0.7.1/tests/statusline/test_store.py +72 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_completion_status_panel.py +3 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_event_renderer_streaming.py +2 -4
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_interrupt_exit_semantics.py +52 -1
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_logo.py +5 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_preflight.py +126 -1
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_status_bar.py +60 -1
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_task_panel_key_bindings.py +1 -1
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/uv.lock +1447 -1463
- comate_cli-0.7.0a11/bash-exit-code-green-dot-bug.md +0 -140
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/.gitignore +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/AGENTS.md +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/CHANGELOG.md +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/README.md +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/__init__.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/__main__.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/main.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/mcp_cli.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/__init__.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/assistant_render.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/codenames.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/config/__init__.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/config/model.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/config/picker.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/config/picker_state.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/config/store.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/env_utils.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/error_display.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/event_renderer.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/figures.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/fragment_utils.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/history_printer.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/input_geometry.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/logging_adapter.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/markdown_render.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/mention_completer.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/message_style.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/models.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/path_context_hint.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/question_view.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/resume_picker.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/resume_preview.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/resume_selector.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/selection_menu.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/startup.py +0 -0
- {comate_cli-0.7.0a11/tests/config → comate_cli-0.7.1/comate_cli/terminal_agent/statusline}/__init__.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/text_effects.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/tips.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/tool_fold.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/tool_result_formatters.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/tool_result_store.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/tool_view.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/transcript_viewer.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/tui_parts/history_sync.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/docs/hooks.md +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/docs/superpowers/plans/2026-04-03-phrase-shuffle.md +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/docs/superpowers/plans/2026-05-22-log-style-implementation-plan.md +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/docs/superpowers/specs/2026-04-01-conditional-diff-subtitle-design.md +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/docs/superpowers/specs/2026-04-03-phrase-shuffle-design.md +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/docs/superpowers/specs/2026-05-22-comate-cli-log-styling-design.md +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/docs/superpowers/specs/2026-05-22-log-style-optimization-design.md +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/report.md +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/config/test_model.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/config/test_picker_state.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/config/test_picker_ui.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/config/test_roundtrip.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/config/test_store_load.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/config/test_store_save.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/conftest.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_animator_shuffle.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_app_mcp_preload.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_app_preflight_gate.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_app_print_mode.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_app_shutdown.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_app_usage_line.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_btw_slash_command.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_cli_project_root.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_compact_command_semantics.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_completion_context_activation.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_context_command.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_custom_slash_commands.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_discover_tab.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_errors_tab.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_event_renderer.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_event_renderer_boundary.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_event_renderer_e2e.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_event_renderer_log_boundary.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_event_renderer_log_queue.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_event_renderer_tool_fold.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_format_error.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_handle_error.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_history_printer.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_history_printer_log.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_history_printer_subtitle_position.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_history_printer_tool_fold.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_history_sync.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_history_sync_tool_fold.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_input_behavior.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_input_history.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_installed_tab.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_layout_coordinator.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_logging_adapter.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_main_args.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_markdown_render.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_marketplaces_tab.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_mcp_cli.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_mcp_slash_command.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_mention_completer.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_path_context_hint.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_plugin_slash_commands.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_plugin_tui_components.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_preflight_copilot.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_question_key_bindings.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_question_view.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_resume_picker.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_resume_preview.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_resume_selector.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_rewind_command_semantics.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_rpc_protocol.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_rpc_stdio_bridge.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_selection_menu.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_session_query_token_summary.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_shutdown_noise_guard.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_shutdown_noise_integration.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_skills_slash_command.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_slash_argument_hint.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_slash_clear.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_slash_completer.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_slash_registry.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_status_bar_transient.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_task_panel_format.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_task_panel_rendering.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_task_poll.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_tool_fold.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_tool_fold_panel.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_tool_result_formatters.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_tool_result_store.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_tool_result_viewer.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_tool_result_viewer_key_bindings.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_tool_view.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_transcript_viewer.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_transcript_viewer_tool_fold.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_tui_elapsed_status.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_tui_esc_queue.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_tui_mcp_init_gate.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_tui_paste_newline_guard.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_tui_paste_placeholder.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_tui_queue_preview.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_tui_queue_sdk_source.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_tui_split_invariance.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_tui_team_messages.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_tui_thinking_display.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
- {comate_cli-0.7.0a11 → comate_cli-0.7.1}/tests/test_update_check.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: comate-cli
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Comate terminal CLI built on comate-agent-sdk
|
|
5
5
|
Project-URL: Homepage, https://github.com/AndyLee1024/agent-sdk
|
|
6
6
|
Project-URL: Repository, https://github.com/AndyLee1024/agent-sdk
|
|
@@ -253,10 +253,8 @@ LOADING_SPINNER_FRAMES: tuple[str, ...] = (
|
|
|
253
253
|
|
|
254
254
|
HIDDEN_THINKING_BADGES: tuple[str, ...] = (
|
|
255
255
|
"ultra thinking",
|
|
256
|
-
"deep
|
|
257
|
-
"thinking
|
|
258
|
-
"extended thinking",
|
|
259
|
-
"working through it",
|
|
256
|
+
"deep thinking",
|
|
257
|
+
"extended thinking"
|
|
260
258
|
)
|
|
261
259
|
|
|
262
260
|
|
|
@@ -394,6 +394,11 @@ async def run(
|
|
|
394
394
|
|
|
395
395
|
status_bar = StatusBar(session)
|
|
396
396
|
status_bar.set_mode(session.get_mode())
|
|
397
|
+
try:
|
|
398
|
+
from comate_cli.terminal_agent.statusline import store as statusline_store
|
|
399
|
+
status_bar.apply_config(statusline_store.load())
|
|
400
|
+
except Exception:
|
|
401
|
+
pass
|
|
397
402
|
if mode == "resume":
|
|
398
403
|
await status_bar.refresh()
|
|
399
404
|
|
|
@@ -48,7 +48,10 @@ def _format_directory_for_display(project_root: Path) -> str:
|
|
|
48
48
|
home = Path.home().expanduser().resolve()
|
|
49
49
|
if resolved_root == home:
|
|
50
50
|
return "~/"
|
|
51
|
-
|
|
51
|
+
try:
|
|
52
|
+
return f"~/{resolved_root.relative_to(home)}"
|
|
53
|
+
except ValueError:
|
|
54
|
+
return str(resolved_root)
|
|
52
55
|
|
|
53
56
|
|
|
54
57
|
def print_logo(console: Console, *, project_root: PathInput | None = None) -> None:
|
|
@@ -5,7 +5,7 @@ import logging
|
|
|
5
5
|
import os
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Any, Callable, Literal, Mapping
|
|
8
|
+
from typing import Any, Callable, Literal, Mapping, Sequence
|
|
9
9
|
|
|
10
10
|
from prompt_toolkit.application import Application
|
|
11
11
|
from prompt_toolkit.filters import Condition
|
|
@@ -33,6 +33,7 @@ from comate_agent_sdk.facade.config.providers import (
|
|
|
33
33
|
build_preview_payload,
|
|
34
34
|
evaluate_preflight_state,
|
|
35
35
|
merge_user_settings,
|
|
36
|
+
resolve_provider_presets,
|
|
36
37
|
)
|
|
37
38
|
|
|
38
39
|
from comate_cli.terminal_agent.figures import BULLET_OPERATOR
|
|
@@ -107,6 +108,7 @@ async def run_preflight_if_needed(
|
|
|
107
108
|
console: Console,
|
|
108
109
|
project_root: Path,
|
|
109
110
|
interactive: bool,
|
|
111
|
+
provider_presets: Sequence[ProviderPreset] | None = None,
|
|
110
112
|
) -> PreflightResult:
|
|
111
113
|
check = evaluate_preflight_state(project_root=project_root)
|
|
112
114
|
if not check.needs_preflight:
|
|
@@ -116,7 +118,11 @@ async def run_preflight_if_needed(
|
|
|
116
118
|
_print_preflight_block_message(console=console, check=check)
|
|
117
119
|
return PreflightResult(status="blocked_non_interactive", detail="missing_llm_config")
|
|
118
120
|
|
|
119
|
-
flow = await _run_preflight_wizard(
|
|
121
|
+
flow = await _run_preflight_wizard(
|
|
122
|
+
check=check,
|
|
123
|
+
project_root=project_root,
|
|
124
|
+
provider_presets=provider_presets,
|
|
125
|
+
)
|
|
120
126
|
if flow.cancel_detail is not None:
|
|
121
127
|
console.print("[yellow]Setup cancelled. Session was not started.[/]")
|
|
122
128
|
return PreflightResult(status="cancelled", detail=flow.cancel_detail)
|
|
@@ -216,9 +222,16 @@ class _PreflightWizardFlowResult:
|
|
|
216
222
|
|
|
217
223
|
|
|
218
224
|
class _PreflightWizard:
|
|
219
|
-
def __init__(
|
|
225
|
+
def __init__(
|
|
226
|
+
self,
|
|
227
|
+
*,
|
|
228
|
+
check: PreflightCheckResult,
|
|
229
|
+
project_root: Path,
|
|
230
|
+
provider_presets: Sequence[ProviderPreset] | None = None,
|
|
231
|
+
) -> None:
|
|
220
232
|
self._check = check
|
|
221
233
|
self._project_root = project_root
|
|
234
|
+
self._provider_presets = resolve_provider_presets(provider_presets)
|
|
222
235
|
self._app: Application | None = None
|
|
223
236
|
|
|
224
237
|
self._mode: Literal["selection", "input"] = "selection"
|
|
@@ -449,7 +462,7 @@ class _PreflightWizard:
|
|
|
449
462
|
def _open_provider_menu(self) -> None:
|
|
450
463
|
self._back_fn = self._open_entry_menu
|
|
451
464
|
ranked_presets = sorted(
|
|
452
|
-
enumerate(
|
|
465
|
+
enumerate(self._provider_presets),
|
|
453
466
|
key=lambda pair: (_PROVIDER_MENU_PRIORITY.get(pair[1].preset_id, 100), pair[0]),
|
|
454
467
|
)
|
|
455
468
|
options = [
|
|
@@ -472,7 +485,7 @@ class _PreflightWizard:
|
|
|
472
485
|
)
|
|
473
486
|
|
|
474
487
|
def _on_provider_selected(self, value: str) -> None:
|
|
475
|
-
selected = next((preset for preset in
|
|
488
|
+
selected = next((preset for preset in self._provider_presets if preset.preset_id == value), None)
|
|
476
489
|
if selected is None:
|
|
477
490
|
self._cancel_with_detail("provider_cancelled")
|
|
478
491
|
return
|
|
@@ -748,6 +761,11 @@ async def _run_preflight_wizard(
|
|
|
748
761
|
*,
|
|
749
762
|
check: PreflightCheckResult,
|
|
750
763
|
project_root: Path,
|
|
764
|
+
provider_presets: Sequence[ProviderPreset] | None = None,
|
|
751
765
|
) -> _PreflightWizardFlowResult:
|
|
752
|
-
wizard = _PreflightWizard(
|
|
766
|
+
wizard = _PreflightWizard(
|
|
767
|
+
check=check,
|
|
768
|
+
project_root=project_root,
|
|
769
|
+
provider_presets=provider_presets,
|
|
770
|
+
)
|
|
753
771
|
return await wizard.run()
|
|
@@ -137,6 +137,11 @@ SLASH_COMMAND_SPECS: tuple[SlashCommandSpec, ...] = (
|
|
|
137
137
|
description="Configure CLI preferences (thinking, model, compaction, memory)",
|
|
138
138
|
execution_kind="local",
|
|
139
139
|
),
|
|
140
|
+
SlashCommandSpec(
|
|
141
|
+
name="statusline",
|
|
142
|
+
description="Customize status bar display items",
|
|
143
|
+
execution_kind="local",
|
|
144
|
+
),
|
|
140
145
|
)
|
|
141
146
|
SLASH_COMMANDS: tuple[str, ...] = tuple(f"/{cmd.name}" for cmd in SLASH_COMMAND_SPECS)
|
|
142
147
|
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import subprocess
|
|
4
5
|
import time
|
|
6
|
+
from pathlib import Path
|
|
5
7
|
from typing import NamedTuple
|
|
6
8
|
|
|
7
9
|
from prompt_toolkit.application.current import get_app_or_none
|
|
8
10
|
|
|
9
11
|
from comate_agent_sdk.agent import ChatSession
|
|
10
12
|
|
|
13
|
+
from comate_cli.terminal_agent.statusline.model import StatuslineConfig
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
11
17
|
|
|
12
18
|
class GitDiffStats(NamedTuple):
|
|
13
19
|
added: int
|
|
@@ -19,6 +25,14 @@ class StatusBar:
|
|
|
19
25
|
_MIN_TERMINAL_WIDTH: int = 40
|
|
20
26
|
_GIT_DIFF_CACHE_SECONDS: float = 5.0
|
|
21
27
|
_GIT_CMD_TIMEOUT_SECONDS: float = 0.2
|
|
28
|
+
_SEPARATOR: str = " \u00b7 " # " · "
|
|
29
|
+
|
|
30
|
+
# 分组颜色
|
|
31
|
+
_COLOR_MODEL: str = "#E17B7D"
|
|
32
|
+
_COLOR_CONTEXT: str = "#DF8E5F"
|
|
33
|
+
_COLOR_PROJECT: str = "#9BC69B"
|
|
34
|
+
_COLOR_GIT: str = "#6B97C1"
|
|
35
|
+
_COLOR_SEPARATOR: str = "#6B7280"
|
|
22
36
|
|
|
23
37
|
def __init__(self, session: ChatSession):
|
|
24
38
|
self._session = session
|
|
@@ -32,7 +46,57 @@ class StatusBar:
|
|
|
32
46
|
self._transient_message: str | None = None
|
|
33
47
|
self._transient_severity: str = "info"
|
|
34
48
|
self._transient_until: float | None = None
|
|
49
|
+
self._config: StatuslineConfig = StatuslineConfig.default()
|
|
50
|
+
|
|
51
|
+
# Cached token data (updated during refresh)
|
|
52
|
+
self._cached_total_tokens: int | None = None
|
|
53
|
+
self._cached_prompt_tokens: int | None = None
|
|
54
|
+
self._cached_completion_tokens: int | None = None
|
|
55
|
+
self._cached_total_cost: float | None = None
|
|
56
|
+
|
|
57
|
+
# ---- config ---------------------------------------------------------
|
|
58
|
+
def apply_config(self, config: StatuslineConfig) -> None:
|
|
59
|
+
self._config = config
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def config(self) -> StatuslineConfig:
|
|
63
|
+
return self._config
|
|
64
|
+
|
|
65
|
+
def live_preview_data(self) -> list[tuple[str, str]]:
|
|
66
|
+
"""为 /statusline UI 提供实时预览数据。"""
|
|
67
|
+
from comate_agent_sdk.agent import ChatSession as _CS
|
|
68
|
+
|
|
69
|
+
agent = getattr(self._session, "_agent", None)
|
|
70
|
+
parts: list[tuple[str, str]] = []
|
|
35
71
|
|
|
72
|
+
parts.append(("show_model_name", self._model_name))
|
|
73
|
+
parts.append(("show_git_branch", f"~{self._git_branch}"))
|
|
74
|
+
parts.append(("show_context_left", self.context_left_text()))
|
|
75
|
+
parts.append(("show_git_diff", self._git_diff_preview()))
|
|
76
|
+
parts.append(("show_llm_level", self._resolve_llm_level()))
|
|
77
|
+
parts.append(("show_turn_number", self._resolve_turn_number()))
|
|
78
|
+
parts.append(("show_project_cwd", self._resolve_project_cwd()))
|
|
79
|
+
|
|
80
|
+
if self._cached_total_tokens is not None:
|
|
81
|
+
parts.append(("show_token_usage", f"{self._format_token_count(self._cached_total_tokens)} tok"))
|
|
82
|
+
else:
|
|
83
|
+
parts.append(("show_token_usage", "-"))
|
|
84
|
+
if self._cached_prompt_tokens is not None:
|
|
85
|
+
parts.append(("show_token_input", f"{self._format_token_count(self._cached_prompt_tokens)} in"))
|
|
86
|
+
else:
|
|
87
|
+
parts.append(("show_token_input", "-"))
|
|
88
|
+
if self._cached_completion_tokens is not None:
|
|
89
|
+
parts.append(("show_token_output", f"{self._format_token_count(self._cached_completion_tokens)} out"))
|
|
90
|
+
else:
|
|
91
|
+
parts.append(("show_token_output", "-"))
|
|
92
|
+
if self._cached_total_cost is not None:
|
|
93
|
+
parts.append(("show_session_cost", f"${self._cached_total_cost:.4f}"))
|
|
94
|
+
else:
|
|
95
|
+
parts.append(("show_session_cost", "-"))
|
|
96
|
+
|
|
97
|
+
return parts
|
|
98
|
+
|
|
99
|
+
# ---- data resolvers -------------------------------------------------
|
|
36
100
|
@staticmethod
|
|
37
101
|
def _resolve_model_name(session: ChatSession) -> str:
|
|
38
102
|
agent = getattr(session, "_agent", None)
|
|
@@ -101,17 +165,13 @@ class StatusBar:
|
|
|
101
165
|
if not output:
|
|
102
166
|
return None
|
|
103
167
|
|
|
104
|
-
# Parse output like "3 files changed, 12 insertions(+), 5 deletions(-)"
|
|
105
|
-
# or just "12 insertions(+), 5 deletions(-)"
|
|
106
168
|
added, removed = 0, 0
|
|
107
169
|
for part in output.split(","):
|
|
108
170
|
part = part.strip()
|
|
109
171
|
if "+" in part and "insertion" in part:
|
|
110
|
-
# e.g., "12 insertions(+)"
|
|
111
172
|
num_str = part.split()[0]
|
|
112
173
|
added = int(num_str)
|
|
113
174
|
elif "-" in part and "deletion" in part:
|
|
114
|
-
# e.g., "5 deletions(-)"
|
|
115
175
|
num_str = part.split()[0]
|
|
116
176
|
removed = int(num_str)
|
|
117
177
|
|
|
@@ -127,6 +187,41 @@ class StatusBar:
|
|
|
127
187
|
self._git_diff_stats = self._resolve_git_diff_stats()
|
|
128
188
|
self._git_diff_cache_time = now
|
|
129
189
|
|
|
190
|
+
def _resolve_llm_level(self) -> str:
|
|
191
|
+
agent = getattr(self._session, "_agent", None)
|
|
192
|
+
level = getattr(agent, "level", None)
|
|
193
|
+
return str(level) if level else "-"
|
|
194
|
+
|
|
195
|
+
def _resolve_turn_number(self) -> str:
|
|
196
|
+
agent = getattr(self._session, "_agent", None)
|
|
197
|
+
turn = getattr(agent, "_turn_number", None)
|
|
198
|
+
return f"#{turn}" if turn is not None else "-"
|
|
199
|
+
|
|
200
|
+
def _resolve_project_cwd(self) -> str:
|
|
201
|
+
cwd = getattr(self._session, "_cwd", None)
|
|
202
|
+
if cwd is None:
|
|
203
|
+
return "-"
|
|
204
|
+
p = Path(cwd)
|
|
205
|
+
home = Path.home()
|
|
206
|
+
try:
|
|
207
|
+
rel = p.relative_to(home)
|
|
208
|
+
return "~/" if rel == Path(".") else f"~/{rel}"
|
|
209
|
+
except ValueError:
|
|
210
|
+
return str(p)
|
|
211
|
+
|
|
212
|
+
def _git_diff_preview(self) -> str:
|
|
213
|
+
if self._git_diff_stats is None:
|
|
214
|
+
return "-"
|
|
215
|
+
a, r = self._git_diff_stats
|
|
216
|
+
if a == 0 and r == 0:
|
|
217
|
+
return "-"
|
|
218
|
+
parts: list[str] = []
|
|
219
|
+
if a > 0:
|
|
220
|
+
parts.append(f"+{a}")
|
|
221
|
+
if r > 0:
|
|
222
|
+
parts.append(f"-{r}")
|
|
223
|
+
return " ".join(parts) if parts else "-"
|
|
224
|
+
|
|
130
225
|
async def refresh(self) -> None:
|
|
131
226
|
try:
|
|
132
227
|
ctx_info = await self._session.get_context_info()
|
|
@@ -143,9 +238,21 @@ class StatusBar:
|
|
|
143
238
|
self._context_used_pct = normalized
|
|
144
239
|
self._context_left_pct = max(0.0, 100.0 - normalized)
|
|
145
240
|
|
|
146
|
-
# Refresh git diff stats off the render path; UI reads only the cache.
|
|
147
241
|
self._ensure_git_diff_stats()
|
|
148
242
|
|
|
243
|
+
# Refresh token usage cache if any token-related items are enabled
|
|
244
|
+
cfg = self._config
|
|
245
|
+
if cfg.show_token_usage or cfg.show_token_input or cfg.show_token_output or cfg.show_session_cost:
|
|
246
|
+
try:
|
|
247
|
+
usage = await self._session.get_usage()
|
|
248
|
+
self._cached_total_tokens = usage.total_tokens
|
|
249
|
+
self._cached_prompt_tokens = usage.total_prompt_tokens
|
|
250
|
+
self._cached_completion_tokens = usage.total_completion_tokens
|
|
251
|
+
self._cached_total_cost = usage.total_cost
|
|
252
|
+
except Exception:
|
|
253
|
+
pass
|
|
254
|
+
|
|
255
|
+
# ---- terminal width / truncation ------------------------------------
|
|
149
256
|
@classmethod
|
|
150
257
|
def _resolve_terminal_width(cls) -> int:
|
|
151
258
|
app = get_app_or_none()
|
|
@@ -192,18 +299,102 @@ class StatusBar:
|
|
|
192
299
|
def get_mode(self) -> str:
|
|
193
300
|
return self._mode
|
|
194
301
|
|
|
302
|
+
# ---- dynamic info_status_text ---------------------------------------
|
|
303
|
+
@staticmethod
|
|
304
|
+
def _format_token_count(n: int) -> str:
|
|
305
|
+
if n >= 1_000_000:
|
|
306
|
+
return f"{n / 1_000_000:.1f}M"
|
|
307
|
+
if n >= 1_000:
|
|
308
|
+
return f"{n / 1_000:.1f}k"
|
|
309
|
+
return str(n)
|
|
310
|
+
|
|
311
|
+
def _build_info_items(self) -> list[tuple[str, str]]:
|
|
312
|
+
"""按 config 开关和固定顺序构建带颜色的展示项列表。
|
|
313
|
+
|
|
314
|
+
返回 (style, text) 列表,style 是 prompt_toolkit fragment style 字符串。
|
|
315
|
+
"""
|
|
316
|
+
cfg = self._config
|
|
317
|
+
items: list[tuple[str, str]] = []
|
|
318
|
+
|
|
319
|
+
if cfg.show_model_name:
|
|
320
|
+
items.append((f"fg:{self._COLOR_MODEL}", self._model_name))
|
|
321
|
+
if cfg.show_llm_level:
|
|
322
|
+
items.append((f"fg:{self._COLOR_MODEL}", self._resolve_llm_level()))
|
|
323
|
+
if cfg.show_turn_number:
|
|
324
|
+
items.append((f"fg:{self._COLOR_CONTEXT}", self._resolve_turn_number()))
|
|
325
|
+
if cfg.show_project_cwd:
|
|
326
|
+
items.append((f"fg:{self._COLOR_PROJECT}", self._resolve_project_cwd()))
|
|
327
|
+
if cfg.show_git_branch:
|
|
328
|
+
items.append((f"fg:{self._COLOR_GIT}", f"~{self._git_branch}"))
|
|
329
|
+
if cfg.show_git_diff:
|
|
330
|
+
diff = self._git_diff_preview()
|
|
331
|
+
if diff != "-":
|
|
332
|
+
items.append((f"fg:{self._COLOR_GIT}", diff))
|
|
333
|
+
if cfg.show_context_left:
|
|
334
|
+
items.append((f"fg:{self._COLOR_CONTEXT}", self.context_left_text()))
|
|
335
|
+
if cfg.show_token_usage and self._cached_total_tokens is not None:
|
|
336
|
+
items.append((f"fg:{self._COLOR_CONTEXT}", f"{self._format_token_count(self._cached_total_tokens)} tok"))
|
|
337
|
+
if cfg.show_token_input and self._cached_prompt_tokens is not None:
|
|
338
|
+
items.append((f"fg:{self._COLOR_CONTEXT}", f"{self._format_token_count(self._cached_prompt_tokens)} in"))
|
|
339
|
+
if cfg.show_token_output and self._cached_completion_tokens is not None:
|
|
340
|
+
items.append((f"fg:{self._COLOR_CONTEXT}", f"{self._format_token_count(self._cached_completion_tokens)} out"))
|
|
341
|
+
if cfg.show_session_cost and self._cached_total_cost is not None:
|
|
342
|
+
items.append((f"fg:{self._COLOR_CONTEXT}", f"${self._cached_total_cost:.3f}"))
|
|
343
|
+
|
|
344
|
+
return items
|
|
345
|
+
|
|
346
|
+
def info_status_fragments(self) -> list[tuple[str, str]]:
|
|
347
|
+
"""带分组颜色的 statusline fragments,超宽时从尾部整项移除。"""
|
|
348
|
+
items = self._build_info_items()
|
|
349
|
+
if not items:
|
|
350
|
+
return []
|
|
351
|
+
|
|
352
|
+
sep_text = self._SEPARATOR
|
|
353
|
+
sep_style = f"fg:{self._COLOR_SEPARATOR}"
|
|
354
|
+
|
|
355
|
+
# 交替插入分隔符:item0, sep, item1, sep, item2, ...
|
|
356
|
+
fragments: list[tuple[str, str]] = []
|
|
357
|
+
for i, (style, text) in enumerate(items):
|
|
358
|
+
if i > 0:
|
|
359
|
+
fragments.append((sep_style, sep_text))
|
|
360
|
+
fragments.append((style, text))
|
|
361
|
+
|
|
362
|
+
width = self._resolve_terminal_width()
|
|
363
|
+
budget = max(0, width - 2)
|
|
364
|
+
total_len = sum(len(text) for _, text in fragments)
|
|
365
|
+
if total_len <= budget:
|
|
366
|
+
return fragments
|
|
367
|
+
|
|
368
|
+
# 从尾部移除整项(含前缀分隔符)直到 fits
|
|
369
|
+
while items and total_len > budget:
|
|
370
|
+
removed_style, removed_text = items.pop()
|
|
371
|
+
total_len -= len(removed_text)
|
|
372
|
+
# 同时移除前一个分隔符
|
|
373
|
+
if items:
|
|
374
|
+
total_len -= len(sep_text)
|
|
375
|
+
if not items:
|
|
376
|
+
return []
|
|
377
|
+
|
|
378
|
+
# 重建 fragments
|
|
379
|
+
fragments = []
|
|
380
|
+
for i, (style, text) in enumerate(items):
|
|
381
|
+
if i > 0:
|
|
382
|
+
fragments.append((sep_style, sep_text))
|
|
383
|
+
fragments.append((style, text))
|
|
384
|
+
return fragments
|
|
385
|
+
|
|
195
386
|
def info_status_text(self) -> str:
|
|
196
|
-
"""
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
387
|
+
"""按 config 动态组装纯文本展示项(用于 picker 预览等无颜色场景),用 · 分隔。"""
|
|
388
|
+
items = self._build_info_items()
|
|
389
|
+
if not items:
|
|
390
|
+
return ""
|
|
391
|
+
parts = [text for _, text in items]
|
|
392
|
+
full_text = self._SEPARATOR.join(parts)
|
|
200
393
|
width = self._resolve_terminal_width()
|
|
201
|
-
budget = max(
|
|
394
|
+
budget = max(0, width - 2)
|
|
202
395
|
if len(full_text) <= budget:
|
|
203
396
|
return full_text
|
|
204
|
-
|
|
205
|
-
prefix_budget = max(0, budget - len(context_text))
|
|
206
|
-
return f"{self._truncate_text(prefix, prefix_budget)}{context_text}"
|
|
397
|
+
return self._truncate_text(full_text, budget)
|
|
207
398
|
|
|
208
399
|
def right_prompt_text(self) -> str:
|
|
209
400
|
return self._status_text_for_width(self._right_prompt_budget())
|
|
@@ -217,6 +408,8 @@ class StatusBar:
|
|
|
217
408
|
return self._status_text_for_width(content_budget)
|
|
218
409
|
|
|
219
410
|
def _git_diff_fragments(self) -> list[tuple[str, str]]:
|
|
411
|
+
if not self._config.show_git_diff:
|
|
412
|
+
return []
|
|
220
413
|
if self._git_diff_stats is None or (
|
|
221
414
|
self._git_diff_stats.added == 0 and self._git_diff_stats.removed == 0
|
|
222
415
|
):
|
|
@@ -230,10 +423,7 @@ class StatusBar:
|
|
|
230
423
|
return parts
|
|
231
424
|
|
|
232
425
|
def git_diff_fragments(self) -> list[tuple[str, str]]:
|
|
233
|
-
"""Prompt-toolkit fragments for git diff stats.
|
|
234
|
-
|
|
235
|
-
Working tree only (unstaged): `git diff --shortstat`.
|
|
236
|
-
"""
|
|
426
|
+
"""Prompt-toolkit fragments for git diff stats."""
|
|
237
427
|
return self._git_diff_fragments()
|
|
238
428
|
|
|
239
429
|
def footer_toolbar(self) -> list[tuple[str, str]]:
|
|
@@ -241,8 +431,7 @@ class StatusBar:
|
|
|
241
431
|
status_text = self.footer_status_text()
|
|
242
432
|
git_fragments = self._git_diff_fragments()
|
|
243
433
|
|
|
244
|
-
|
|
245
|
-
git_len = sum(len(text) + 1 for _, text in git_fragments) # +1 for space
|
|
434
|
+
git_len = sum(len(text) + 1 for _, text in git_fragments)
|
|
246
435
|
left_padding = max(0, width - len(status_text) - git_len - 1)
|
|
247
436
|
|
|
248
437
|
fragments: list[tuple[str, str]] = [
|
|
@@ -250,9 +439,8 @@ class StatusBar:
|
|
|
250
439
|
("", status_text),
|
|
251
440
|
]
|
|
252
441
|
|
|
253
|
-
# Add git diff stats with colors
|
|
254
442
|
if git_fragments:
|
|
255
|
-
fragments.append(("", " "))
|
|
443
|
+
fragments.append(("", " "))
|
|
256
444
|
for class_name, text in git_fragments:
|
|
257
445
|
fragments.append((class_name, text))
|
|
258
446
|
|
|
@@ -267,11 +455,7 @@ class StatusBar:
|
|
|
267
455
|
self._transient_until = time.monotonic() + duration_s
|
|
268
456
|
|
|
269
457
|
def clear_transient_if_expired(self) -> bool:
|
|
270
|
-
"""Check and clear expired transient message.
|
|
271
|
-
|
|
272
|
-
Returns True if the message was just cleared (state changed, needs repaint).
|
|
273
|
-
Returns False otherwise (no message, or message still active).
|
|
274
|
-
"""
|
|
458
|
+
"""Check and clear expired transient message."""
|
|
275
459
|
if self._transient_until is not None and time.monotonic() >= self._transient_until:
|
|
276
460
|
self._transient_message = None
|
|
277
461
|
self._transient_severity = "info"
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""StatuslineConfig:statusline 展示项开关的不可变快照。"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass, replace
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True, slots=True)
|
|
8
|
+
class StatuslineConfig:
|
|
9
|
+
"""11 项 statusline 展示项开关。"""
|
|
10
|
+
|
|
11
|
+
show_model_name: bool = True
|
|
12
|
+
show_git_branch: bool = True
|
|
13
|
+
show_context_left: bool = True
|
|
14
|
+
show_token_usage: bool = False
|
|
15
|
+
show_token_input: bool = False
|
|
16
|
+
show_token_output: bool = False
|
|
17
|
+
show_project_cwd: bool = False
|
|
18
|
+
show_llm_level: bool = False
|
|
19
|
+
show_turn_number: bool = False
|
|
20
|
+
show_git_diff: bool = False
|
|
21
|
+
show_session_cost: bool = False
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def default(cls) -> StatuslineConfig:
|
|
25
|
+
return cls()
|
|
26
|
+
|
|
27
|
+
# ---- with_* mutators (返回新实例) ----
|
|
28
|
+
|
|
29
|
+
def with_show_model_name(self, v: bool) -> StatuslineConfig:
|
|
30
|
+
return replace(self, show_model_name=v)
|
|
31
|
+
|
|
32
|
+
def with_show_git_branch(self, v: bool) -> StatuslineConfig:
|
|
33
|
+
return replace(self, show_git_branch=v)
|
|
34
|
+
|
|
35
|
+
def with_show_context_left(self, v: bool) -> StatuslineConfig:
|
|
36
|
+
return replace(self, show_context_left=v)
|
|
37
|
+
|
|
38
|
+
def with_show_token_usage(self, v: bool) -> StatuslineConfig:
|
|
39
|
+
return replace(self, show_token_usage=v)
|
|
40
|
+
|
|
41
|
+
def with_show_token_input(self, v: bool) -> StatuslineConfig:
|
|
42
|
+
return replace(self, show_token_input=v)
|
|
43
|
+
|
|
44
|
+
def with_show_token_output(self, v: bool) -> StatuslineConfig:
|
|
45
|
+
return replace(self, show_token_output=v)
|
|
46
|
+
|
|
47
|
+
def with_show_project_cwd(self, v: bool) -> StatuslineConfig:
|
|
48
|
+
return replace(self, show_project_cwd=v)
|
|
49
|
+
|
|
50
|
+
def with_show_llm_level(self, v: bool) -> StatuslineConfig:
|
|
51
|
+
return replace(self, show_llm_level=v)
|
|
52
|
+
|
|
53
|
+
def with_show_turn_number(self, v: bool) -> StatuslineConfig:
|
|
54
|
+
return replace(self, show_turn_number=v)
|
|
55
|
+
|
|
56
|
+
def with_show_git_diff(self, v: bool) -> StatuslineConfig:
|
|
57
|
+
return replace(self, show_git_diff=v)
|
|
58
|
+
|
|
59
|
+
def with_show_session_cost(self, v: bool) -> StatuslineConfig:
|
|
60
|
+
return replace(self, show_session_cost=v)
|