comate-cli 0.7.5__tar.gz → 0.7.7__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.5 → comate_cli-0.7.7}/PKG-INFO +1 -1
  2. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/main.py +10 -0
  3. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/app.py +38 -16
  4. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/startup_profile.py +38 -11
  5. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/update_check.py +210 -49
  6. {comate_cli-0.7.5 → comate_cli-0.7.7}/pyproject.toml +1 -1
  7. comate_cli-0.7.7/tests/conftest.py +33 -0
  8. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_app_startup_latency.py +65 -38
  9. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_startup_profile.py +112 -1
  10. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_update_check.py +270 -15
  11. {comate_cli-0.7.5 → comate_cli-0.7.7}/uv.lock +3 -415
  12. comate_cli-0.7.5/tests/conftest.py +0 -14
  13. {comate_cli-0.7.5 → comate_cli-0.7.7}/.gitignore +0 -0
  14. {comate_cli-0.7.5 → comate_cli-0.7.7}/CHANGELOG.md +0 -0
  15. {comate_cli-0.7.5 → comate_cli-0.7.7}/README.md +0 -0
  16. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/__init__.py +0 -0
  17. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/__main__.py +0 -0
  18. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/mcp_cli.py +0 -0
  19. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/__init__.py +0 -0
  20. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/animations.py +0 -0
  21. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/assistant_render.py +0 -0
  22. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/codenames.py +0 -0
  23. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/config/__init__.py +0 -0
  24. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/config/model.py +0 -0
  25. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/config/picker.py +0 -0
  26. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/config/picker_state.py +0 -0
  27. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/config/store.py +0 -0
  28. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
  29. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/env_utils.py +0 -0
  30. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/error_display.py +0 -0
  31. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/event_renderer.py +0 -0
  32. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/figures.py +0 -0
  33. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/fragment_utils.py +0 -0
  34. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/goal_resume_view.py +0 -0
  35. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/history_printer.py +0 -0
  36. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/input_geometry.py +0 -0
  37. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
  38. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/logging_adapter.py +0 -0
  39. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/logo.py +0 -0
  40. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/markdown_render.py +0 -0
  41. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/mention_completer.py +0 -0
  42. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/message_style.py +0 -0
  43. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/models.py +0 -0
  44. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/path_context_hint.py +0 -0
  45. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
  46. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
  47. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
  48. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
  49. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
  50. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
  51. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
  52. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
  53. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
  54. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
  55. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
  56. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
  57. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
  58. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/preflight.py +0 -0
  59. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/question_view.py +0 -0
  60. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/resume_picker.py +0 -0
  61. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/resume_preview.py +0 -0
  62. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/resume_selector.py +0 -0
  63. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
  64. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
  65. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/selection_menu.py +0 -0
  66. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/slash_commands.py +0 -0
  67. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/startup.py +0 -0
  68. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/status_bar.py +0 -0
  69. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/statusline/__init__.py +0 -0
  70. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/statusline/model.py +0 -0
  71. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/statusline/picker.py +0 -0
  72. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/statusline/picker_state.py +0 -0
  73. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/statusline/store.py +0 -0
  74. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/text_effects.py +0 -0
  75. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/tips.py +0 -0
  76. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/tool_fold.py +0 -0
  77. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/tool_result_formatters.py +0 -0
  78. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/tool_result_store.py +0 -0
  79. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
  80. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/tool_view.py +0 -0
  81. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/transcript_viewer.py +0 -0
  82. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/tui.py +0 -0
  83. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
  84. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
  85. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/tui_parts/commands.py +0 -0
  86. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/tui_parts/history_sync.py +0 -0
  87. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
  88. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/tui_parts/key_bindings.py +0 -0
  89. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
  90. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/tui_parts/render_panels.py +0 -0
  91. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
  92. {comate_cli-0.7.5 → comate_cli-0.7.7}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
  93. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/config/__init__.py +0 -0
  94. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/config/test_model.py +0 -0
  95. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/config/test_picker_state.py +0 -0
  96. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/config/test_picker_ui.py +0 -0
  97. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/config/test_roundtrip.py +0 -0
  98. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/config/test_store_load.py +0 -0
  99. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/config/test_store_save.py +0 -0
  100. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
  101. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/statusline/__init__.py +0 -0
  102. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/statusline/test_model.py +0 -0
  103. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/statusline/test_picker_state.py +0 -0
  104. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/statusline/test_store.py +0 -0
  105. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_animator_shuffle.py +0 -0
  106. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_app_mcp_preload.py +0 -0
  107. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_app_preflight_gate.py +0 -0
  108. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_app_print_mode.py +0 -0
  109. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_app_shutdown.py +0 -0
  110. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_app_token_cost_config.py +0 -0
  111. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_app_usage_line.py +0 -0
  112. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_btw_slash_command.py +0 -0
  113. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_cli_project_root.py +0 -0
  114. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_compact_command_semantics.py +0 -0
  115. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_completion_context_activation.py +0 -0
  116. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_completion_status_panel.py +0 -0
  117. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_context_command.py +0 -0
  118. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_custom_slash_commands.py +0 -0
  119. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_discover_tab.py +0 -0
  120. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_errors_tab.py +0 -0
  121. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_event_renderer.py +0 -0
  122. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_event_renderer_boundary.py +0 -0
  123. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_event_renderer_e2e.py +0 -0
  124. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_event_renderer_log_boundary.py +0 -0
  125. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_event_renderer_log_queue.py +0 -0
  126. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_event_renderer_streaming.py +0 -0
  127. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_event_renderer_tool_fold.py +0 -0
  128. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_format_error.py +0 -0
  129. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_goal_resume_tui.py +0 -0
  130. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_goal_resume_view.py +0 -0
  131. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_goal_slash_command.py +0 -0
  132. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_handle_error.py +0 -0
  133. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_history_printer.py +0 -0
  134. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_history_printer_log.py +0 -0
  135. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_history_printer_subtitle_position.py +0 -0
  136. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_history_printer_tool_fold.py +0 -0
  137. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_history_sync.py +0 -0
  138. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_history_sync_tool_fold.py +0 -0
  139. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_input_behavior.py +0 -0
  140. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_input_history.py +0 -0
  141. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_installed_tab.py +0 -0
  142. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_interrupt_exit_semantics.py +0 -0
  143. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_layout_coordinator.py +0 -0
  144. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_logging_adapter.py +0 -0
  145. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_logo.py +0 -0
  146. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_main_args.py +0 -0
  147. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_markdown_render.py +0 -0
  148. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_marketplaces_tab.py +0 -0
  149. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_mcp_cli.py +0 -0
  150. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_mcp_slash_command.py +0 -0
  151. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_mention_completer.py +0 -0
  152. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_path_context_hint.py +0 -0
  153. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_plugin_slash_commands.py +0 -0
  154. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_plugin_tui_components.py +0 -0
  155. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_preflight.py +0 -0
  156. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_preflight_copilot.py +0 -0
  157. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_question_key_bindings.py +0 -0
  158. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_question_view.py +0 -0
  159. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_resume_picker.py +0 -0
  160. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_resume_preview.py +0 -0
  161. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_resume_selector.py +0 -0
  162. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_rewind_command_semantics.py +0 -0
  163. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_rpc_protocol.py +0 -0
  164. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_rpc_stdio_bridge.py +0 -0
  165. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_selection_menu.py +0 -0
  166. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_session_query_token_summary.py +0 -0
  167. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_shutdown_noise_guard.py +0 -0
  168. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_shutdown_noise_integration.py +0 -0
  169. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_skills_slash_command.py +0 -0
  170. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_slash_argument_hint.py +0 -0
  171. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_slash_clear.py +0 -0
  172. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_slash_completer.py +0 -0
  173. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_slash_registry.py +0 -0
  174. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_status_bar.py +0 -0
  175. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_status_bar_transient.py +0 -0
  176. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_task_panel_format.py +0 -0
  177. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_task_panel_key_bindings.py +0 -0
  178. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_task_panel_rendering.py +0 -0
  179. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_task_poll.py +0 -0
  180. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_tool_fold.py +0 -0
  181. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_tool_fold_panel.py +0 -0
  182. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_tool_result_formatters.py +0 -0
  183. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_tool_result_store.py +0 -0
  184. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_tool_result_viewer.py +0 -0
  185. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_tool_result_viewer_key_bindings.py +0 -0
  186. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_tool_view.py +0 -0
  187. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_transcript_viewer.py +0 -0
  188. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_transcript_viewer_tool_fold.py +0 -0
  189. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_tui_elapsed_status.py +0 -0
  190. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_tui_esc_queue.py +0 -0
  191. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_tui_mcp_init_gate.py +0 -0
  192. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_tui_paste_newline_guard.py +0 -0
  193. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_tui_paste_placeholder.py +0 -0
  194. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_tui_queue_preview.py +0 -0
  195. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_tui_queue_sdk_source.py +0 -0
  196. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_tui_split_invariance.py +0 -0
  197. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_tui_startup_latency.py +0 -0
  198. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_tui_team_messages.py +0 -0
  199. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_tui_thinking_display.py +0 -0
  200. {comate_cli-0.7.5 → comate_cli-0.7.7}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
  201. {comate_cli-0.7.5 → comate_cli-0.7.7}/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.7
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
@@ -7,7 +7,12 @@ import os
7
7
  import signal
