comate-cli 0.7.5__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 (200) hide show
  1. {comate_cli-0.7.5 → comate_cli-0.7.6}/PKG-INFO +1 -1
  2. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/app.py +5 -1
  3. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/startup_profile.py +22 -8
  4. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/update_check.py +114 -48
  5. {comate_cli-0.7.5 → comate_cli-0.7.6}/pyproject.toml +1 -1
  6. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_app_startup_latency.py +41 -31
  7. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_startup_profile.py +100 -1
  8. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_update_check.py +167 -10
  9. {comate_cli-0.7.5 → comate_cli-0.7.6}/uv.lock +3 -415
  10. {comate_cli-0.7.5 → comate_cli-0.7.6}/.gitignore +0 -0
  11. {comate_cli-0.7.5 → comate_cli-0.7.6}/CHANGELOG.md +0 -0
  12. {comate_cli-0.7.5 → comate_cli-0.7.6}/README.md +0 -0
  13. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/__init__.py +0 -0
  14. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/__main__.py +0 -0
  15. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/main.py +0 -0
  16. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/mcp_cli.py +0 -0
  17. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/__init__.py +0 -0
  18. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/animations.py +0 -0
  19. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/assistant_render.py +0 -0
  20. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/codenames.py +0 -0
  21. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/config/__init__.py +0 -0
  22. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/config/model.py +0 -0
  23. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/config/picker.py +0 -0
  24. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/config/picker_state.py +0 -0
  25. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/config/store.py +0 -0
  26. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
  27. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/env_utils.py +0 -0
  28. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/error_display.py +0 -0
  29. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/event_renderer.py +0 -0
  30. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/figures.py +0 -0
  31. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/fragment_utils.py +0 -0
  32. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/goal_resume_view.py +0 -0
  33. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/history_printer.py +0 -0
  34. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/input_geometry.py +0 -0
  35. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
  36. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/logging_adapter.py +0 -0
  37. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/logo.py +0 -0
  38. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/markdown_render.py +0 -0
  39. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/mention_completer.py +0 -0
  40. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/message_style.py +0 -0
  41. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/models.py +0 -0
  42. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/path_context_hint.py +0 -0
  43. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
  44. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
  45. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
  46. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
  47. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
  48. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
  49. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
  50. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
  51. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
  52. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
  53. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
  54. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
  55. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
  56. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/preflight.py +0 -0
  57. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/question_view.py +0 -0
  58. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/resume_picker.py +0 -0
  59. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/resume_preview.py +0 -0
  60. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/resume_selector.py +0 -0
  61. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
  62. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
  63. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/selection_menu.py +0 -0
  64. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/slash_commands.py +0 -0
  65. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/startup.py +0 -0
  66. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/status_bar.py +0 -0
  67. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/statusline/__init__.py +0 -0
  68. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/statusline/model.py +0 -0
  69. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/statusline/picker.py +0 -0
  70. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/statusline/picker_state.py +0 -0
  71. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/statusline/store.py +0 -0
  72. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/text_effects.py +0 -0
  73. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/tips.py +0 -0
  74. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/tool_fold.py +0 -0
  75. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/tool_result_formatters.py +0 -0
  76. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/tool_result_store.py +0 -0
  77. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
  78. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/tool_view.py +0 -0
  79. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/transcript_viewer.py +0 -0
  80. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui.py +0 -0
  81. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
  82. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
  83. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/commands.py +0 -0
  84. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/history_sync.py +0 -0
  85. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
  86. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/key_bindings.py +0 -0
  87. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
  88. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/render_panels.py +0 -0
  89. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
  90. {comate_cli-0.7.5 → comate_cli-0.7.6}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
  91. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/config/__init__.py +0 -0
  92. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/config/test_model.py +0 -0
  93. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/config/test_picker_state.py +0 -0
  94. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/config/test_picker_ui.py +0 -0
  95. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/config/test_roundtrip.py +0 -0
  96. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/config/test_store_load.py +0 -0
  97. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/config/test_store_save.py +0 -0
  98. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/conftest.py +0 -0
  99. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
  100. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/statusline/__init__.py +0 -0
  101. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/statusline/test_model.py +0 -0
  102. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/statusline/test_picker_state.py +0 -0
  103. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/statusline/test_store.py +0 -0
  104. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_animator_shuffle.py +0 -0
  105. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_app_mcp_preload.py +0 -0
  106. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_app_preflight_gate.py +0 -0
  107. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_app_print_mode.py +0 -0
  108. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_app_shutdown.py +0 -0
  109. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_app_token_cost_config.py +0 -0
  110. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_app_usage_line.py +0 -0
  111. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_btw_slash_command.py +0 -0
  112. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_cli_project_root.py +0 -0
  113. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_compact_command_semantics.py +0 -0
  114. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_completion_context_activation.py +0 -0
  115. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_completion_status_panel.py +0 -0
  116. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_context_command.py +0 -0
  117. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_custom_slash_commands.py +0 -0
  118. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_discover_tab.py +0 -0
  119. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_errors_tab.py +0 -0
  120. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_event_renderer.py +0 -0
  121. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_event_renderer_boundary.py +0 -0
  122. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_event_renderer_e2e.py +0 -0
  123. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_event_renderer_log_boundary.py +0 -0
  124. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_event_renderer_log_queue.py +0 -0
  125. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_event_renderer_streaming.py +0 -0
  126. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_event_renderer_tool_fold.py +0 -0
  127. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_format_error.py +0 -0
  128. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_goal_resume_tui.py +0 -0
  129. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_goal_resume_view.py +0 -0
  130. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_goal_slash_command.py +0 -0
  131. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_handle_error.py +0 -0
  132. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_history_printer.py +0 -0
  133. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_history_printer_log.py +0 -0
  134. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_history_printer_subtitle_position.py +0 -0
  135. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_history_printer_tool_fold.py +0 -0
  136. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_history_sync.py +0 -0
  137. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_history_sync_tool_fold.py +0 -0
  138. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_input_behavior.py +0 -0
  139. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_input_history.py +0 -0
  140. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_installed_tab.py +0 -0
  141. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_interrupt_exit_semantics.py +0 -0
  142. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_layout_coordinator.py +0 -0
  143. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_logging_adapter.py +0 -0
  144. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_logo.py +0 -0
  145. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_main_args.py +0 -0
  146. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_markdown_render.py +0 -0
  147. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_marketplaces_tab.py +0 -0
  148. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_mcp_cli.py +0 -0
  149. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_mcp_slash_command.py +0 -0
  150. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_mention_completer.py +0 -0
  151. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_path_context_hint.py +0 -0
  152. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_plugin_slash_commands.py +0 -0
  153. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_plugin_tui_components.py +0 -0
  154. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_preflight.py +0 -0
  155. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_preflight_copilot.py +0 -0
  156. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_question_key_bindings.py +0 -0
  157. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_question_view.py +0 -0
  158. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_resume_picker.py +0 -0
  159. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_resume_preview.py +0 -0
  160. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_resume_selector.py +0 -0
  161. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_rewind_command_semantics.py +0 -0
  162. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_rpc_protocol.py +0 -0
  163. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_rpc_stdio_bridge.py +0 -0
  164. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_selection_menu.py +0 -0
  165. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_session_query_token_summary.py +0 -0
  166. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_shutdown_noise_guard.py +0 -0
  167. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_shutdown_noise_integration.py +0 -0
  168. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_skills_slash_command.py +0 -0
  169. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_slash_argument_hint.py +0 -0
  170. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_slash_clear.py +0 -0
  171. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_slash_completer.py +0 -0
  172. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_slash_registry.py +0 -0
  173. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_status_bar.py +0 -0
  174. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_status_bar_transient.py +0 -0
  175. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_task_panel_format.py +0 -0
  176. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_task_panel_key_bindings.py +0 -0
  177. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_task_panel_rendering.py +0 -0
  178. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_task_poll.py +0 -0
  179. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_tool_fold.py +0 -0
  180. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_tool_fold_panel.py +0 -0
  181. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_tool_result_formatters.py +0 -0
  182. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_tool_result_store.py +0 -0
  183. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_tool_result_viewer.py +0 -0
  184. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_tool_result_viewer_key_bindings.py +0 -0
  185. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_tool_view.py +0 -0
  186. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_transcript_viewer.py +0 -0
  187. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_transcript_viewer_tool_fold.py +0 -0
  188. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_tui_elapsed_status.py +0 -0
  189. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_tui_esc_queue.py +0 -0
  190. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_tui_mcp_init_gate.py +0 -0
  191. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_tui_paste_newline_guard.py +0 -0
  192. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_tui_paste_placeholder.py +0 -0
  193. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_tui_queue_preview.py +0 -0
  194. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_tui_queue_sdk_source.py +0 -0
  195. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_tui_split_invariance.py +0 -0
  196. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_tui_startup_latency.py +0 -0
  197. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_tui_team_messages.py +0 -0
  198. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_tui_thinking_display.py +0 -0
  199. {comate_cli-0.7.5 → comate_cli-0.7.6}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
  200. {comate_cli-0.7.5 → 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.5
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
@@ -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:
@@ -69,7 +70,7 @@ async def _handle_update_on_launch(info: UpdateInfo) -> bool:
69
70
  if decision == UpdatePromptDecision.SKIP:
70
71
  return False
71
72
  if decision == UpdatePromptDecision.SHOW_HINT:
72
- console.print(format_update_hint(info))
73
+ console.print(f"[dim]{format_update_hint(info)}[/]")
73
74
  mark_update_seen(info)
74
75
  return False
75
76
 
@@ -115,6 +116,9 @@ def _schedule_update_check_on_launch(
115
116
  async def _run() -> None:
116
117
  try:
117
118
  profiler.mark("update_check.start")
119
+ profiler.mark("update_check.delay.start")
120
+ await asyncio.sleep(_UPDATE_CHECK_DELAY_S)
121
+ profiler.mark("update_check.delay.done")
118
122
  update_info = await _check_update(profiler=profiler.child("update_check"))
119
123
  profiler.mark("update_check.done")
120
124
  if update_info is not None:
@@ -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
  )
@@ -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,6 +67,11 @@ 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,
@@ -73,77 +80,136 @@ async def check_update(
73
80
  profiler: Any | None = None,
74
81
  ) -> UpdateInfo | None:
75
82
  """异步检查 comate-cli 是否有新版本,返回结构化版本信息或 None。"""
76
- active_logger = log or logger
83
+ if profiler is not None:
84
+ profiler.mark("thread.await.start")
77
85
  try:
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:
78
94
  if profiler is not None:
79
- profiler.mark("current_version.start")
80
- current = importlib.metadata.version(package)
81
- except importlib.metadata.PackageNotFoundError:
95
+ profiler.mark("thread.await.cancelled")
96
+ raise
97
+ except Exception:
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)
82
102
  return None
