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.
Files changed (201) hide show
  1. {comate_cli-0.7.4 → comate_cli-0.7.6}/PKG-INFO +2 -2
  2. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/app.py +103 -28
  3. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/startup_profile.py +22 -8
  4. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/status_bar.py +30 -10
  5. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui.py +20 -2
  6. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/update_check.py +127 -38
  7. {comate_cli-0.7.4 → comate_cli-0.7.6}/pyproject.toml +2 -2
  8. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_app_startup_latency.py +48 -32
  9. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_completion_status_panel.py +1 -1
  10. comate_cli-0.7.6/tests/test_startup_profile.py +435 -0
  11. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_status_bar.py +18 -4
  12. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_startup_latency.py +4 -2
  13. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_update_check.py +212 -9
  14. {comate_cli-0.7.4 → comate_cli-0.7.6}/uv.lock +3 -415
  15. comate_cli-0.7.4/tests/test_startup_profile.py +0 -216
  16. {comate_cli-0.7.4 → comate_cli-0.7.6}/.gitignore +0 -0
  17. {comate_cli-0.7.4 → comate_cli-0.7.6}/CHANGELOG.md +0 -0
  18. {comate_cli-0.7.4 → comate_cli-0.7.6}/README.md +0 -0
  19. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/__init__.py +0 -0
  20. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/__main__.py +0 -0
  21. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/main.py +0 -0
  22. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/mcp_cli.py +0 -0
  23. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/__init__.py +0 -0
  24. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/animations.py +0 -0
  25. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/assistant_render.py +0 -0
  26. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/codenames.py +0 -0
  27. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/config/__init__.py +0 -0
  28. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/config/model.py +0 -0
  29. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/config/picker.py +0 -0
  30. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/config/picker_state.py +0 -0
  31. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/config/store.py +0 -0
  32. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
  33. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/env_utils.py +0 -0
  34. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/error_display.py +0 -0
  35. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/event_renderer.py +0 -0
  36. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/figures.py +0 -0
  37. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/fragment_utils.py +0 -0
  38. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/goal_resume_view.py +0 -0
  39. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/history_printer.py +0 -0
  40. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/input_geometry.py +0 -0
  41. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
  42. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/logging_adapter.py +0 -0
  43. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/logo.py +0 -0
  44. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/markdown_render.py +0 -0
  45. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/mention_completer.py +0 -0
  46. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/message_style.py +0 -0
  47. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/models.py +0 -0
  48. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/path_context_hint.py +0 -0
  49. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
  50. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
  51. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
  52. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
  53. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
  54. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
  55. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
  56. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
  57. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
  58. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
  59. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
  60. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
  61. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
  62. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/preflight.py +0 -0
  63. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/question_view.py +0 -0
  64. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/resume_picker.py +0 -0
  65. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/resume_preview.py +0 -0
  66. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/resume_selector.py +0 -0
  67. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
  68. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
  69. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/selection_menu.py +0 -0
  70. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/slash_commands.py +0 -0
  71. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/startup.py +0 -0
  72. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/statusline/__init__.py +0 -0
  73. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/statusline/model.py +0 -0
  74. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/statusline/picker.py +0 -0
  75. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/statusline/picker_state.py +0 -0
  76. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/statusline/store.py +0 -0
  77. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/text_effects.py +0 -0
  78. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tips.py +0 -0
  79. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tool_fold.py +0 -0
  80. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tool_result_formatters.py +0 -0
  81. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tool_result_store.py +0 -0
  82. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
  83. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tool_view.py +0 -0
  84. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/transcript_viewer.py +0 -0
  85. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
  86. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
  87. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/commands.py +0 -0
  88. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/history_sync.py +0 -0
  89. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
  90. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/key_bindings.py +0 -0
  91. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
  92. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/render_panels.py +0 -0
  93. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
  94. {comate_cli-0.7.4 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
  95. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/config/__init__.py +0 -0
  96. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/config/test_model.py +0 -0
  97. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/config/test_picker_state.py +0 -0
  98. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/config/test_picker_ui.py +0 -0
  99. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/config/test_roundtrip.py +0 -0
  100. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/config/test_store_load.py +0 -0
  101. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/config/test_store_save.py +0 -0
  102. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/conftest.py +0 -0
  103. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
  104. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/statusline/__init__.py +0 -0
  105. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/statusline/test_model.py +0 -0
  106. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/statusline/test_picker_state.py +0 -0
  107. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/statusline/test_store.py +0 -0
  108. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_animator_shuffle.py +0 -0
  109. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_app_mcp_preload.py +0 -0
  110. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_app_preflight_gate.py +0 -0
  111. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_app_print_mode.py +0 -0
  112. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_app_shutdown.py +0 -0
  113. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_app_token_cost_config.py +0 -0
  114. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_app_usage_line.py +0 -0
  115. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_btw_slash_command.py +0 -0
  116. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_cli_project_root.py +0 -0
  117. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_compact_command_semantics.py +0 -0
  118. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_completion_context_activation.py +0 -0
  119. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_context_command.py +0 -0
  120. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_custom_slash_commands.py +0 -0
  121. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_discover_tab.py +0 -0
  122. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_errors_tab.py +0 -0
  123. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_event_renderer.py +0 -0
  124. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_event_renderer_boundary.py +0 -0
  125. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_event_renderer_e2e.py +0 -0
  126. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_event_renderer_log_boundary.py +0 -0
  127. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_event_renderer_log_queue.py +0 -0
  128. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_event_renderer_streaming.py +0 -0
  129. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_event_renderer_tool_fold.py +0 -0
  130. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_format_error.py +0 -0
  131. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_goal_resume_tui.py +0 -0
  132. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_goal_resume_view.py +0 -0
  133. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_goal_slash_command.py +0 -0
  134. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_handle_error.py +0 -0
  135. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_history_printer.py +0 -0
  136. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_history_printer_log.py +0 -0
  137. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_history_printer_subtitle_position.py +0 -0
  138. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_history_printer_tool_fold.py +0 -0
  139. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_history_sync.py +0 -0
  140. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_history_sync_tool_fold.py +0 -0
  141. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_input_behavior.py +0 -0
  142. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_input_history.py +0 -0
  143. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_installed_tab.py +0 -0
  144. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_interrupt_exit_semantics.py +0 -0
  145. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_layout_coordinator.py +0 -0
  146. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_logging_adapter.py +0 -0
  147. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_logo.py +0 -0
  148. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_main_args.py +0 -0
  149. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_markdown_render.py +0 -0
  150. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_marketplaces_tab.py +0 -0
  151. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_mcp_cli.py +0 -0
  152. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_mcp_slash_command.py +0 -0
  153. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_mention_completer.py +0 -0
  154. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_path_context_hint.py +0 -0
  155. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_plugin_slash_commands.py +0 -0
  156. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_plugin_tui_components.py +0 -0
  157. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_preflight.py +0 -0
  158. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_preflight_copilot.py +0 -0
  159. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_question_key_bindings.py +0 -0
  160. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_question_view.py +0 -0
  161. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_resume_picker.py +0 -0
  162. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_resume_preview.py +0 -0
  163. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_resume_selector.py +0 -0
  164. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_rewind_command_semantics.py +0 -0
  165. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_rpc_protocol.py +0 -0
  166. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_rpc_stdio_bridge.py +0 -0
  167. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_selection_menu.py +0 -0
  168. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_session_query_token_summary.py +0 -0
  169. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_shutdown_noise_guard.py +0 -0
  170. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_shutdown_noise_integration.py +0 -0
  171. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_skills_slash_command.py +0 -0
  172. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_slash_argument_hint.py +0 -0
  173. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_slash_clear.py +0 -0
  174. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_slash_completer.py +0 -0
  175. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_slash_registry.py +0 -0
  176. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_status_bar_transient.py +0 -0
  177. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_task_panel_format.py +0 -0
  178. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_task_panel_key_bindings.py +0 -0
  179. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_task_panel_rendering.py +0 -0
  180. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_task_poll.py +0 -0
  181. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tool_fold.py +0 -0
  182. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tool_fold_panel.py +0 -0
  183. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tool_result_formatters.py +0 -0
  184. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tool_result_store.py +0 -0
  185. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tool_result_viewer.py +0 -0
  186. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tool_result_viewer_key_bindings.py +0 -0
  187. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tool_view.py +0 -0
  188. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_transcript_viewer.py +0 -0
  189. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_transcript_viewer_tool_fold.py +0 -0
  190. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_elapsed_status.py +0 -0
  191. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_esc_queue.py +0 -0
  192. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_mcp_init_gate.py +0 -0
  193. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_paste_newline_guard.py +0 -0
  194. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_paste_placeholder.py +0 -0
  195. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_queue_preview.py +0 -0
  196. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_queue_sdk_source.py +0 -0
  197. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_split_invariance.py +0 -0
  198. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_team_messages.py +0 -0
  199. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_thinking_display.py +0 -0
  200. {comate_cli-0.7.4 → comate_cli-0.7.6}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
  201. {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.4
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: >=3.11
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
- return await check_update(log=logger)
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
- update_info = await _check_update()
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(*, project_root: Path | None = None) -> 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
- return Agent(
239
- config=AgentConfig(
240
- role="software_engineering",
241
- cwd=resolved_project_root,
242
- env_options=EnvOptions(system_env=True, git_env=True),
243
- use_streaming_task=True,
244
- include_cost=bool(getattr(snapshot, "token_cost_enabled", False)),
245
- memory_background_enabled=snapshot.memory_background_enabled,
246
- memory_dreaming_enabled=snapshot.memory_dreaming_enabled,
247
- compaction=CompactionConfig(
248
- threshold_ratio=snapshot.compaction_threshold_ratio,
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, resume_session_id: str | None, *, cwd: Path | None = None
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
- return ChatSession.resume(agent, session_id=resume_session_id, cwd=cwd), "resume"
259
- return ChatSession(agent, cwd=cwd), "new"
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(session: ChatSession) -> None:
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(agent, resume_session_id, cwd=project_root)
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(agent, resume_session_id, cwd=project_root)
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._pending_messages.append(
41
- f"startup_profile phase={normalized_phase} elapsed_ms={elapsed_ms:.3f}"
42
- )
43
- self.flush()
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 or not self._pending_messages:
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
- for message in pending:
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
- try:
264
- ctx_info = await self._session.get_context_info()
265
- utilization = float(getattr(ctx_info, "utilization_percent", 0.0))
266
- except Exception:
267
- return
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}% left"
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
- active_logger = log or logger
76
- try:
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
- import httpx
90
-
91
- async with httpx.AsyncClient(timeout=3.0) as client:
92
- resp = await client.get(url)
93
- resp.raise_for_status()
94
- latest = resp.json()["info"]["version"]
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
- active_logger.debug(f"update check failed (source={source_label})", exc_info=True)
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
- if Version(latest) > Version(current):
103
- return UpdateInfo(
104
- package=package,
105
- current_version=current,
106
- latest_version=latest,
107
- release_notes_url=release_notes_url,
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
- except Exception:
110
- active_logger.debug(
111
- "update comparison failed (current=%s, latest=%s)",
112
- current,
113
- latest,
114
- exc_info=True,
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"[dim] New version available: [bold cyan]{info.latest_version}[/] "
210
+ f"New version available: {info.latest_version} "
122
211
  f"(current: {info.current_version}) "
123
- f"Run [bold green]uv tool update {info.package}[/] to update.[/]"
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.4"
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
  ]