8
8
  import subprocess
9
9
  import sys
10
+ import time
10
11
  import warnings
12
+
13
+ # 进程启动锚点:在任何重三方库 import(anthropic/prompt_toolkit/rich)之前捕获,
14
+ # 交给 StartupProfiler 测量「run() 之前的 import」这段以往不可见的启动成本。
15
+ _PROCESS_START_PERF = time.perf_counter()
11
16
  try:
12
17
  import termios
13
18
  except ImportError:
@@ -268,7 +273,9 @@ def main(argv: list[str] | None = None) -> None:
268
273
  atexit.register(term_guard.restore, reason="atexit")
269
274
  atexit.register(noise_guard.begin_shutdown)
270
275
 
276
+ _app_import_start_perf = time.perf_counter()
271
277
  from comate_cli.terminal_agent.app import run
278
+ _app_import_done_perf = time.perf_counter()
272
279
 
273
280
  try:
274
281
  rpc_stdio, resume_session_id, resume_select, print_prompt = _parse_args(run_argv)
@@ -295,6 +302,9 @@ def main(argv: list[str] | None = None) -> None:
295
302
  resume_session_id=resume_session_id,
296
303
  resume_select=resume_select,
297
304
  print_message=print_message,
305
+ process_start_perf=_PROCESS_START_PERF,
306
+ app_import_start_perf=_app_import_start_perf,
307
+ app_import_done_perf=_app_import_done_perf,
298
308
  )