83
103
  finally:
84
104
  if profiler is not None:
85
- profiler.mark("current_version.done")
105
+ profiler.mark("thread.await.done")
86
106
 
87
- if profiler is not None:
88
- profiler.mark("locale.start")
89
- if _is_chinese_locale():
90
- url = f"https://mirrors.tuna.tsinghua.edu.cn/pypi/{package}/json"
91
- source_label = "tuna"
92
- else:
93
- url = f"https://pypi.org/pypi/{package}/json"
94
- source_label = "pypi"
95
- if profiler is not None:
96
- profiler.mark("locale.done")
97
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
98
117
  try:
99
118
  if profiler is not None:
100
- profiler.mark("httpx_import.start")
101
- import httpx
102
- if profiler is not None:
103
- profiler.mark("httpx_import.done")
119
+ profiler.mark("thread.run.start")
104
120
 
105
- async with httpx.AsyncClient(timeout=3.0) as client:
121
+ try:
106
122
  if profiler is not None:
107
- profiler.mark("http_get.start")
108
- resp = await client.get(url)
109
- resp.raise_for_status()
110
- latest = resp.json()["info"]["version"]
123
+ profiler.mark("current_version.start")
124
+ current = importlib.metadata.version(package)
125
+ except importlib.metadata.PackageNotFoundError:
126
+ return None
127
+ finally:
111
128
  if profiler is not None:
