comate-cli 0.7.8__tar.gz → 0.7.9__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 (204) hide show
  1. {comate_cli-0.7.8 → comate_cli-0.7.9}/PKG-INFO +1 -1
  2. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/main.py +113 -11
  3. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/app.py +70 -26
  4. comate_cli-0.7.9/comate_cli/terminal_agent/print_mode.py +224 -0
  5. {comate_cli-0.7.8 → comate_cli-0.7.9}/pyproject.toml +1 -1
  6. comate_cli-0.7.9/tests/test_app_print_mode.py +375 -0
  7. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_app_startup_latency.py +82 -2
  8. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_app_token_cost_config.py +68 -0
  9. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_main_args.py +122 -11
  10. comate_cli-0.7.8/tests/test_app_print_mode.py +0 -99
  11. {comate_cli-0.7.8 → comate_cli-0.7.9}/.gitignore +0 -0
  12. {comate_cli-0.7.8 → comate_cli-0.7.9}/CHANGELOG.md +0 -0
  13. {comate_cli-0.7.8 → comate_cli-0.7.9}/README.md +0 -0
  14. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/__init__.py +0 -0
  15. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/__main__.py +0 -0
  16. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/mcp_cli.py +0 -0
  17. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/__init__.py +0 -0
  18. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/animations.py +0 -0
  19. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/assistant_render.py +0 -0
  20. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/codenames.py +0 -0
  21. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/config/__init__.py +0 -0
  22. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/config/model.py +0 -0
  23. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/config/picker.py +0 -0
  24. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/config/picker_state.py +0 -0
  25. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/config/store.py +0 -0
  26. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
  27. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/env_utils.py +0 -0
  28. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/error_display.py +0 -0
  29. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/event_renderer.py +0 -0
  30. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/figures.py +0 -0
  31. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/fragment_utils.py +0 -0
  32. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/goal_resume_view.py +0 -0
  33. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/history_printer.py +0 -0
  34. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/input_geometry.py +0 -0
  35. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
  36. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/logging_adapter.py +0 -0
  37. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/logo.py +0 -0
  38. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/markdown_render.py +0 -0
  39. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/mention_completer.py +0 -0
  40. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/message_style.py +0 -0
  41. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/models.py +0 -0
  42. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/path_context_hint.py +0 -0
  43. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
  44. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
  45. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
  46. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
  47. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
  48. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
  49. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
  50. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
  51. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
  52. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
  53. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
  54. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
  55. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
  56. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/preflight.py +0 -0
  57. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/preflight_wizard.py +0 -0
  58. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/question_view.py +0 -0
  59. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/resume_picker.py +0 -0
  60. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/resume_preview.py +0 -0
  61. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/resume_selector.py +0 -0
  62. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
  63. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
  64. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/selection_menu.py +0 -0
  65. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/slash_commands.py +0 -0
  66. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/startup.py +0 -0
  67. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/startup_profile.py +0 -0
  68. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/status_bar.py +0 -0
  69. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/statusline/__init__.py +0 -0
  70. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/statusline/model.py +0 -0
  71. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/statusline/picker.py +0 -0
  72. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/statusline/picker_state.py +0 -0
  73. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/statusline/store.py +0 -0
  74. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/text_effects.py +0 -0
  75. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tips.py +0 -0
  76. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tool_fold.py +0 -0
  77. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tool_result_formatters.py +0 -0
  78. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tool_result_store.py +0 -0
  79. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
  80. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tool_view.py +0 -0
  81. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/transcript_viewer.py +0 -0
  82. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui.py +0 -0
  83. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
  84. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
  85. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui_parts/commands.py +0 -0
  86. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui_parts/history_sync.py +0 -0
  87. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
  88. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui_parts/key_bindings.py +0 -0
  89. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
  90. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui_parts/render_panels.py +0 -0
  91. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
  92. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
  93. {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/update_check.py +0 -0
  94. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/config/__init__.py +0 -0
  95. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/config/test_model.py +0 -0
  96. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/config/test_picker_state.py +0 -0
  97. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/config/test_picker_ui.py +0 -0
  98. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/config/test_roundtrip.py +0 -0
  99. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/config/test_store_load.py +0 -0
  100. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/config/test_store_save.py +0 -0
  101. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/conftest.py +0 -0
  102. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
  103. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/statusline/__init__.py +0 -0
  104. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/statusline/test_model.py +0 -0
  105. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/statusline/test_picker_state.py +0 -0
  106. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/statusline/test_store.py +0 -0
  107. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_animator_shuffle.py +0 -0
  108. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_app_mcp_preload.py +0 -0
  109. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_app_preflight_gate.py +0 -0
  110. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_app_shutdown.py +0 -0
  111. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_app_usage_line.py +0 -0
  112. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_btw_slash_command.py +0 -0
  113. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_cli_project_root.py +0 -0
  114. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_compact_command_semantics.py +0 -0
  115. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_completion_context_activation.py +0 -0
  116. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_completion_status_panel.py +0 -0
  117. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_context_command.py +0 -0
  118. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_custom_slash_commands.py +0 -0
  119. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_discover_tab.py +0 -0
  120. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_errors_tab.py +0 -0
  121. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_event_renderer.py +0 -0
  122. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_event_renderer_boundary.py +0 -0
  123. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_event_renderer_e2e.py +0 -0
  124. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_event_renderer_log_boundary.py +0 -0
  125. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_event_renderer_log_queue.py +0 -0
  126. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_event_renderer_streaming.py +0 -0
  127. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_event_renderer_tool_fold.py +0 -0
  128. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_format_error.py +0 -0
  129. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_goal_resume_tui.py +0 -0
  130. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_goal_resume_view.py +0 -0
  131. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_goal_slash_command.py +0 -0
  132. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_handle_error.py +0 -0
  133. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_history_printer.py +0 -0
  134. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_history_printer_log.py +0 -0
  135. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_history_printer_subtitle_position.py +0 -0
  136. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_history_printer_tool_fold.py +0 -0
  137. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_history_sync.py +0 -0
  138. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_history_sync_tool_fold.py +0 -0
  139. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_input_behavior.py +0 -0
  140. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_input_history.py +0 -0
  141. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_installed_tab.py +0 -0
  142. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_interrupt_exit_semantics.py +0 -0
  143. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_layout_coordinator.py +0 -0
  144. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_logging_adapter.py +0 -0
  145. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_logo.py +0 -0
  146. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_markdown_render.py +0 -0
  147. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_marketplaces_tab.py +0 -0
  148. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_mcp_cli.py +0 -0
  149. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_mcp_slash_command.py +0 -0
  150. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_mention_completer.py +0 -0
  151. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_path_context_hint.py +0 -0
  152. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_plugin_slash_commands.py +0 -0
  153. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_plugin_tui_components.py +0 -0
  154. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_preflight.py +0 -0
  155. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_preflight_copilot.py +0 -0
  156. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_question_key_bindings.py +0 -0
  157. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_question_view.py +0 -0
  158. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_resume_picker.py +0 -0
  159. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_resume_preview.py +0 -0
  160. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_resume_selector.py +0 -0
  161. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_rewind_command_semantics.py +0 -0
  162. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_rpc_protocol.py +0 -0
  163. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_rpc_stdio_bridge.py +0 -0
  164. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_selection_menu.py +0 -0
  165. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_session_query_token_summary.py +0 -0
  166. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_shutdown_noise_guard.py +0 -0
  167. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_shutdown_noise_integration.py +0 -0
  168. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_skills_slash_command.py +0 -0
  169. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_slash_argument_hint.py +0 -0
  170. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_slash_clear.py +0 -0
  171. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_slash_completer.py +0 -0
  172. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_slash_registry.py +0 -0
  173. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_startup_import_budget.py +0 -0
  174. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_startup_profile.py +0 -0
  175. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_status_bar.py +0 -0
  176. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_status_bar_transient.py +0 -0
  177. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_task_panel_format.py +0 -0
  178. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_task_panel_key_bindings.py +0 -0
  179. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_task_panel_rendering.py +0 -0
  180. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_task_poll.py +0 -0
  181. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tool_fold.py +0 -0
  182. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tool_fold_panel.py +0 -0
  183. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tool_result_formatters.py +0 -0
  184. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tool_result_store.py +0 -0
  185. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tool_result_viewer.py +0 -0
  186. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tool_result_viewer_key_bindings.py +0 -0
  187. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tool_view.py +0 -0
  188. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_transcript_viewer.py +0 -0
  189. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_transcript_viewer_tool_fold.py +0 -0
  190. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_elapsed_status.py +0 -0
  191. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_esc_queue.py +0 -0
  192. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_mcp_init_gate.py +0 -0
  193. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_paste_newline_guard.py +0 -0
  194. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_paste_placeholder.py +0 -0
  195. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_queue_preview.py +0 -0
  196. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_queue_sdk_source.py +0 -0
  197. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_split_invariance.py +0 -0
  198. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_startup_latency.py +0 -0
  199. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_team_messages.py +0 -0
  200. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_thinking_display.py +0 -0
  201. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
  202. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_update_check.py +0 -0
  203. {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_usage_command.py +0 -0
  204. {comate_cli-0.7.8 → comate_cli-0.7.9}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: comate-cli
3
- Version: 0.7.8
3
+ Version: 0.7.9
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
@@ -26,6 +26,15 @@ class _ArgumentError(ValueError):
26
26
  """Raised when CLI arguments are invalid."""
27
27
 
28
28
 
29
+ PRINT_STDIN_IDLE_TIMEOUT_SECONDS = 3.0
30
+ PRINT_OUTPUT_FORMAT_TEXT = "text"
31
+ PRINT_OUTPUT_FORMAT_JSON = "json"
32
+ PRINT_OUTPUT_FORMATS = frozenset({PRINT_OUTPUT_FORMAT_TEXT, PRINT_OUTPUT_FORMAT_JSON})
33
+ PRINT_OUTPUT_FORMAT_UNSUPPORTED = frozenset({"stream-json"})
34
+
35
+ ParsedArgs = tuple[bool, str | None, bool, str | None, str]
36
+
37
+
29
38
  class _TerminalStateGuard:
30
39
  """Best-effort tty restore guard for abnormal shutdown paths."""
31
40
 
@@ -200,37 +209,68 @@ def _usage_text() -> str:
200
209
  return (
201
210
  "Usage:\n"
202
211
  " comate [--rpc-stdio]\n"
203
- " comate -p <prompt>\n"
212
+ " comate -p [--output-format text|json] <prompt>\n"
204
213
  " comate resume [<session_id>] [--rpc-stdio]\n"
205
214
  " comate mcp <subcommand> [options]"
206
215
  )
207
216
 
208
217
 
209
- def _parse_args(argv: list[str]) -> tuple[bool, str | None, bool, str | None]:
218
+ def _parse_args(argv: list[str]) -> ParsedArgs:
210
219
  rpc_stdio = False
211
220
  print_mode = False
221
+ output_format = PRINT_OUTPUT_FORMAT_TEXT
222
+ output_format_explicit = False
212
223
  positionals: list[str] = []
213
- for arg in argv:
224
+
225
+ index = 0
226
+ while index < len(argv):
227
+ arg = argv[index]
214
228
  if arg == "--rpc-stdio":
215
229
  rpc_stdio = True
230
+ index += 1
216
231
  continue
217
232
  if arg in ("-p", "--print"):
218
233
  print_mode = True
234
+ index += 1
235
+ continue
236
+ if arg == "--output-format" or arg.startswith("--output-format="):
237
+ output_format_explicit = True
238
+ if arg == "--output-format":
239
+ index += 1
240
+ if index >= len(argv):
241
+ raise _ArgumentError("--output-format requires one of: text, json")
242
+ value = argv[index]
243
+ else:
244
+ value = arg.partition("=")[2]
245
+ if not value:
246
+ raise _ArgumentError("--output-format requires one of: text, json")
247
+ if value in PRINT_OUTPUT_FORMAT_UNSUPPORTED:
248
+ raise _ArgumentError(
249
+ "--output-format stream-json is not supported in this release"
250
+ )
251
+ if value not in PRINT_OUTPUT_FORMATS:
252
+ raise _ArgumentError(f"Unsupported --output-format: {value}")
253
+ output_format = value
254
+ index += 1
219
255
  continue
220
256
  if arg.startswith("-"):
221
257
  raise _ArgumentError(f"Unknown option: {arg}")
222
258
  positionals.append(arg)
259
+ index += 1
260
+
261
+ if output_format_explicit and not print_mode:
262
+ raise _ArgumentError("--output-format can only be used with -p/--print")
223
263
 
224
264
  # -p mode: all positionals become the prompt
225
265
  if print_mode:
226
266
  if rpc_stdio:
227
267
  raise _ArgumentError("-p/--print and --rpc-stdio are mutually exclusive")
228
268
  print_prompt = " ".join(positionals) if positionals else ""
229
- return rpc_stdio, None, False, print_prompt
269
+ return rpc_stdio, None, False, print_prompt, output_format
230
270
 
231
271
  # Non -p mode: original logic
232
272
  if not positionals:
233
- return rpc_stdio, None, False, None
273
+ return rpc_stdio, None, False, None, output_format
234
274
 
235
275
  command = positionals[0]
236
276
  if command != "resume":
@@ -241,14 +281,75 @@ def _parse_args(argv: list[str]) -> tuple[bool, str | None, bool, str | None]:
241
281
  raise _ArgumentError(
242
282
  "resume without <session_id> does not support --rpc-stdio"
243
283
  )
244
- return rpc_stdio, None, True, None
284
+ return rpc_stdio, None, True, None, output_format
245
285
 
246
286
  if len(positionals) == 2:
247
- return rpc_stdio, positionals[1], False, None
287
+ return rpc_stdio, positionals[1], False, None, output_format
248
288
 
249
289
  raise _ArgumentError("resume accepts at most one <session_id>")
250
290
 
251
291
 
292
+ def _read_stdin_posix_idle_drain(
293
+ stdin: object,
294
+ *,
295
+ timeout_seconds: float,
296
+ chunk_size: int = 64 * 1024,
297
+ ) -> str:
298
+ import select
299
+
300
+ fileno = getattr(stdin, "fileno", None)
301
+ if not callable(fileno):
302
+ read = getattr(stdin, "read", None)
303
+ return str(read()) if callable(read) else ""
304
+
305
+ try:
306
+ fd = fileno()
307
+ except (AttributeError, OSError, ValueError):
308
+ read = getattr(stdin, "read", None)
309
+ return str(read()) if callable(read) else ""
310
+
311
+ chunks: list[bytes] = []
312
+ while True:
313
+ readable, _, _ = select.select([fd], [], [], timeout_seconds)
314
+ if not readable:
315
+ break
316
+ try:
317
+ chunk = os.read(fd, chunk_size)
318
+ except BlockingIOError:
319
+ break
320
+ except OSError:
321
+ break
322
+ if not chunk:
323
+ break
324
+ chunks.append(chunk)
325
+
326
+ encoding = str(getattr(stdin, "encoding", "") or "utf-8")
327
+ return b"".join(chunks).decode(encoding, errors="replace")
328
+
329
+
330
+ def _read_stdin_if_ready(
331
+ stdin: object,
332
+ *,
333
+ timeout_seconds: float = PRINT_STDIN_IDLE_TIMEOUT_SECONDS,
334
+ ) -> str:
335
+ isatty = getattr(stdin, "isatty", None)
336
+ if callable(isatty) and isatty():
337
+ return ""
338
+
339
+ if os.name != "posix":
340
+ read = getattr(stdin, "read", None)
341
+ return str(read()) if callable(read) else ""
342
+
343
+ try:
344
+ return _read_stdin_posix_idle_drain(
345
+ stdin,
346
+ timeout_seconds=timeout_seconds,
347
+ )
348
+ except (OSError, ValueError):
349
+ read = getattr(stdin, "read", None)
350
+ return str(read()) if callable(read) else ""
351
+
352
+
252
353
  def main(argv: list[str] | None = None) -> None:
253
354
  run_argv = list(argv) if argv is not None else sys.argv[1:]
254
355
 
@@ -278,7 +379,9 @@ def main(argv: list[str] | None = None) -> None:
278
379
  _app_import_done_perf = time.perf_counter()
279
380
 
280
381
  try:
281
- rpc_stdio, resume_session_id, resume_select, print_prompt = _parse_args(run_argv)
382
+ rpc_stdio, resume_session_id, resume_select, print_prompt, print_output_format = (
383
+ _parse_args(run_argv)
384
+ )
282
385
  except _ArgumentError as exc:
283
386
  sys.stderr.write(f"{exc}\n{_usage_text()}\n")
284
387
  raise SystemExit(2) from exc
@@ -286,9 +389,7 @@ def main(argv: list[str] | None = None) -> None:
286
389
  # Assemble print mode message
287
390
  print_message: str | None = None
288
391
  if print_prompt is not None:
289
- stdin_text = ""
290
- if not sys.stdin.isatty():
291
- stdin_text = sys.stdin.read()
392
+ stdin_text = _read_stdin_if_ready(sys.stdin)
292
393
  parts = [p for p in (stdin_text.strip(), print_prompt.strip()) if p]
293
394
  if not parts:
294
395
  sys.stderr.write("Error: -p requires a prompt argument or stdin input\n")
@@ -302,6 +403,7 @@ def main(argv: list[str] | None = None) -> None:
302
403
  resume_session_id=resume_session_id,
303
404
  resume_select=resume_select,
304
405
  print_message=print_message,
406
+ print_output_format=print_output_format,
305
407
  process_start_perf=_PROCESS_START_PERF,
306
408
  app_import_start_perf=_app_import_start_perf,
307
409
  app_import_done_perf=_app_import_done_perf,
@@ -5,7 +5,6 @@ import logging
5
5
  import os
6
6
  import random # noqa: F401 - tests patch this module-level import for deterministic startup tips.
7
7
  import signal
8
- import sys
9
8
  import threading
10
9
  import time
11
10
  from collections.abc import Iterator
@@ -20,13 +19,30 @@ from comate_cli.terminal_agent.startup_profile import StartupProfiler
20
19
  if TYPE_CHECKING:
21
20
  from comate_agent_sdk.agent.chat_session import ChatSession
22
21
  from comate_agent_sdk.agent.core.template import AgentTemplate as Agent
23
- from comate_agent_sdk.agent.events import TextEvent
24
22
  from comate_cli.terminal_agent.event_renderer import EventRenderer
25
23
  from comate_cli.terminal_agent.update_check import UpdateInfo
26
24
 
27
25
  console = Console()
28
26
  logger = logging.getLogger(__name__)
29
27
  _UPDATE_CHECK_DELAY_S = 1.0
28
+ PRINT_MODE_DISALLOWED_TOOLS = (
29
+ "TeamCreate",
30
+ "TeamDelete",
31
+ "SendMessage",
32
+ "YieldTurn",
33
+ )
34
+
35
+
36
+ def _merge_disallowed_tools(*groups: tuple[str, ...] | None) -> tuple[str, ...] | None:
37
+ merged: list[str] = []
38
+ seen: set[str] = set()
39
+ for group in groups:
40
+ for name in group or ():
41
+ if name in seen:
42
+ continue
43
+ seen.add(name)
44
+ merged.append(name)
45
+ return tuple(merged) or None
30
46
 
31
47
 
32
48
  def _resolve_cli_project_root() -> Path:
@@ -221,6 +237,18 @@ def _import_preflight_runner():
221
237
  return run_preflight_if_needed
222
238
 
223
239
 
240
+ def _import_print_mode_runner():
241
+ from comate_cli.terminal_agent.print_mode import run_print_mode
242
+
243
+ return run_print_mode
244
+
245
+
246
+ def _import_print_mode_json_error_writer():
247
+ from comate_cli.terminal_agent.print_mode import write_json_error_result
248
+
249
+ return write_json_error_result
250
+
251
+
224
252
  def print_logo(*args, **kwargs) -> None:
225
253
  from comate_cli.terminal_agent.logo import print_logo as _print_logo
226
254
 
@@ -357,6 +385,7 @@ def _build_agent(
357
385
  *,
358
386
  project_root: Path | None = None,
359
387
  profiler: StartupProfiler | None = None,
388
+ extra_disallowed_tools: tuple[str, ...] | None = None,
360
389
  ) -> Agent:
361
390
  Agent, AgentConfig, CompactionConfig, EnvOptions = _import_sdk_agent_components(profiler)
362
391
  from comate_cli.terminal_agent.config import store
@@ -367,6 +396,7 @@ def _build_agent(
367
396
  snapshot = store.load()
368
397
  if profiler is not None:
369
398
  profiler.mark("agent.config.load.done")
399
+ disallowed_tools = _merge_disallowed_tools(extra_disallowed_tools)
370
400
 
371
401
  agent_config = AgentConfig(
372
402
  role="software_engineering",
@@ -374,11 +404,13 @@ def _build_agent(
374
404
  env_options=EnvOptions(system_env=True, git_env=True),
375
405
  use_streaming_task=True,
376
406
  include_cost=bool(getattr(snapshot, "token_cost_enabled", False)),
407
+ strict_hook_settings=False,
377
408
  memory_background_enabled=snapshot.memory_background_enabled,
378
409
  memory_dreaming_enabled=snapshot.memory_dreaming_enabled,
379
410
  compaction=CompactionConfig(
380
411
  threshold_ratio=snapshot.compaction_threshold_ratio,
381
412
  ),
413
+ disallowed_tools=disallowed_tools,
382
414
  )
383
415
  if profiler is not None:
384
416
  profiler.mark("agent.construct.start")
@@ -502,28 +534,17 @@ async def _run_print_mode(
502
534
  message: str,
503
535
  *,
504
536
  project_root: Path,
537
+ output_format: str = "text",
505
538
  ) -> None:
506
- ChatSession, TextEvent = _import_print_mode_components()
507
- session = ChatSession(agent, cwd=project_root, persistent=False)
508
- try:
509
- await _preload_mcp_in_tui(session)
510
-
511
- final_text = ""
512
- async for event in session.query_stream(message):
513
- if isinstance(event, TextEvent):
514
- final_text = event.content
515
-
516
- if final_text:
517
- sys.stdout.write(final_text)
518
- if not final_text.endswith("\n"):
519
- sys.stdout.write("\n")
520
- sys.stdout.flush()
521
- except Exception as exc:
522
- logger.error(f"Print mode failed: {exc}", exc_info=True)
523
- sys.stderr.write(f"Error: {exc}\n")
524
- raise SystemExit(1) from exc
525
- finally:
526
- await _graceful_shutdown(session)
539
+ run_print_mode = _import_print_mode_runner()
540
+ await run_print_mode(
541
+ agent,
542
+ message,
543
+ project_root=project_root,
544
+ output_format=output_format,
545
+ preload_mcp=_preload_mcp_in_tui,
546
+ graceful_shutdown=_graceful_shutdown,
547
+ )
527
548
 
528
549
 
529
550
  def _install_event_loop_exception_handler() -> None:
@@ -557,6 +578,7 @@ async def run(
557
578
  resume_session_id: str | None = None,
558
579
  resume_select: bool = False,
559
580
  print_message: str | None = None,
581
+ print_output_format: str = "text",
560
582
  process_start_perf: float | None = None,
561
583
  app_import_start_perf: float | None = None,
562
584
  app_import_done_perf: float | None = None,
@@ -576,14 +598,25 @@ async def run(
576
598
  profiler.mark("cli_project_root.done")
577
599
  profiler.mark("preflight.start")
578
600
  run_preflight_if_needed = _import_preflight_runner()
601
+ preflight_console = console
602
+ if print_message is not None and print_output_format == "json":
603
+ preflight_console = Console(stderr=True)
579
604
  preflight_result = await run_preflight_if_needed(
580
- console=console,
605
+ console=preflight_console,
581
606
  project_root=project_root,
582
607
  interactive=not rpc_stdio and print_message is None,
583
608
  )
584
609
  profiler.mark("preflight.done")
585
610
  if preflight_result.should_abort_launch:
586
611
  if print_message is not None:
612
+ if print_output_format == "json":
613
+ write_json_error_result = _import_print_mode_json_error_writer()
614
+ detail = preflight_result.detail or preflight_result.status
615
+ write_json_error_result(
616
+ error=f"Preflight failed: {detail}",
617
+ stop_reason=None,
618
+ session_id=None,
619
+ )
587
620
  raise SystemExit(1)
588
621
  return
589
622
 
@@ -604,7 +637,13 @@ async def run(
604
637
  return
605
638
 
606
639
  profiler.mark("agent.build.start")
607
- agent = _build_agent(project_root=project_root, profiler=profiler)
640
+ agent = _build_agent(
641
+ project_root=project_root,
642
+ profiler=profiler,
643
+ extra_disallowed_tools=(
644
+ PRINT_MODE_DISALLOWED_TOOLS if print_message is not None else None
645
+ ),
646
+ )
608
647
  profiler.mark("agent.build.done")
609
648
 
610
649
  if rpc_stdio and resume_select and not resume_session_id:
@@ -628,7 +667,12 @@ async def run(
628
667
 
629
668
  # --- Print mode branch ---
630
669
  if print_message is not None:
631
- await _run_print_mode(agent, print_message, project_root=project_root)
670
+ await _run_print_mode(
671
+ agent,
672
+ print_message,
673
+ project_root=project_root,
674
+ output_format=print_output_format,
675
+ )
632
676
  return
633
677
 
634
678
  if resume_select and not resume_session_id:
@@ -0,0 +1,224 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import logging
5
+ import sys
6
+ import time
7
+ import uuid
8
+ from collections.abc import Awaitable, Callable
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from comate_agent_sdk.tokens import UsageSummary
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ TEXT_OUTPUT_FORMAT = "text"
18
+ JSON_OUTPUT_FORMAT = "json"
19
+ ERROR_DURING_EXECUTION = "error_during_execution"
20
+ ERROR_MAX_TURNS = "error_max_turns"
21
+
22
+
23
+ def _import_print_mode_components() -> tuple[type, type, type, type]:
24
+ from comate_agent_sdk.agent.chat_session import ChatSession
25
+ from comate_agent_sdk.agent.events import SessionInitEvent, StopEvent, TextEvent
26
+
27
+ return ChatSession, SessionInitEvent, StopEvent, TextEvent
28
+
29
+
30
+ @dataclass(slots=True)
31
+ class _PrintRunFacts:
32
+ final_text: str = ""
33
+ structured_output: dict[str, Any] | None = None
34
+ stop_reason: str | None = None
35
+ session_id: str | None = None
36
+
37
+
38
+ def _empty_usage_summary() -> UsageSummary:
39
+ return UsageSummary(
40
+ total_prompt_tokens=0,
41
+ total_prompt_cost=0.0,
42
+ total_prompt_cached_tokens=0,
43
+ total_prompt_cached_cost=0.0,
44
+ total_completion_tokens=0,
45
+ total_reasoning_tokens=0,
46
+ total_completion_cost=0.0,
47
+ total_tokens=0,
48
+ total_cost=0.0,
49
+ entry_count=0,
50
+ by_model={},
51
+ by_level={},
52
+ by_source={},
53
+ )
54
+
55
+
56
+ async def _get_usage_or_empty(session: object | None) -> UsageSummary:
57
+ if session is None:
58
+ return _empty_usage_summary()
59
+ get_usage: Callable[..., Awaitable[object]] | None = getattr(session, "get_usage", None)
60
+ if not callable(get_usage):
61
+ return _empty_usage_summary()
62
+ try:
63
+ usage = await get_usage()
64
+ except Exception as exc:
65
+ logger.warning("Print mode failed to collect usage: %s", exc, exc_info=True)
66
+ return _empty_usage_summary()
67
+ if isinstance(usage, UsageSummary):
68
+ return usage
69
+ return _empty_usage_summary()
70
+
71
+
72
+ def _dump_usage(usage: UsageSummary) -> dict[str, Any]:
73
+ return usage.model_dump(mode="json")
74
+
75
+
76
+ def _is_error_stop(stop_reason: str | None) -> bool:
77
+ return stop_reason is not None and stop_reason != "completed"
78
+
79
+
80
+ def _subtype_for_stop(stop_reason: str | None) -> str:
81
+ if stop_reason == "max_iterations":
82
+ return ERROR_MAX_TURNS
83
+ if _is_error_stop(stop_reason):
84
+ return ERROR_DURING_EXECUTION
85
+ return "success"
86
+
87
+
88
+ def _build_result_payload(
89
+ *,
90
+ facts: _PrintRunFacts,
91
+ usage: UsageSummary,
92
+ duration_ms: int,
93
+ errors: list[str] | None = None,
94
+ ) -> dict[str, Any]:
95
+ is_error = bool(errors) or _is_error_stop(facts.stop_reason)
96
+ subtype = ERROR_DURING_EXECUTION if errors else _subtype_for_stop(facts.stop_reason)
97
+ payload: dict[str, Any] = {
98
+ "type": "result",
99
+ "subtype": subtype,
100
+ "duration_ms": duration_ms,
101
+ "duration_api_ms": 0,
102
+ "is_error": is_error,
103
+ "num_turns": 1,
104
+ "stop_reason": facts.stop_reason,
105
+ "total_cost_usd": usage.total_cost,
106
+ "usage": _dump_usage(usage),
107
+ "modelUsage": {},
108
+ "permission_denials": [],
109
+ "uuid": str(uuid.uuid4()),
110
+ "session_id": facts.session_id,
111
+ }
112
+ if is_error:
113
+ payload["errors"] = errors or [f"Run stopped: {facts.stop_reason}"]
114
+ else:
115
+ payload["result"] = facts.final_text
116
+ if facts.structured_output is not None:
117
+ payload["structured_output"] = facts.structured_output
118
+ return payload
119
+
120
+
121
+ def _write_text_result(text: str) -> None:
122
+ if not text:
123
+ return
124
+ sys.stdout.write(text)
125
+ if not text.endswith("\n"):
126
+ sys.stdout.write("\n")
127
+ sys.stdout.flush()
128
+
129
+
130
+ def _write_json_result(payload: dict[str, Any]) -> None:
131
+ sys.stdout.write(json.dumps(payload, ensure_ascii=False, separators=(",", ":")))
132
+ sys.stdout.write("\n")
133
+ sys.stdout.flush()
134
+
135
+
136
+ def write_json_error_result(
137
+ *,
138
+ error: str,
139
+ stop_reason: str | None = None,
140
+ session_id: str | None = None,
141
+ duration_ms: int = 0,
142
+ ) -> None:
143
+ payload = _build_result_payload(
144
+ facts=_PrintRunFacts(stop_reason=stop_reason, session_id=session_id),
145
+ usage=_empty_usage_summary(),
146
+ duration_ms=duration_ms,
147
+ errors=[error],
148
+ )
149
+ _write_json_result(payload)
150
+
151
+
152
+ async def _consume_query_stream(session: object, message: str) -> _PrintRunFacts:
153
+ ChatSession, SessionInitEvent, StopEvent, TextEvent = _import_print_mode_components()
154
+ del ChatSession
155
+ facts = _PrintRunFacts(session_id=str(getattr(session, "session_id", "") or "") or None)
156
+ async for event in session.query_stream(message):
157
+ if isinstance(event, SessionInitEvent):
158
+ facts.session_id = event.session_id
159
+ continue
160
+ if isinstance(event, TextEvent):
161
+ facts.final_text = event.content
162
+ facts.structured_output = event.structured_output
163
+ continue
164
+ if isinstance(event, StopEvent):
165
+ facts.stop_reason = event.reason
166
+ error = event.metadata.get("error") if event.metadata else None
167
+ if error is not None and facts.stop_reason == "interrupted":
168
+ if isinstance(error, BaseException):
169
+ raise RuntimeError(str(error)) from error
170
+ raise RuntimeError(str(error))
171
+ return facts
172
+
173
+
174
+ async def run_print_mode(
175
+ agent: object,
176
+ message: str,
177
+ *,
178
+ project_root: Path,
179
+ output_format: str = TEXT_OUTPUT_FORMAT,
180
+ preload_mcp: Callable[[object], Awaitable[None]],
181
+ graceful_shutdown: Callable[[object], Awaitable[None]],
182
+ ) -> None:
183
+ ChatSession, _, _, _ = _import_print_mode_components()
184
+ session: object | None = None
185
+ started = time.perf_counter()
186
+ facts = _PrintRunFacts()
187
+ try:
188
+ session = ChatSession(agent, cwd=project_root, persistent=False)
189
+ facts.session_id = str(getattr(session, "session_id", "") or "") or None
190
+ await preload_mcp(session)
191
+ facts = await _consume_query_stream(session, message)
192
+ usage = await _get_usage_or_empty(session)
193
+ duration_ms = int((time.perf_counter() - started) * 1000)
194
+ if output_format == JSON_OUTPUT_FORMAT:
195
+ payload = _build_result_payload(
196
+ facts=facts,
197
+ usage=usage,
198
+ duration_ms=duration_ms,
199
+ )
200
+ _write_json_result(payload)
201
+ if payload["is_error"]:
202
+ raise SystemExit(1)
203
+ return
204
+ _write_text_result(facts.final_text)
205
+ except SystemExit:
206
+ raise
207
+ except Exception as exc:
208
+ logger.error("Print mode failed: %s", exc, exc_info=True)
209
+ duration_ms = int((time.perf_counter() - started) * 1000)
210
+ usage = await _get_usage_or_empty(session)
211
+ if output_format == JSON_OUTPUT_FORMAT:
212
+ payload = _build_result_payload(
213
+ facts=facts,
214
+ usage=usage,
215
+ duration_ms=duration_ms,
216
+ errors=[str(exc)],
217
+ )
218
+ _write_json_result(payload)
219
+ else:
220
+ sys.stderr.write(f"Error: {exc}\n")
221
+ raise SystemExit(1) from exc
222
+ finally:
223
+ if session is not None:
224
+ await graceful_shutdown(session)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "comate-cli"
7
- version = "0.7.8 "
7
+ version = "0.7.9"
8
8
  description = "Comate terminal CLI built on comate-agent-sdk"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11,<3.14"