299
309
  )
300
310
  except KeyboardInterrupt:
@@ -36,12 +36,15 @@ from comate_cli.terminal_agent.update_check import (
36
36
  get_pending_update_prompt_info,
37
37
  mark_update_seen,
38
38
  record_skip_until_next_version,
39
+ record_update_check_attempt,
39
40
  run_update_command,
41
+ should_check_for_update,
40
42
  show_update_prompt,
41
43
  )
42
44
 
43
45
  console = Console()
44
46
  logger = logging.getLogger(__name__)
47
+ _UPDATE_CHECK_DELAY_S = 1.0
45
48
 
46
49
 
47
50
  def _resolve_cli_project_root() -> Path:
@@ -69,7 +72,7 @@ async def _handle_update_on_launch(info: UpdateInfo) -> bool:
69
72
  if decision == UpdatePromptDecision.SKIP:
70
73
  return False
71
74
  if decision == UpdatePromptDecision.SHOW_HINT:
72
- console.print(format_update_hint(info))
75
+ console.print(f"[dim]{format_update_hint(info)}[/]")
73
76
  mark_update_seen(info)
74
77
  return False
75
78
 
@@ -86,7 +89,7 @@ async def _handle_update_on_launch(info: UpdateInfo) -> bool:
86
89
  async def _handle_background_update_on_launch(
87
90
  info: UpdateInfo,
88
91
  *,
89
- status_bar: StatusBar,
92
+ renderer: EventRenderer,
90
93
  profiler: StartupProfiler,
91
94
  ) -> None:
92
95
  try:
@@ -95,12 +98,12 @@ async def _handle_background_update_on_launch(
95
98
  profiler.mark("update_check.skip")
96
99
  return
97
100
  if decision == UpdatePromptDecision.SHOW_HINT:
98
- status_bar.show_transient(format_update_hint(info), duration_s=8.0)
101
+ renderer.append_system_message(format_update_hint(info))
99
102
  mark_update_seen(info)
100
103
  profiler.mark("update_check.hint")
101
104
  return
102
105
 
103
- status_bar.show_transient(format_update_hint(info), duration_s=8.0)
106
+ renderer.append_system_message(format_update_hint(info))
104
107
  profiler.mark("update_check.prompt_deferred")
105
108
  except Exception:
106
109
  logger.debug("startup background update handling failed", exc_info=True)
@@ -109,18 +112,21 @@ async def _handle_background_update_on_launch(
109
112
 
110
113
  def _schedule_update_check_on_launch(
111
114
  *,
112
- status_bar: StatusBar,
115
+ renderer: EventRenderer,
113
116
  profiler: StartupProfiler,
114
117
  ) -> asyncio.Task[None]:
115
118
  async def _run() -> None:
116
119
  try:
117
120
  profiler.mark("update_check.start")
121
+ profiler.mark("update_check.delay.start")
122
+ await asyncio.sleep(_UPDATE_CHECK_DELAY_S)
123
+ profiler.mark("update_check.delay.done")
118
124
  update_info = await _check_update(profiler=profiler.child("update_check"))
119
125
  profiler.mark("update_check.done")
120
126
  if update_info is not None:
121
127
  await _handle_background_update_on_launch(
122
128
  update_info,
123
- status_bar=status_bar,
129
+ renderer=renderer,
124
130
  profiler=profiler,
125
131
  )
126
132
  except asyncio.CancelledError:
@@ -436,9 +442,18 @@ async def run(
436
442
  resume_session_id: str | None = None,
437
443
  resume_select: bool = False,
438
444
  print_message: str | None = None,
445
+ process_start_perf: float | None = None,
446
+ app_import_start_perf: float | None = None,
447
+ app_import_done_perf: float | None = None,
439
448
  ) -> None:
440
449
  _install_event_loop_exception_handler()
441
- profiler = StartupProfiler.from_env(logger=logger)
450
+ # process_start_perf 由入口 main() 在模块 import 之前捕获,使 profiler 能测到
451
+ # 「run() 之前的重三方库 import」这段以往不可见的启动成本(柱子 A)。
452
+ profiler = StartupProfiler.from_env(logger=logger, started_at=process_start_perf)
453
+ if app_import_start_perf is not None:
454
+ profiler.mark_at("main.app_import.start", app_import_start_perf)
455
+ if app_import_done_perf is not None:
456
+ profiler.mark_at("main.app_import.done", app_import_done_perf)
442
457
  profiler.mark("cli_project_root.start")
443
458
  project_root = _resolve_cli_project_root()
444
459
  profiler.mark("cli_project_root.done")
@@ -534,10 +549,16 @@ async def run(
534
549
  tui = TerminalAgentTUI(session, status_bar, renderer)
535
550
  profiler.mark("tui.init.done")
536
551
  tui.add_resume_history(mode)
537
- update_check_task = _schedule_update_check_on_launch(
538
- status_bar=status_bar,
539
- profiler=profiler,
540
- )
552
+ update_check_task: asyncio.Task[None] | None = None
553
+ if should_check_for_update():
554
+ # attempt 语义:先记录尝试时间,确保 interval 内最多发起一次网络检查
555
+ record_update_check_attempt()
556
+ update_check_task = _schedule_update_check_on_launch(
557
+ renderer=renderer,
558
+ profiler=profiler,
559
+ )
560
+ else:
561
+ profiler.mark("update_check.throttled")
541
562
 
542
563
  async def _mcp_loader() -> None:
543
564
  await _preload_mcp_in_tui(session, profiler=profiler.child("mcp"))
@@ -565,11 +586,12 @@ async def run(
565
586
  )
566
587
  finally:
567
588
  try:
568
- await _cancel_background_task(
569
- update_check_task,
570
- timeout_s=0.1,
571
- task_name="terminal-update-check",
572
- )
589
+ if update_check_task is not None:
590
+ await _cancel_background_task(
591
+ update_check_task,
592
+ timeout_s=0.1,
593
+ task_name="terminal-update-check",
594
+ )
573
595
  if active_session is session:
574
596
  await _graceful_shutdown(active_session)
575
597
  else:
@@ -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,40 +18,65 @@ 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
- def from_env(cls, *, logger: logging.Logger) -> "StartupProfiler":
31
+ def from_env(
32
+ cls,
33
+ *,
34
+ logger: logging.Logger,
35
+ started_at: float | None = None,
36
+ ) -> "StartupProfiler":
29
37
  raw_value = os.environ.get("COMATE_STARTUP_PROFILE", "")
30
38
  enabled = raw_value.strip().lower() in _TRUTHY_VALUES
31
- return cls(logger=logger, enabled=enabled)
39
+ return cls(logger=logger, enabled=enabled, started_at=started_at)
32
40
 
33
41
  def mark(self, phase: str) -> None:
42
+ self.mark_at(phase, time.perf_counter())
43
+
44
+ def mark_at(self, phase: str, perf_value: float) -> None:
45
+ """记录一个发生在指定 perf_counter() 时刻的阶段。
46
+
47
+ 用于补记 profiler 创建之前(如 main 入口的模块 import)已经测好的时间点,
48
+ elapsed_ms 仍以 started_at 为锚点,保持与 mark() 同一坐标系。
49
+ """
34
50
  if not self._enabled:
35
51
  return
36
52
  normalized_phase = phase.strip(".")
37
53
  if self._prefix:
38
54
  normalized_phase = f"{self._prefix}.{normalized_phase}"
39
- 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()
55
+ elapsed_ms = (perf_value - self._started_at) * 1000
56
+ with self._lock:
57
+ self._pending_messages.append(
58
+ f"startup_profile phase={normalized_phase} elapsed_ms={elapsed_ms:.3f}"
59
+ )
60
+ pending = self._drain_pending_locked()
61
+ for message in pending:
62
+ self._logger.info(message)
44
63
 
45
64
  def flush(self) -> None:
46
- if not self._enabled or not self._pending_messages:
65
+ if not self._enabled:
47
66
  return
67
+ with self._lock:
68
+ pending = self._drain_pending_locked()
69
+ for message in pending:
70
+ self._logger.info(message)
71
+
72
+ def _drain_pending_locked(self) -> list[str]:
73
+ if not self._pending_messages:
74
+ return []
48
75
  if not self._logger.isEnabledFor(logging.INFO):
49
- return
76
+ return []
50
77
  pending = list(self._pending_messages)
51
78
  self._pending_messages.clear()
52
- for message in pending:
53
- self._logger.info(message)
79
+ return pending
54
80
 
55
81
  def child(self, prefix: str) -> "StartupProfiler":
56
82
  normalized_prefix = prefix.strip(".")
@@ -64,4 +90,5 @@ class StartupProfiler:
64
90
  started_at=self._started_at,
65
91
  prefix=normalized_prefix,
66
92
  pending_messages=self._pending_messages,
93
+ lock=self._lock,
67
94
  )
@@ -5,6 +5,9 @@ import importlib.metadata
5
5
  import locale
6
6
  import logging
7
7
  import os
8
+ import ssl
9
+ import threading
10
+ import time
8
11
  from dataclasses import dataclass
9
12
  from enum import Enum
10
13
  from pathlib import Path
@@ -24,6 +27,93 @@ PACKAGE_NAME = "comate-cli"
24
27
  RELEASE_NOTES_URL = "https://github.com/AndyLee1024/agent-sdk/releases/latest"
25
28
  UPDATE_COMMAND = ("uv", "tool", "update", PACKAGE_NAME)
26
29
  _SETTINGS_SECTION = "updates"
30
+ _UPDATE_CHECK_TRUST_ENV = "COMATE_CLI_UPDATE_CHECK_TRUST_ENV"
31
+ _TRUTHY_ENV_VALUES = {"1", "true", "yes", "on"}
32
+
33
+ # 启动后台更新检查限频:默认 24h 内只查一次,避免每次启动都付网络 + httpx 构造成本。
34
+ # 设为 0(或负数)表示禁用限频,每次启动都检查。
35
+ _LAST_CHECK_AT_KEY = "last_check_at"
36
+ _UPDATE_CHECK_INTERVAL_ENV = "COMATE_CLI_UPDATE_CHECK_INTERVAL_S"
37
+ _DEFAULT_UPDATE_CHECK_INTERVAL_S = 24 * 60 * 60
38
+
39
+ # certifi 预建的 SSL context(进程级复用)。默认 verify=True 会让 httpx 走
40
+ # ssl.create_default_context() → 在 Windows 上枚举系统证书库,实测可达数秒;
41
+ # 显式传 cafile 则不触发 load_default_certs(),构造降到几十毫秒。
42
+ _update_check_ssl_context: ssl.SSLContext | None = None
43
+ _ssl_context_lock = threading.Lock()
44
+
45
+
46
+ def _get_update_check_ssl_context() -> ssl.SSLContext | None:
47
+ """惰性构建并复用 certifi SSL context;失败返回 None(回退 httpx 默认行为)。"""
48
+ global _update_check_ssl_context
49
+ if _update_check_ssl_context is not None:
50
+ return _update_check_ssl_context
51
+ with _ssl_context_lock:
52
+ if _update_check_ssl_context is None:
53
+ try:
54
+ import certifi
55
+
56
+ _update_check_ssl_context = ssl.create_default_context(
57
+ cafile=certifi.where()
58
+ )
59
+ except Exception:
60
+ logger.debug(
61
+ "failed to build certifi SSL context for update check",
62
+ exc_info=True,
63
+ )
64
+ return None
65
+ return _update_check_ssl_context
66
+
67
+
68
+ def _resolve_update_check_interval_s() -> float:
69
+ raw = os.environ.get(_UPDATE_CHECK_INTERVAL_ENV, "").strip()
70
+ if not raw:
71
+ return float(_DEFAULT_UPDATE_CHECK_INTERVAL_S)
72
+ try:
73
+ value = float(raw)
74
+ except ValueError:
75
+ return float(_DEFAULT_UPDATE_CHECK_INTERVAL_S)
76
+ return value if value >= 0 else float(_DEFAULT_UPDATE_CHECK_INTERVAL_S)
77
+
78
+
79
+ def should_check_for_update(
80
+ *,
81
+ package: str = PACKAGE_NAME,
82
+ settings_path: Path | None = None,
83
+ now: float | None = None,
84
+ ) -> bool:
85
+ """启动时是否应执行网络版本检查(限频判断,纯本地 IO)。"""
86
+ interval_s = _resolve_update_check_interval_s()
87
+ if interval_s <= 0:
88
+ return True
89
+ state = _load_package_update_state(package, settings_path=settings_path)
90
+ try:
91
+ last_check_at = float(state.get(_LAST_CHECK_AT_KEY)) # type: ignore[arg-type]
92
+ except (TypeError, ValueError):
93
+ return True
94
+ current = now if now is not None else time.time()
95
+ if current < last_check_at:
96
+ # 时钟回拨:视为需要重新检查
97
+ return True
98
+ return (current - last_check_at) >= interval_s
99
+
100
+
101
+ def record_update_check_attempt(
102
+ *,
103
+ package: str = PACKAGE_NAME,
104
+ settings_path: Path | None = None,
105
+ now: float | None = None,
106
+ ) -> None:
107
+ """记录一次更新检查尝试时间(attempt 语义:每个 interval 内最多一次网络请求)。"""
108
+ current = now if now is not None else time.time()
109
+ try:
110
+ _write_package_update_state(
111
+ package,
112
+ {_LAST_CHECK_AT_KEY: current},
113
+ settings_path=settings_path,
114
+ )
115
+ except Exception:
116
+ logger.debug("failed to record update check attempt", exc_info=True)
27
117
 
28
118
 
29
119
  class UpdatePromptDecision(Enum):
@@ -65,6 +155,11 @@ def _is_chinese_locale() -> bool:
65
155
  return False
66
156
 
67
157
 
158
+ def _update_check_trust_env() -> bool:
159
+ raw = os.environ.get(_UPDATE_CHECK_TRUST_ENV, "")
160
+ return raw.strip().lower() in _TRUTHY_ENV_VALUES
161
+
162
+
68
163
  async def check_update(
69
164
  *,
70
165
  package: str = PACKAGE_NAME,
@@ -73,77 +168,143 @@ async def check_update(
73
168
  profiler: Any | None = None,
74
169
  ) -> UpdateInfo | None:
75
170
  """异步检查 comate-cli 是否有新版本,返回结构化版本信息或 None。"""
76
- active_logger = log or logger
171
+ if profiler is not None:
172
+ profiler.mark("thread.await.start")
77
173
  try:
174
+ return await asyncio.to_thread(
175
+ check_update_blocking,
176
+ package=package,
177
+ release_notes_url=release_notes_url,
178
+ log=log,
179
+ profiler=profiler,
180
+ )
181
+ except asyncio.CancelledError:
78
182
  if profiler is not None:
79
- profiler.mark("current_version.start")
80
- current = importlib.metadata.version(package)
81
- except importlib.metadata.PackageNotFoundError:
183
+ profiler.mark("thread.await.cancelled")
184
+ raise
185
+ except Exception:
186
+ if profiler is not None:
187
+ profiler.mark("thread.await.failed")
188
+ active_logger = log or logger
189
+ active_logger.debug("update check worker failed", exc_info=True)
82
190
  return None
83
191
  finally:
84
192
  if profiler is not None:
85
- profiler.mark("current_version.done")
193
+ profiler.mark("thread.await.done")
86
194
 
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
195
 
196
+ def check_update_blocking(
197
+ *,
198
+ package: str = PACKAGE_NAME,
199
+ release_notes_url: str = RELEASE_NOTES_URL,
200
+ log: logging.Logger | None = None,
201
+ profiler: Any | None = None,
202
+ ) -> UpdateInfo | None:
203
+ """同步执行版本检查;由 async wrapper 放入 worker thread,避免阻塞 TUI event loop。"""
204
+ active_logger = log or logger
98
205
  try:
99
206
  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")
207
+ profiler.mark("thread.run.start")
104
208
 
105
- async with httpx.AsyncClient(timeout=3.0) as client:
209
+ try:
106
210
  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"]
211
+ profiler.mark("current_version.start")
212
+ current = importlib.metadata.version(package)
213
+ except importlib.metadata.PackageNotFoundError:
214
+ return None
215
+ finally:
111
216
  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
217
+ profiler.mark("current_version.done")
116
218
 
117
- try:
118
219
  if profiler is not None:
119
- profiler.mark("version_compare.start")
120
- from packaging.version import Version
220
+ profiler.mark("locale.start")
221
+ if _is_chinese_locale():
222
+ url = f"https://mirrors.tuna.tsinghua.edu.cn/pypi/{package}/json"
223
+ source_label = "tuna"
224
+ else:
225
+ url = f"https://pypi.org/pypi/{package}/json"
226
+ source_label = "pypi"
227
+ if profiler is not None:
228
+ profiler.mark("locale.done")
121
229
 
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,
230
+ try:
231
+ if profiler is not None:
232
+ profiler.mark("httpx_import.start")
233
+ import httpx
234
+ if profiler is not None:
235
+ profiler.mark("httpx_import.done")
236
+
237
+ trust_env = _update_check_trust_env()
238
+ if profiler is not None:
239
+ profiler.mark(
240
+ "http_client.trust_env.enabled"
241
+ if trust_env
242
+ else "http_client.trust_env.disabled"
243
+ )
244
+ profiler.mark("http_client.construct.start")
245
+ client_kwargs: dict[str, Any] = {"timeout": 3.0, "trust_env": trust_env}
246
+ if not trust_env:
247
+ # 默认路径用 certifi context 绕开 Windows 证书库枚举(见 _get_update_check_ssl_context)。
248
+ # trust_env 逃生通道(企业代理/自签根证书)保持 httpx 默认 verify=True 行为不变。
249
+ ssl_context = _get_update_check_ssl_context()
250
+ if ssl_context is not None:
251
+ client_kwargs["verify"] = ssl_context
252
+ client = httpx.Client(**client_kwargs)
253
+ if profiler is not None:
254
+ profiler.mark("http_client.construct.done")
255
+
256
+ if profiler is not None:
257
+ profiler.mark("http_client.enter.start")
258
+ with client:
259
+ if profiler is not None:
260
+ profiler.mark("http_client.enter.done")
261
+ profiler.mark("http_get.start")
262
+ resp = client.get(url)
263
+ resp.raise_for_status()
264
+ latest = resp.json()["info"]["version"]
265
+ if profiler is not None:
266
+ profiler.mark("http_get.done")
267
+ except Exception:
268
+ if profiler is not None:
269
+ profiler.mark("http_client.failed")
270
+ active_logger.debug(f"update check failed (source={source_label})", exc_info=True)
271
+ return None
272
+
273
+ try:
274
+ if profiler is not None:
275
+ profiler.mark("version_compare.start")
276
+ from packaging.version import Version
277
+
278
+ if Version(latest) > Version(current):
279
+ return UpdateInfo(
280
+ package=package,
281
+ current_version=current,
282
+ latest_version=latest,
283
+ release_notes_url=release_notes_url,
284
+ )
285
+ except Exception:
286
+ active_logger.debug(
287
+ "update comparison failed (current=%s, latest=%s)",
288
+ current,
289
+ latest,
290
+ exc_info=True,
128
291
  )
129
- except Exception:
130
- active_logger.debug(
131
- "update comparison failed (current=%s, latest=%s)",
132
- current,
133
- latest,
134
- exc_info=True,
135
- )
292
+ finally:
293
+ if profiler is not None:
294
+ profiler.mark("version_compare.done")
295
+ return None
136
296
  finally:
137
297
  if profiler is not None:
138
- profiler.mark("version_compare.done")
139
- return None
298
+ profiler.mark("thread.run.done")
140
299
 
141
300
 
142
301
  def format_update_hint(info: UpdateInfo) -> str:
302
+ # Returns plain text only. Callers are responsible for adding markup
303
+ # appropriate to their render target (Rich Console vs prompt_toolkit).
143
304
  return (
144
- f"[dim] New version available: [bold cyan]{info.latest_version}[/] "
305
+ f" New version available: {info.latest_version} "
145
306
  f"(current: {info.current_version}) "
146
- f"Run [bold green]uv tool update {info.package}[/] to update.[/]"
307
+ f"Run `uv tool update {info.package}` to update."
147
308
  )
148
309
 
149
310
 
@@ -367,7 +528,7 @@ def _load_package_update_state(
367
528
 
368
529
  def _write_package_update_state(
369
530
  package: str,
370
- values: dict[str, str],
531
+ values: dict[str, Any],
371
532
  *,
372
533
  settings_path: Path | None,
373
534
  ) -> None:
@@ -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.7"
8
8
  description = "Comate terminal CLI built on comate-agent-sdk"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11,<3.14"
@@ -0,0 +1,33 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ import pytest
7
+
8
+
9
+ def _ensure_cli_package_on_path() -> None:
10
+ cli_project_root = Path(__file__).resolve().parents[1]
11
+ cli_project_root_str = str(cli_project_root)
12
+ if cli_project_root_str not in sys.path:
13
+ sys.path.insert(0, cli_project_root_str)
14
+
15
+
16
+ _ensure_cli_package_on_path()
17
+
18
+
19
+ @pytest.fixture(autouse=True)
20
+ def _isolate_update_check_settings(tmp_path, monkeypatch):
21
+ """每个测试用独立临时 settings.json,隔离启动更新检查的限频状态。
22
+
23
+ update_check 的限频(last_check_at)与 last_seen_version 都落在 USER_SETTINGS_PATH。
24
+ 驱动 run()/scheduler 的集成测试若写到真实用户配置,会造成跨用例顺序相关的脏状态。
25
+ 在这里把模块级 USER_SETTINGS_PATH 指向临时目录,统一隔离。
26
+ 显式传 settings_path 的单测不受影响。
27
+ """
28
+ from comate_cli.terminal_agent import update_check
29
+
30
+ monkeypatch.setattr(
31
+ update_check, "USER_SETTINGS_PATH", tmp_path / "settings.json"
32
+ )
33
+ yield