112
- profiler.mark("http_get.done")
113
- except Exception:
114
- active_logger.debug(f"update check failed (source={source_label})", exc_info=True)
115
- return None
129
+ profiler.mark("current_version.done")
116
130
 
117
- try:
118
131
  if profiler is not None:
119
- profiler.mark("version_compare.start")
120
- from packaging.version import Version
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")
121
141
 
122
- if Version(latest) > Version(current):
123
- return UpdateInfo(
124
- package=package,
125
- current_version=current,
126
- latest_version=latest,
127
- release_notes_url=release_notes_url,
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,
128
196
  )
129
- except Exception:
130
- active_logger.debug(
131
- "update comparison failed (current=%s, latest=%s)",
132
- current,
133
- latest,
134
- exc_info=True,
135
- )
197
+ finally:
198
+ if profiler is not None:
199
+ profiler.mark("version_compare.done")
200
+ return None
136
201
  finally:
137
202
  if profiler is not None:
138
- profiler.mark("version_compare.done")
139
- return None
203
+ profiler.mark("thread.run.done")
140
204
 
141
205
 
142
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).
143
209
  return (
144
- f"[dim] New version available: [bold cyan]{info.latest_version}[/] "
210
+ f"New version available: {info.latest_version} "
145
211
  f"(current: {info.current_version}) "
146
- f"Run [bold green]uv tool update {info.package}[/] to update.[/]"
212
+ f"Run `uv tool update {info.package}` to update."
147
213
  )
