comate-cli 0.7.4__tar.gz → 0.7.6__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.4 → comate_cli-0.7.6}/PKG-INFO +2 -2
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/app.py +103 -28
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/startup_profile.py +22 -8
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/status_bar.py +30 -10
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui.py +20 -2
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/update_check.py +127 -38
- {comate_cli-0.7.4 → comate_cli-0.7.6}/pyproject.toml +2 -2
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_app_startup_latency.py +48 -32
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_completion_status_panel.py +1 -1
- comate_cli-0.7.6/tests/test_startup_profile.py +435 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_status_bar.py +18 -4
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_startup_latency.py +4 -2
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_update_check.py +212 -9
- {comate_cli-0.7.4 → comate_cli-0.7.6}/uv.lock +3 -415
- comate_cli-0.7.4/tests/test_startup_profile.py +0 -216
- {comate_cli-0.7.4 → comate_cli-0.7.6}/.gitignore +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/CHANGELOG.md +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/README.md +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/__init__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/__main__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/main.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/mcp_cli.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/__init__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/animations.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/assistant_render.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/codenames.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/config/__init__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/config/model.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/config/picker.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/config/picker_state.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/config/store.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/env_utils.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/error_display.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/event_renderer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/figures.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/fragment_utils.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/goal_resume_view.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/history_printer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/input_geometry.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/logging_adapter.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/logo.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/markdown_render.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/mention_completer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/message_style.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/models.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/path_context_hint.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/preflight.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/question_view.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/resume_picker.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/resume_preview.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/resume_selector.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/selection_menu.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/slash_commands.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/startup.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/statusline/__init__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/statusline/model.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/statusline/picker.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/statusline/picker_state.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/statusline/store.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/text_effects.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tips.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tool_fold.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tool_result_formatters.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tool_result_store.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tool_view.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/transcript_viewer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/commands.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/history_sync.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/key_bindings.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/render_panels.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/config/__init__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/config/test_model.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/config/test_picker_state.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/config/test_picker_ui.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/config/test_roundtrip.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/config/test_store_load.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/config/test_store_save.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/conftest.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/statusline/__init__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/statusline/test_model.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/statusline/test_picker_state.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/statusline/test_store.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_animator_shuffle.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_app_mcp_preload.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_app_preflight_gate.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_app_print_mode.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_app_shutdown.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_app_token_cost_config.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_app_usage_line.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_btw_slash_command.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_cli_project_root.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_compact_command_semantics.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_completion_context_activation.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_context_command.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_custom_slash_commands.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_discover_tab.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_errors_tab.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_event_renderer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_event_renderer_boundary.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_event_renderer_e2e.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_event_renderer_log_boundary.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_event_renderer_log_queue.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_event_renderer_streaming.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_event_renderer_tool_fold.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_format_error.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_goal_resume_tui.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_goal_resume_view.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_goal_slash_command.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_handle_error.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_history_printer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_history_printer_log.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_history_printer_subtitle_position.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_history_printer_tool_fold.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_history_sync.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_history_sync_tool_fold.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_input_behavior.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_input_history.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_installed_tab.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_interrupt_exit_semantics.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_layout_coordinator.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_logging_adapter.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_logo.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_main_args.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_markdown_render.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_marketplaces_tab.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_mcp_cli.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_mcp_slash_command.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_mention_completer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_path_context_hint.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_plugin_slash_commands.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_plugin_tui_components.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_preflight.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_preflight_copilot.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_question_key_bindings.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_question_view.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_resume_picker.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_resume_preview.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_resume_selector.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_rewind_command_semantics.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_rpc_protocol.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_rpc_stdio_bridge.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_selection_menu.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_session_query_token_summary.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_shutdown_noise_guard.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_shutdown_noise_integration.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_skills_slash_command.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_slash_argument_hint.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_slash_clear.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_slash_completer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_slash_registry.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_status_bar_transient.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_task_panel_format.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_task_panel_key_bindings.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_task_panel_rendering.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_task_poll.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tool_fold.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tool_fold_panel.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tool_result_formatters.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tool_result_store.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tool_result_viewer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tool_result_viewer_key_bindings.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tool_view.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_transcript_viewer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_transcript_viewer_tool_fold.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_elapsed_status.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_esc_queue.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_mcp_init_gate.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_paste_newline_guard.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_paste_placeholder.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_queue_preview.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_queue_sdk_source.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_split_invariance.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_team_messages.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_thinking_display.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_usage_command.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.6
|
|
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
|
|
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
-
Requires-Python:
|
|
16
|
+
Requires-Python: <3.14,>=3.11
|
|
17
17
|
Requires-Dist: charset-normalizer==3.4.7
|
|
18
18
|
Requires-Dist: comate-agent-sdk<0.9.0,>=0.8.0a1
|
|
19
19
|
Requires-Dist: concurrent-log-handler>=0.9.25
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
6
|
-
import random
|
|
6
|
+
import random # noqa: F401 - tests patch this module-level import for deterministic startup tips.
|
|
7
7
|
import signal
|
|
8
8
|
import sys
|
|
9
9
|
import threading
|
|
@@ -42,6 +42,7 @@ from comate_cli.terminal_agent.update_check import (
|
|
|
42
42
|
|
|
43
43
|
console = Console()
|
|
44
44
|
logger = logging.getLogger(__name__)
|
|
45
|
+
_UPDATE_CHECK_DELAY_S = 1.0
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
def _resolve_cli_project_root() -> Path:
|
|
@@ -49,8 +50,14 @@ def _resolve_cli_project_root() -> Path:
|
|
|
49
50
|
return Path.cwd().expanduser().resolve()
|
|
50
51
|
|
|
51
52
|
|
|
52
|
-
async def _check_update() -> UpdateInfo | None:
|
|
53
|
-
|
|
53
|
+
async def _check_update(*, profiler: StartupProfiler | None = None) -> UpdateInfo | None:
|
|
54
|
+
if profiler is not None:
|
|
55
|
+
profiler.mark("impl.start")
|
|
56
|
+
try:
|
|
57
|
+
return await check_update(log=logger, profiler=profiler)
|
|
58
|
+
finally:
|
|
59
|
+
if profiler is not None:
|
|
60
|
+
profiler.mark("impl.done")
|
|
54
61
|
|
|
55
62
|
|
|
56
63
|
async def _handle_update_on_launch(info: UpdateInfo) -> bool:
|
|
@@ -63,7 +70,7 @@ async def _handle_update_on_launch(info: UpdateInfo) -> bool:
|
|
|
63
70
|
if decision == UpdatePromptDecision.SKIP:
|
|
64
71
|
return False
|
|
65
72
|
if decision == UpdatePromptDecision.SHOW_HINT:
|
|
66
|
-
console.print(format_update_hint(info))
|
|
73
|
+
console.print(f"[dim]{format_update_hint(info)}[/]")
|
|
67
74
|
mark_update_seen(info)
|
|
68
75
|
return False
|
|
69
76
|
|
|
@@ -109,7 +116,10 @@ def _schedule_update_check_on_launch(
|
|
|
109
116
|
async def _run() -> None:
|
|
110
117
|
try:
|
|
111
118
|
profiler.mark("update_check.start")
|
|
112
|
-
|
|
119
|
+
profiler.mark("update_check.delay.start")
|
|
120
|
+
await asyncio.sleep(_UPDATE_CHECK_DELAY_S)
|
|
121
|
+
profiler.mark("update_check.delay.done")
|
|
122
|
+
update_info = await _check_update(profiler=profiler.child("update_check"))
|
|
113
123
|
profiler.mark("update_check.done")
|
|
114
124
|
if update_info is not None:
|
|
115
125
|
await _handle_background_update_on_launch(
|
|
@@ -228,35 +238,64 @@ async def add(a: int, b: int) -> int:
|
|
|
228
238
|
return a + b
|
|
229
239
|
|
|
230
240
|
|
|
231
|
-
def _build_agent(
|
|
241
|
+
def _build_agent(
|
|
242
|
+
*,
|
|
243
|
+
project_root: Path | None = None,
|
|
244
|
+
profiler: StartupProfiler | None = None,
|
|
245
|
+
) -> Agent:
|
|
232
246
|
from comate_agent_sdk.agent.compaction import CompactionConfig
|
|
233
247
|
from comate_cli.terminal_agent.config import store
|
|
234
248
|
|
|
235
249
|
resolved_project_root = project_root or _resolve_cli_project_root()
|
|
250
|
+
if profiler is not None:
|
|
251
|
+
profiler.mark("agent.config.load.start")
|
|
236
252
|
snapshot = store.load()
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
)
|
|
253
|
+
if profiler is not None:
|
|
254
|
+
profiler.mark("agent.config.load.done")
|
|
255
|
+
|
|
256
|
+
agent_config = AgentConfig(
|
|
257
|
+
role="software_engineering",
|
|
258
|
+
cwd=resolved_project_root,
|
|
259
|
+
env_options=EnvOptions(system_env=True, git_env=True),
|
|
260
|
+
use_streaming_task=True,
|
|
261
|
+
include_cost=bool(getattr(snapshot, "token_cost_enabled", False)),
|
|
262
|
+
memory_background_enabled=snapshot.memory_background_enabled,
|
|
263
|
+
memory_dreaming_enabled=snapshot.memory_dreaming_enabled,
|
|
264
|
+
compaction=CompactionConfig(
|
|
265
|
+
threshold_ratio=snapshot.compaction_threshold_ratio,
|
|
266
|
+
),
|
|
251
267
|
)
|
|
268
|
+
if profiler is not None:
|
|
269
|
+
profiler.mark("agent.construct.start")
|
|
270
|
+
try:
|
|
271
|
+
return Agent(config=agent_config)
|
|
272
|
+
finally:
|
|
273
|
+
if profiler is not None:
|
|
274
|
+
profiler.mark("agent.construct.done")
|
|
252
275
|
|
|
253
276
|
|
|
254
277
|
def _resolve_session(
|
|
255
|
-
agent: Agent,
|
|
278
|
+
agent: Agent,
|
|
279
|
+
resume_session_id: str | None,
|
|
280
|
+
*,
|
|
281
|
+
cwd: Path | None = None,
|
|
282
|
+
profiler: StartupProfiler | None = None,
|
|
256
283
|
) -> tuple[ChatSession, str]:
|
|
257
284
|
if resume_session_id:
|
|
258
|
-
|
|
259
|
-
|
|
285
|
+
if profiler is not None:
|
|
286
|
+
profiler.mark("session.resume.construct.start")
|
|
287
|
+
try:
|
|
288
|
+
return ChatSession.resume(agent, session_id=resume_session_id, cwd=cwd), "resume"
|
|
289
|
+
finally:
|
|
290
|
+
if profiler is not None:
|
|
291
|
+
profiler.mark("session.resume.construct.done")
|
|
292
|
+
if profiler is not None:
|
|
293
|
+
profiler.mark("session.new.construct.start")
|
|
294
|
+
try:
|
|
295
|
+
return ChatSession(agent, cwd=cwd), "new"
|
|
296
|
+
finally:
|
|
297
|
+
if profiler is not None:
|
|
298
|
+
profiler.mark("session.new.construct.done")
|
|
260
299
|
|
|
261
300
|
|
|
262
301
|
def _format_exit_usage_line(usage: object) -> str:
|
|
@@ -287,23 +326,47 @@ def _format_resume_hint(session_id: str | None) -> str | None:
|
|
|
287
326
|
)
|
|
288
327
|
|
|
289
328
|
|
|
290
|
-
async def _preload_mcp_in_tui(
|
|
329
|
+
async def _preload_mcp_in_tui(
|
|
330
|
+
session: ChatSession,
|
|
331
|
+
*,
|
|
332
|
+
profiler: StartupProfiler | None = None,
|
|
333
|
+
) -> None:
|
|
291
334
|
"""在 TUI 内异步加载 MCP,初始化阶段不输出 scrollback 文案。"""
|
|
335
|
+
if profiler is not None:
|
|
336
|
+
profiler.mark("preload.start")
|
|
292
337
|
runtime = session.runtime
|
|
293
338
|
if not bool(runtime.config.mcp_enabled):
|
|
339
|
+
if profiler is not None:
|
|
340
|
+
profiler.mark("preload.skip_disabled")
|
|
294
341
|
return
|
|
295
342
|
|
|
296
343
|
try:
|
|
344
|
+
if profiler is not None:
|
|
345
|
+
profiler.mark("start_preload.start")
|
|
297
346
|
preload_task = runtime.start_mcp_preload()
|
|
347
|
+
if profiler is not None:
|
|
348
|
+
profiler.mark("start_preload.done")
|
|
298
349
|
if preload_task is None:
|
|
350
|
+
if profiler is not None:
|
|
351
|
+
profiler.mark("preload.no_task")
|
|
299
352
|
return
|
|
353
|
+
if profiler is not None:
|
|
354
|
+
profiler.mark("await_preload.start")
|
|
300
355
|
await preload_task
|
|
356
|
+
if profiler is not None:
|
|
357
|
+
profiler.mark("await_preload.done")
|
|
301
358
|
except Exception as e:
|
|
302
359
|
logger.debug(f"MCP init failed: {e}", exc_info=True)
|
|
360
|
+
if profiler is not None:
|
|
361
|
+
profiler.mark("preload.failed")
|
|
303
362
|
return
|
|
304
363
|
|
|
364
|
+
if profiler is not None:
|
|
365
|
+
profiler.mark("inspect_manager.start")
|
|
305
366
|
mgr = runtime._mcp_manager
|
|
306
367
|
if mgr is None:
|
|
368
|
+
if profiler is not None:
|
|
369
|
+
profiler.mark("inspect_manager.no_manager")
|
|
307
370
|
return
|
|
308
371
|
|
|
309
372
|
for alias, reason in mgr.failed_servers:
|
|
@@ -314,6 +377,8 @@ async def _preload_mcp_in_tui(session: ChatSession) -> None:
|
|
|
314
377
|
count = len(loaded)
|
|
315
378
|
aliases = sorted({i.server_alias for i in loaded})
|
|
316
379
|
logger.info(f"MCP Server loaded: {', '.join(aliases)} ({count} tools)")
|
|
380
|
+
if profiler is not None:
|
|
381
|
+
profiler.mark("inspect_manager.done")
|
|
317
382
|
|
|
318
383
|
|
|
319
384
|
async def _run_print_mode(
|
|
@@ -404,7 +469,7 @@ async def run(
|
|
|
404
469
|
return
|
|
405
470
|
|
|
406
471
|
profiler.mark("agent.build.start")
|
|
407
|
-
agent = _build_agent(project_root=project_root)
|
|
472
|
+
agent = _build_agent(project_root=project_root, profiler=profiler)
|
|
408
473
|
profiler.mark("agent.build.done")
|
|
409
474
|
|
|
410
475
|
if rpc_stdio and resume_select and not resume_session_id:
|
|
@@ -412,7 +477,12 @@ async def run(
|
|
|
412
477
|
return
|
|
413
478
|
|
|
414
479
|
if rpc_stdio:
|
|
415
|
-
session, _mode = _resolve_session(
|
|
480
|
+
session, _mode = _resolve_session(
|
|
481
|
+
agent,
|
|
482
|
+
resume_session_id,
|
|
483
|
+
cwd=project_root,
|
|
484
|
+
profiler=profiler,
|
|
485
|
+
)
|
|
416
486
|
bridge = StdioRPCBridge(session)
|
|
417
487
|
try:
|
|
418
488
|
await bridge.run()
|
|
@@ -441,7 +511,12 @@ async def run(
|
|
|
441
511
|
profiler.mark("logging.setup.done")
|
|
442
512
|
|
|
443
513
|
profiler.mark("session.resolve.start")
|
|
444
|
-
session, mode = _resolve_session(
|
|
514
|
+
session, mode = _resolve_session(
|
|
515
|
+
agent,
|
|
516
|
+
resume_session_id,
|
|
517
|
+
cwd=project_root,
|
|
518
|
+
profiler=profiler,
|
|
519
|
+
)
|
|
445
520
|
profiler.mark("session.resolve.done")
|
|
446
521
|
# setup_tui_logging 在 _resolve_session 前安装,用于捕获 session 初始化期 warning/error。
|
|
447
522
|
# 这些日志没有交互 anchor,需在恢复历史或首次用户输入前落为 standalone log。
|
|
@@ -469,7 +544,7 @@ async def run(
|
|
|
469
544
|
)
|
|
470
545
|
|
|
471
546
|
async def _mcp_loader() -> None:
|
|
472
|
-
await _preload_mcp_in_tui(session)
|
|
547
|
+
await _preload_mcp_in_tui(session, profiler=profiler.child("mcp"))
|
|
473
548
|
mgr = session.runtime._mcp_manager
|
|
474
549
|
if mgr and mgr.failed_servers:
|
|
475
550
|
aliases = [alias for alias, _ in mgr.failed_servers]
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
|
+
import threading
|
|
5
6
|
import time
|
|
6
7
|
|
|
7
8
|
|
|
@@ -17,12 +18,14 @@ class StartupProfiler:
|
|
|
17
18
|
started_at: float | None = None,
|
|
18
19
|
prefix: str = "",
|
|
19
20
|
pending_messages: list[str] | None = None,
|
|
21
|
+
lock: threading.Lock | None = None,
|
|
20
22
|
) -> None:
|
|
21
23
|
self._logger = logger
|
|
22
24
|
self._enabled = enabled
|
|
23
25
|
self._started_at = started_at if started_at is not None else time.perf_counter()
|
|
24
26
|
self._prefix = prefix.strip(".")
|
|
25
27
|
self._pending_messages = pending_messages if pending_messages is not None else []
|
|
28
|
+
self._lock = lock if lock is not None else threading.Lock()
|
|
26
29
|
|
|
27
30
|
@classmethod
|
|
28
31
|
def from_env(cls, *, logger: logging.Logger) -> "StartupProfiler":
|
|
@@ -37,20 +40,30 @@ class StartupProfiler:
|
|
|
37
40
|
if self._prefix:
|
|
38
41
|
normalized_phase = f"{self._prefix}.{normalized_phase}"
|
|
39
42
|
elapsed_ms = (time.perf_counter() - self._started_at) * 1000
|
|
40
|
-
self.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
with self._lock:
|
|
44
|
+
self._pending_messages.append(
|
|
45
|
+
f"startup_profile phase={normalized_phase} elapsed_ms={elapsed_ms:.3f}"
|
|
46
|
+
)
|
|
47
|
+
pending = self._drain_pending_locked()
|
|
48
|
+
for message in pending:
|
|
49
|
+
self._logger.info(message)
|
|
44
50
|
|
|
45
51
|
def flush(self) -> None:
|
|
46
|
-
if not self._enabled
|
|
52
|
+
if not self._enabled:
|
|
47
53
|
return
|
|
54
|
+
with self._lock:
|
|
55
|
+
pending = self._drain_pending_locked()
|
|
56
|
+
for message in pending:
|
|
57
|
+
self._logger.info(message)
|
|
58
|
+
|
|
59
|
+
def _drain_pending_locked(self) -> list[str]:
|
|
60
|
+
if not self._pending_messages:
|
|
61
|
+
return []
|
|
48
62
|
if not self._logger.isEnabledFor(logging.INFO):
|
|
49
|
-
return
|
|
63
|
+
return []
|
|
50
64
|
pending = list(self._pending_messages)
|
|
51
65
|
self._pending_messages.clear()
|
|
52
|
-
|
|
53
|
-
self._logger.info(message)
|
|
66
|
+
return pending
|
|
54
67
|
|
|
55
68
|
def child(self, prefix: str) -> "StartupProfiler":
|
|
56
69
|
normalized_prefix = prefix.strip(".")
|
|
@@ -64,4 +77,5 @@ class StartupProfiler:
|
|
|
64
77
|
started_at=self._started_at,
|
|
65
78
|
prefix=normalized_prefix,
|
|
66
79
|
pending_messages=self._pending_messages,
|
|
80
|
+
lock=self._lock,
|
|
67
81
|
)
|
|
@@ -206,6 +206,25 @@ class StatusBar:
|
|
|
206
206
|
turn = getattr(agent, "_turn_number", None)
|
|
207
207
|
return f"#{turn}" if turn is not None else "-"
|
|
208
208
|
|
|
209
|
+
def _context_percent_refresh_started(self) -> bool:
|
|
210
|
+
for owner in (
|
|
211
|
+
self._session,
|
|
212
|
+
getattr(self._session, "_agent", None),
|
|
213
|
+
getattr(getattr(self._session, "_agent", None), "_context", None),
|
|
214
|
+
):
|
|
215
|
+
if owner is None:
|
|
216
|
+
continue
|
|
217
|
+
turn = getattr(owner, "_turn_number", None)
|
|
218
|
+
if turn is None:
|
|
219
|
+
turn = getattr(owner, "turn_number", None)
|
|
220
|
+
if turn is None:
|
|
221
|
+
continue
|
|
222
|
+
try:
|
|
223
|
+
return int(turn) > 0
|
|
224
|
+
except (TypeError, ValueError):
|
|
225
|
+
continue
|
|
226
|
+
return True
|
|
227
|
+
|
|
209
228
|
def _resolve_project_cwd(self) -> str:
|
|
210
229
|
cwd = getattr(self._session, "_cwd", None)
|
|
211
230
|
if cwd is None:
|
|
@@ -260,21 +279,22 @@ class StatusBar:
|
|
|
260
279
|
return " ".join(parts) if parts else "-"
|
|
261
280
|
|
|
262
281
|
async def refresh(self) -> None:
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
282
|
+
if self._context_percent_refresh_started():
|
|
283
|
+
try:
|
|
284
|
+
ctx_info = await self._session.get_context_info()
|
|
285
|
+
utilization = float(getattr(ctx_info, "utilization_percent", 0.0))
|
|
286
|
+
except Exception:
|
|
287
|
+
return
|
|
288
|
+
|
|
289
|
+
normalized = max(0.0, min(utilization, 100.0))
|
|
290
|
+
self._context_used_pct = normalized
|
|
291
|
+
self._context_left_pct = max(0.0, 100.0 - normalized)
|
|
268
292
|
|
|
269
293
|
try:
|
|
270
294
|
self._mode = str(self._session.get_mode()).strip().lower() or "act"
|
|
271
295
|
except Exception:
|
|
272
296
|
pass
|
|
273
297
|
|
|
274
|
-
normalized = max(0.0, min(utilization, 100.0))
|
|
275
|
-
self._context_used_pct = normalized
|
|
276
|
-
self._context_left_pct = max(0.0, 100.0 - normalized)
|
|
277
|
-
|
|
278
298
|
branch_task = asyncio.to_thread(self._resolve_git_branch)
|
|
279
299
|
now = time.monotonic()
|
|
280
300
|
diff_is_stale = (
|
|
@@ -338,7 +358,7 @@ class StatusBar:
|
|
|
338
358
|
return max(24, min(width - 6, 72))
|
|
339
359
|
|
|
340
360
|
def context_left_text(self) -> str:
|
|
341
|
-
return f"{self._context_left_pct:.0f}%
|
|
361
|
+
return f"Context {self._context_left_pct:.0f}% left"
|
|
342
362
|
|
|
343
363
|
def _status_text_for_width(self, width: int) -> str:
|
|
344
364
|
mode_text = f"[{self._mode}]"
|
|
@@ -1770,7 +1770,7 @@ class TerminalAgentTUI(
|
|
|
1770
1770
|
try:
|
|
1771
1771
|
if profiler is not None:
|
|
1772
1772
|
profiler.mark("plugins.init.start")
|
|
1773
|
-
await self._init_plugins()
|
|
1773
|
+
await self._init_plugins(profiler=profiler.child("plugins") if profiler is not None else None)
|
|
1774
1774
|
if profiler is not None:
|
|
1775
1775
|
profiler.mark("plugins.init.done")
|
|
1776
1776
|
except asyncio.CancelledError:
|
|
@@ -1784,6 +1784,8 @@ class TerminalAgentTUI(
|
|
|
1784
1784
|
finally:
|
|
1785
1785
|
self._refresh_layers()
|
|
1786
1786
|
|
|
1787
|
+
if profiler is not None:
|
|
1788
|
+
profiler.mark("plugins.task.create")
|
|
1787
1789
|
self._plugin_init_task = asyncio.create_task(
|
|
1788
1790
|
_do_plugin_init(),
|
|
1789
1791
|
name="terminal-plugin-init",
|
|
@@ -1803,11 +1805,15 @@ class TerminalAgentTUI(
|
|
|
1803
1805
|
finally:
|
|
1804
1806
|
self._refresh_layers()
|
|
1805
1807
|
|
|
1808
|
+
if profiler is not None:
|
|
1809
|
+
profiler.mark("mcp.task.create")
|
|
1806
1810
|
self._mcp_init_task = asyncio.create_task(
|
|
1807
1811
|
_do_init(),
|
|
1808
1812
|
name="terminal-mcp-init",
|
|
1809
1813
|
)
|
|
1810
1814
|
|
|
1815
|
+
if profiler is not None:
|
|
1816
|
+
profiler.mark("ui_tick.task.create")
|
|
1811
1817
|
self._ui_tick_task = asyncio.create_task(
|
|
1812
1818
|
self._ui_tick(),
|
|
1813
1819
|
name="terminal-ui-tick",
|
|
@@ -1826,11 +1832,15 @@ class TerminalAgentTUI(
|
|
|
1826
1832
|
self._refresh_layers()
|
|
1827
1833
|
self._invalidate()
|
|
1828
1834
|
|
|
1835
|
+
if profiler is not None:
|
|
1836
|
+
profiler.mark("status_bar.refresh.task.create")
|
|
1829
1837
|
self._startup_status_refresh_task = asyncio.create_task(
|
|
1830
1838
|
_do_startup_status_refresh(),
|
|
1831
1839
|
name="terminal-startup-status-refresh",
|
|
1832
1840
|
)
|
|
1833
1841
|
if hasattr(self, "_session") and self._session is not None:
|
|
1842
|
+
if profiler is not None:
|
|
1843
|
+
profiler.mark("event_pump.task.create")
|
|
1834
1844
|
self._event_pump_task = asyncio.create_task(
|
|
1835
1845
|
self._consume_event_stream(),
|
|
1836
1846
|
name="terminal-session-event-pump",
|
|
@@ -1894,12 +1904,16 @@ class TerminalAgentTUI(
|
|
|
1894
1904
|
return Path(session_cwd).expanduser().resolve()
|
|
1895
1905
|
return Path.cwd().expanduser().resolve()
|
|
1896
1906
|
|
|
1897
|
-
async def _init_plugins(self) -> None:
|
|
1907
|
+
async def _init_plugins(self, *, profiler: Any | None = None) -> None:
|
|
1898
1908
|
"""Load and inject plugin resources at startup."""
|
|
1909
|
+
if profiler is not None:
|
|
1910
|
+
profiler.mark("load_thread.start")
|
|
1899
1911
|
loaded_plugins, plugin_errors = await asyncio.to_thread(
|
|
1900
1912
|
_load_plugins_blocking,
|
|
1901
1913
|
self._plugin_project_path(),
|
|
1902
1914
|
)
|
|
1915
|
+
if profiler is not None:
|
|
1916
|
+
profiler.mark("load_thread.done")
|
|
1903
1917
|
if self._closing:
|
|
1904
1918
|
logger.debug("Plugin load completed after TUI close; discarding results")
|
|
1905
1919
|
return
|
|
@@ -1907,7 +1921,11 @@ class TerminalAgentTUI(
|
|
|
1907
1921
|
self._loaded_plugins = loaded_plugins
|
|
1908
1922
|
self._plugin_errors = plugin_errors
|
|
1909
1923
|
self._plugin_command_names: set[str] = set()
|
|
1924
|
+
if profiler is not None:
|
|
1925
|
+
profiler.mark("inject.start")
|
|
1910
1926
|
self._inject_plugin_resources(self._loaded_plugins)
|
|
1927
|
+
if profiler is not None:
|
|
1928
|
+
profiler.mark("inject.done")
|
|
1911
1929
|
logger.info(
|
|
1912
1930
|
"Loaded %d plugins (%d errors)",
|
|
1913
1931
|
len(self._loaded_plugins),
|
|
@@ -24,6 +24,8 @@ PACKAGE_NAME = "comate-cli"
|
|
|
24
24
|
RELEASE_NOTES_URL = "https://github.com/AndyLee1024/agent-sdk/releases/latest"
|
|
25
25
|
UPDATE_COMMAND = ("uv", "tool", "update", PACKAGE_NAME)
|
|
26
26
|
_SETTINGS_SECTION = "updates"
|
|
27
|
+
_UPDATE_CHECK_TRUST_ENV = "COMATE_CLI_UPDATE_CHECK_TRUST_ENV"
|
|
28
|
+
_TRUTHY_ENV_VALUES = {"1", "true", "yes", "on"}
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
class UpdatePromptDecision(Enum):
|
|
@@ -65,62 +67,149 @@ def _is_chinese_locale() -> bool:
|
|
|
65
67
|
return False
|
|
66
68
|
|
|
67
69
|
|
|
70
|
+
def _update_check_trust_env() -> bool:
|
|
71
|
+
raw = os.environ.get(_UPDATE_CHECK_TRUST_ENV, "")
|
|
72
|
+
return raw.strip().lower() in _TRUTHY_ENV_VALUES
|
|
73
|
+
|
|
74
|
+
|
|
68
75
|
async def check_update(
|
|
69
76
|
*,
|
|
70
77
|
package: str = PACKAGE_NAME,
|
|
71
78
|
release_notes_url: str = RELEASE_NOTES_URL,
|
|
72
79
|
log: logging.Logger | None = None,
|
|
80
|
+
profiler: Any | None = None,
|
|
73
81
|
) -> UpdateInfo | None:
|
|
74
82
|
"""异步检查 comate-cli 是否有新版本,返回结构化版本信息或 None。"""
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
current = importlib.metadata.version(package)
|
|
78
|
-
except importlib.metadata.PackageNotFoundError:
|
|
79
|
-
return None
|
|
80
|
-
|
|
81
|
-
if _is_chinese_locale():
|
|
82
|
-
url = f"https://mirrors.tuna.tsinghua.edu.cn/pypi/{package}/json"
|
|
83
|
-
source_label = "tuna"
|
|
84
|
-
else:
|
|
85
|
-
url = f"https://pypi.org/pypi/{package}/json"
|
|
86
|
-
source_label = "pypi"
|
|
87
|
-
|
|
83
|
+
if profiler is not None:
|
|
84
|
+
profiler.mark("thread.await.start")
|
|
88
85
|
try:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
86
|
+
return await asyncio.to_thread(
|
|
87
|
+
check_update_blocking,
|
|
88
|
+
package=package,
|
|
89
|
+
release_notes_url=release_notes_url,
|
|
90
|
+
log=log,
|
|
91
|
+
profiler=profiler,
|
|
92
|
+
)
|
|
93
|
+
except asyncio.CancelledError:
|
|
94
|
+
if profiler is not None:
|
|
95
|
+
profiler.mark("thread.await.cancelled")
|
|
96
|
+
raise
|
|
95
97
|
except Exception:
|
|
96
|
-
|
|
98
|
+
if profiler is not None:
|
|
99
|
+
profiler.mark("thread.await.failed")
|
|
100
|
+
active_logger = log or logger
|
|
101
|
+
active_logger.debug("update check worker failed", exc_info=True)
|
|
97
102
|
return None
|
|
103
|
+
finally:
|
|
104
|
+
if profiler is not None:
|
|
105
|
+
profiler.mark("thread.await.done")
|
|
98
106
|
|
|
99
|
-
try:
|
|
100
|
-
from packaging.version import Version
|
|
101
107
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
def check_update_blocking(
|
|
109
|
+
*,
|
|
110
|
+
package: str = PACKAGE_NAME,
|
|
111
|
+
release_notes_url: str = RELEASE_NOTES_URL,
|
|
112
|
+
log: logging.Logger | None = None,
|
|
113
|
+
profiler: Any | None = None,
|
|
114
|
+
) -> UpdateInfo | None:
|
|
115
|
+
"""同步执行版本检查;由 async wrapper 放入 worker thread,避免阻塞 TUI event loop。"""
|
|
116
|
+
active_logger = log or logger
|
|
117
|
+
try:
|
|
118
|
+
if profiler is not None:
|
|
119
|
+
profiler.mark("thread.run.start")
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
if profiler is not None:
|
|
123
|
+
profiler.mark("current_version.start")
|
|
124
|
+
current = importlib.metadata.version(package)
|
|
125
|
+
except importlib.metadata.PackageNotFoundError:
|
|
126
|
+
return None
|
|
127
|
+
finally:
|
|
128
|
+
if profiler is not None:
|
|
129
|
+
profiler.mark("current_version.done")
|
|
130
|
+
|
|
131
|
+
if profiler is not None:
|
|
132
|
+
profiler.mark("locale.start")
|
|
133
|
+
if _is_chinese_locale():
|
|
134
|
+
url = f"https://mirrors.tuna.tsinghua.edu.cn/pypi/{package}/json"
|
|
135
|
+
source_label = "tuna"
|
|
136
|
+
else:
|
|
137
|
+
url = f"https://pypi.org/pypi/{package}/json"
|
|
138
|
+
source_label = "pypi"
|
|
139
|
+
if profiler is not None:
|
|
140
|
+
profiler.mark("locale.done")
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
if profiler is not None:
|
|
144
|
+
profiler.mark("httpx_import.start")
|
|
145
|
+
import httpx
|
|
146
|
+
if profiler is not None:
|
|
147
|
+
profiler.mark("httpx_import.done")
|
|
148
|
+
|
|
149
|
+
trust_env = _update_check_trust_env()
|
|
150
|
+
if profiler is not None:
|
|
151
|
+
profiler.mark(
|
|
152
|
+
"http_client.trust_env.enabled"
|
|
153
|
+
if trust_env
|
|
154
|
+
else "http_client.trust_env.disabled"
|
|
155
|
+
)
|
|
156
|
+
profiler.mark("http_client.construct.start")
|
|
157
|
+
client = httpx.Client(timeout=3.0, trust_env=trust_env)
|
|
158
|
+
if profiler is not None:
|
|
159
|
+
profiler.mark("http_client.construct.done")
|
|
160
|
+
|
|
161
|
+
if profiler is not None:
|
|
162
|
+
profiler.mark("http_client.enter.start")
|
|
163
|
+
with client:
|
|
164
|
+
if profiler is not None:
|
|
165
|
+
profiler.mark("http_client.enter.done")
|
|
166
|
+
profiler.mark("http_get.start")
|
|
167
|
+
resp = client.get(url)
|
|
168
|
+
resp.raise_for_status()
|
|
169
|
+
latest = resp.json()["info"]["version"]
|
|
170
|
+
if profiler is not None:
|
|
171
|
+
profiler.mark("http_get.done")
|
|
172
|
+
except Exception:
|
|
173
|
+
if profiler is not None:
|
|
174
|
+
profiler.mark("http_client.failed")
|
|
175
|
+
active_logger.debug(f"update check failed (source={source_label})", exc_info=True)
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
if profiler is not None:
|
|
180
|
+
profiler.mark("version_compare.start")
|
|
181
|
+
from packaging.version import Version
|
|
182
|
+
|
|
183
|
+
if Version(latest) > Version(current):
|
|
184
|
+
return UpdateInfo(
|
|
185
|
+
package=package,
|
|
186
|
+
current_version=current,
|
|
187
|
+
latest_version=latest,
|
|
188
|
+
release_notes_url=release_notes_url,
|
|
189
|
+
)
|
|
190
|
+
except Exception:
|
|
191
|
+
active_logger.debug(
|
|
192
|
+
"update comparison failed (current=%s, latest=%s)",
|
|
193
|
+
current,
|
|
194
|
+
latest,
|
|
195
|
+
exc_info=True,
|
|
108
196
|
)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return None
|
|
197
|
+
finally:
|
|
198
|
+
if profiler is not None:
|
|
199
|
+
profiler.mark("version_compare.done")
|
|
200
|
+
return None
|
|
201
|
+
finally:
|
|
202
|
+
if profiler is not None:
|
|
203
|
+
profiler.mark("thread.run.done")
|
|
117
204
|
|
|
118
205
|
|
|
119
206
|
def format_update_hint(info: UpdateInfo) -> str:
|
|
207
|
+
# Returns plain text only. Callers are responsible for adding markup
|
|
208
|
+
# appropriate to their render target (Rich Console vs prompt_toolkit).
|
|
120
209
|
return (
|
|
121
|
-
f"
|
|
210
|
+
f"New version available: {info.latest_version} "
|
|
122
211
|
f"(current: {info.current_version}) "
|
|
123
|
-
f"Run
|
|
212
|
+
f"Run `uv tool update {info.package}` to update."
|
|
124
213
|
)
|
|
125
214
|
|
|
126
215
|
|
|
@@ -4,10 +4,10 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "comate-cli"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.6"
|
|
8
8
|
description = "Comate terminal CLI built on comate-agent-sdk"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.11"
|
|
10
|
+
requires-python = ">=3.11,<3.14"
|
|
11
11
|
authors = [
|
|
12
12
|
{ name = "Andy", email = "andy.dev@aliyun.com" }
|
|
13
13
|
]
|