148
214
 
149
215
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "comate-cli"
7
- version = "0.7.5"
7
+ version = "0.7.6"
8
8
  description = "Comate terminal CLI built on comate-agent-sdk"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11,<3.14"
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import threading
4
5
  import unittest
5
6
  from pathlib import Path
6
7
  from types import SimpleNamespace
@@ -140,8 +141,8 @@ class TestAppStartupLatency(unittest.IsolatedAsyncioTestCase):
140
141
  async def test_pending_update_check_does_not_block_tui_run(self) -> None:
141
142
  session = _fake_session()
142
143
  created_tuis: list[_FakeTUI] = []
143
- update_started = asyncio.Event()
144
- update_cancelled = asyncio.Event()
144
+ update_started = threading.Event()
145
+ release_update = threading.Event()
145
146
 
146
147
  class _RecordingTUI(_FakeTUI):
147
148
  def __init__(self, session, status_bar, renderer) -> None:
@@ -150,41 +151,48 @@ class TestAppStartupLatency(unittest.IsolatedAsyncioTestCase):
150
151
 
151
152
  async def run(self, *, mcp_init, profiler=None) -> None:
152
153
  del mcp_init, profiler
153
- await asyncio.wait_for(update_started.wait(), timeout=0.1)
154
154
  self.run_called.set()
155
+ assert not update_started.is_set()
156
+ assert await asyncio.to_thread(update_started.wait, 0.2)
157
+ release_update.set()
158
+ await asyncio.sleep(0)
155
159
 
156
- async def _never_finishes(*, profiler=None) -> UpdateInfo | None:
157
- del profiler
160
+ def _slow_blocking_check(**kwargs) -> UpdateInfo | None:
161
+ del kwargs
158
162
  update_started.set()
159
- try:
160
- await asyncio.Event().wait()
161
- return None
162
- finally:
163
- update_cancelled.set()
164
-
165
- with (
166
- patch.object(app_module, "_resolve_cli_project_root", return_value=Path("/tmp/project")),
167
- patch.object(
168
- app_module,
169
- "run_preflight_if_needed",
170
- AsyncMock(return_value=PreflightResult(status="configured")),
171
- ),
172
- patch.object(app_module, "_build_agent", return_value=object()),
173
- patch.object(app_module, "print_logo"),
174
- patch.object(app_module, "_check_update", side_effect=_never_finishes),
175
- patch.object(app_module, "EventRenderer", return_value=MagicMock()),
176
- patch("comate_cli.terminal_agent.logging_adapter.setup_tui_logging", return_value=MagicMock()),
177
- patch.object(app_module, "_resolve_session", return_value=(session, "new")),
178
- patch.object(app_module, "StatusBar", _FakeStatusBar),
179
- patch.object(app_module, "TerminalAgentTUI", _RecordingTUI),
180
- patch.object(app_module, "_graceful_shutdown", AsyncMock()),
181
- patch.object(app_module, "_format_exit_usage_line", return_value=None),
182
- ):
183
- await asyncio.wait_for(app_module.run(), timeout=0.2)
163
+ release_update.wait(timeout=1.0)
164
+ return None
165
+
166
+ try:
167
+ with (
168
+ patch.object(app_module, "_UPDATE_CHECK_DELAY_S", 0.01),
169
+ patch.object(app_module, "_resolve_cli_project_root", return_value=Path("/tmp/project")),
170
+ patch.object(
171
+ app_module,
172
+ "run_preflight_if_needed",
173
+ AsyncMock(return_value=PreflightResult(status="configured")),
174
+ ),
175
+ patch.object(app_module, "_build_agent", return_value=object()),
176
+ patch.object(app_module, "print_logo"),
177
+ patch(
178
+ "comate_cli.terminal_agent.update_check.check_update_blocking",
179
+ side_effect=_slow_blocking_check,
180
+ ),
181
+ patch.object(app_module, "EventRenderer", return_value=MagicMock()),
182
+ patch("comate_cli.terminal_agent.logging_adapter.setup_tui_logging", return_value=MagicMock()),
183
+ patch.object(app_module, "_resolve_session", return_value=(session, "new")),
184
+ patch.object(app_module, "StatusBar", _FakeStatusBar),
185
+ patch.object(app_module, "TerminalAgentTUI", _RecordingTUI),
186
+ patch.object(app_module, "_graceful_shutdown", AsyncMock()),
187
+ patch.object(app_module, "_format_exit_usage_line", return_value=None),
188
+ ):
189
+ await asyncio.wait_for(app_module.run(), timeout=0.4)
190
+ finally:
191
+ release_update.set()
184
192
 
185
193
  self.assertEqual(len(created_tuis), 1)
186
194
  self.assertTrue(created_tuis[0].run_called.is_set())
187
- self.assertTrue(update_cancelled.is_set())
195
+ self.assertTrue(update_started.is_set())
188
196
 
189
197
  async def test_update_check_exception_does_not_block_tui_run(self) -> None:
190
198
  session = _fake_session()
@@ -232,6 +240,7 @@ class TestAppStartupLatency(unittest.IsolatedAsyncioTestCase):
232
240
  )
233
241
 
234
242
  with (
243
+ patch.object(app_module, "_UPDATE_CHECK_DELAY_S", 0.01),
235
244
  patch.object(app_module, "_check_update", AsyncMock(return_value=info)),
236
245
  patch.object(app_module, "decide_update_prompt", return_value=UpdatePromptDecision.SHOW_HINT),
237
246
  patch.object(app_module, "mark_update_seen") as mark_seen,
@@ -256,6 +265,7 @@ class TestAppStartupLatency(unittest.IsolatedAsyncioTestCase):
256
265
  )
257
266
 
258
267
  with (
268
+ patch.object(app_module, "_UPDATE_CHECK_DELAY_S", 0.01),
259
269
  patch.object(app_module, "_check_update", AsyncMock(return_value=info)),
260
270
  patch.object(app_module, "decide_update_prompt", return_value=UpdatePromptDecision.SHOW_PROMPT),
261
271
  patch.object(app_module, "show_update_prompt", AsyncMock()) as show_prompt,
@@ -1,7 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- import logging
4
3
  import asyncio
4
+ import logging
5
+ import threading
5
6
  import unittest
6
7
  from contextlib import contextmanager
7
8
  from pathlib import Path
@@ -129,6 +130,104 @@ class TestStartupProfiler(unittest.TestCase):
129
130
  self.assertIn("phase=agent.build.start", messages[0])
130
131
  self.assertIn("phase=logging.setup.done", messages[1])
131
132
 
133
+ def test_flush_releases_buffered_marks_after_info_logging_is_enabled(self) -> None:
134
+ logger = logging.getLogger("comate_cli.tests.startup_profile.flush")
135
+ original_level = logger.level
136
+ original_propagate = logger.propagate
137
+ records: list[logging.LogRecord] = []
138
+
139
+ class _ListHandler(logging.Handler):
140
+ def emit(self, record: logging.LogRecord) -> None:
141
+ records.append(record)
142
+
143
+ handler = _ListHandler(level=logging.INFO)
144
+ logger.handlers[:] = []
145
+ logger.addHandler(handler)
146
+ logger.propagate = False
147
+ logger.setLevel(logging.WARNING)
148
+
149
+ try:
150
+ with (
151
+ patch.dict("os.environ", {"COMATE_STARTUP_PROFILE": "1"}, clear=False),
152
+ patch("comate_cli.terminal_agent.startup_profile.time.perf_counter", side_effect=[3.0, 3.01]),
153
+ ):
154
+ profiler = StartupProfiler.from_env(logger=logger)
155
+ profiler.mark("pre_logging.phase")
156
+
157
+ self.assertEqual(records, [])
158
+
159
+ logger.setLevel(logging.INFO)
160
+ profiler.flush()
161
+
162
+ messages = [record.getMessage() for record in records]
163
+ finally:
164
+ logger.removeHandler(handler)
165
+ logger.setLevel(original_level)
166
+ logger.propagate = original_propagate
167
+
168
+ self.assertEqual(len(messages), 1)
169
+ self.assertIn("phase=pre_logging.phase", messages[0])
170
+
171
+ def test_child_profiler_shares_thread_lock(self) -> None:
172
+ logger = MagicMock(spec=logging.Logger)
173
+
174
+ with patch.dict("os.environ", {"COMATE_STARTUP_PROFILE": "1"}, clear=False):
175
+ profiler = StartupProfiler.from_env(logger=logger)
176
+
177
+ child = profiler.child("worker")
178
+
179
+ self.assertIs(child._lock, profiler._lock)
180
+
181
+ def test_child_marks_from_threads_do_not_drop_phases(self) -> None:
182
+ logger = logging.getLogger("comate_cli.tests.startup_profile.threaded")
183
+ original_level = logger.level
184
+ original_propagate = logger.propagate
185
+ records: list[logging.LogRecord] = []
186
+ records_lock = threading.Lock()
187
+
188
+ class _ListHandler(logging.Handler):
189
+ def emit(self, record: logging.LogRecord) -> None:
190
+ with records_lock:
191
+ records.append(record)
192
+
193
+ handler = _ListHandler(level=logging.INFO)
194
+ logger.handlers[:] = []
195
+ logger.addHandler(handler)
196
+ logger.propagate = False
197
+ logger.setLevel(logging.INFO)
198
+
199
+ try:
200
+ with patch.dict("os.environ", {"COMATE_STARTUP_PROFILE": "1"}, clear=False):
201
+ profiler = StartupProfiler.from_env(logger=logger)
202
+
203
+ def _mark(prefix: str, count: int) -> None:
204
+ child = profiler.child(prefix)
205
+ for index in range(count):
206
+ child.mark(f"phase{index}")
207
+
208
+ threads = [
209
+ threading.Thread(target=_mark, args=("worker_a", 20)),
210
+ threading.Thread(target=_mark, args=("worker_b", 20)),
211
+ ]
212
+ for thread in threads:
213
+ thread.start()
214
+ for thread in threads:
215
+ thread.join()
216
+
217
+ messages = [record.getMessage() for record in records]
218
+ finally:
219
+ logger.removeHandler(handler)
220
+ logger.setLevel(original_level)
221
+ logger.propagate = original_propagate
222
+
223
+ self.assertEqual(len(messages), 40)
224
+ for prefix in ("worker_a", "worker_b"):
225
+ for index in range(20):
226
+ self.assertTrue(
227
+ any(f"phase={prefix}.phase{index}" in message for message in messages),
228
+ f"missing {prefix}.phase{index}: {messages}",
229
+ )
230
+
132
231
 
133
232
  class TestStartupProfilerTUI(unittest.IsolatedAsyncioTestCase):
134
233
  async def test_tui_run_marks_run_async_boundary(self) -> None: