comate-cli 0.7.1__tar.gz → 0.7.3__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 (214) hide show
  1. {comate_cli-0.7.1 → comate_cli-0.7.3}/PKG-INFO +13 -1
  2. {comate_cli-0.7.1 → comate_cli-0.7.3}/README.md +12 -0
  3. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/mcp_cli.py +291 -40
  4. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/app.py +136 -67
  5. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/config/model.py +6 -2
  6. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/config/picker.py +11 -2
  7. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/config/picker_state.py +5 -1
  8. comate_cli-0.7.3/comate_cli/terminal_agent/config/store.py +387 -0
  9. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/event_renderer.py +6 -3
  10. comate_cli-0.7.3/comate_cli/terminal_agent/goal_resume_view.py +143 -0
  11. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/history_printer.py +3 -5
  12. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/models.py +1 -0
  13. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/preflight.py +51 -2
  14. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/slash_commands.py +6 -0
  15. comate_cli-0.7.3/comate_cli/terminal_agent/startup_profile.py +67 -0
  16. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/status_bar.py +107 -29
  17. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/statusline/model.py +5 -1
  18. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/statusline/picker_state.py +1 -0
  19. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/statusline/store.py +30 -5
  20. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/tool_view.py +3 -0
  21. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui.py +147 -7
  22. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui_parts/commands.py +213 -3
  23. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui_parts/input_behavior.py +3 -0
  24. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui_parts/key_bindings.py +38 -0
  25. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui_parts/ui_mode.py +1 -0
  26. comate_cli-0.7.3/comate_cli/terminal_agent/update_check.py +370 -0
  27. {comate_cli-0.7.1 → comate_cli-0.7.3}/pyproject.toml +7 -1
  28. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/config/test_model.py +16 -3
  29. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/config/test_picker_state.py +17 -3
  30. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/config/test_picker_ui.py +10 -0
  31. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/config/test_roundtrip.py +5 -3
  32. comate_cli-0.7.3/tests/config/test_store_load.py +377 -0
  33. comate_cli-0.7.3/tests/config/test_store_save.py +216 -0
  34. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/statusline/test_model.py +8 -0
  35. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/statusline/test_picker_state.py +10 -0
  36. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/statusline/test_store.py +39 -8
  37. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_app_shutdown.py +1 -1
  38. comate_cli-0.7.3/tests/test_app_startup_latency.py +271 -0
  39. comate_cli-0.7.3/tests/test_app_token_cost_config.py +77 -0
  40. comate_cli-0.7.3/tests/test_goal_resume_tui.py +190 -0
  41. comate_cli-0.7.3/tests/test_goal_resume_view.py +92 -0
  42. comate_cli-0.7.3/tests/test_goal_slash_command.py +209 -0
  43. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_interrupt_exit_semantics.py +79 -1
  44. comate_cli-0.7.3/tests/test_mcp_cli.py +859 -0
  45. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_mcp_slash_command.py +110 -0
  46. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_preflight.py +207 -0
  47. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_preflight_copilot.py +9 -2
  48. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_slash_completer.py +9 -4
  49. comate_cli-0.7.3/tests/test_startup_profile.py +216 -0
  50. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_status_bar.py +173 -3
  51. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_tool_view.py +10 -0
  52. comate_cli-0.7.3/tests/test_tui_startup_latency.py +104 -0
  53. comate_cli-0.7.3/tests/test_update_check.py +509 -0
  54. comate_cli-0.7.3/tests/test_usage_command.py +81 -0
  55. {comate_cli-0.7.1 → comate_cli-0.7.3}/uv.lock +1457 -1441
  56. comate_cli-0.7.1/AGENTS.md +0 -115
  57. comate_cli-0.7.1/comate_cli/terminal_agent/config/store.py +0 -173
  58. comate_cli-0.7.1/docs/hooks.md +0 -1047
  59. comate_cli-0.7.1/docs/superpowers/plans/2026-04-03-phrase-shuffle.md +0 -187
  60. comate_cli-0.7.1/docs/superpowers/plans/2026-05-22-log-style-implementation-plan.md +0 -444
  61. comate_cli-0.7.1/docs/superpowers/specs/2026-04-01-conditional-diff-subtitle-design.md +0 -67
  62. comate_cli-0.7.1/docs/superpowers/specs/2026-04-03-phrase-shuffle-design.md +0 -55
  63. comate_cli-0.7.1/docs/superpowers/specs/2026-05-22-comate-cli-log-styling-design.md +0 -71
  64. comate_cli-0.7.1/docs/superpowers/specs/2026-05-22-log-style-optimization-design.md +0 -114
  65. comate_cli-0.7.1/report.md +0 -382
  66. comate_cli-0.7.1/tests/config/test_store_load.py +0 -112
  67. comate_cli-0.7.1/tests/config/test_store_save.py +0 -103
  68. comate_cli-0.7.1/tests/test_mcp_cli.py +0 -269
  69. comate_cli-0.7.1/tests/test_update_check.py +0 -201
  70. {comate_cli-0.7.1 → comate_cli-0.7.3}/.gitignore +0 -0
  71. {comate_cli-0.7.1 → comate_cli-0.7.3}/CHANGELOG.md +0 -0
  72. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/__init__.py +0 -0
  73. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/__main__.py +0 -0
  74. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/main.py +0 -0
  75. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/__init__.py +0 -0
  76. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/animations.py +0 -0
  77. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/assistant_render.py +0 -0
  78. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/codenames.py +0 -0
  79. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/config/__init__.py +0 -0
  80. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
  81. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/env_utils.py +0 -0
  82. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/error_display.py +0 -0
  83. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/figures.py +0 -0
  84. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/fragment_utils.py +0 -0
  85. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/input_geometry.py +0 -0
  86. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
  87. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/logging_adapter.py +0 -0
  88. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/logo.py +0 -0
  89. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/markdown_render.py +0 -0
  90. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/mention_completer.py +0 -0
  91. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/message_style.py +0 -0
  92. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/path_context_hint.py +0 -0
  93. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
  94. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
  95. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
  96. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
  97. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
  98. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
  99. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
  100. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
  101. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
  102. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
  103. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
  104. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
  105. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
  106. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/question_view.py +0 -0
  107. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/resume_picker.py +0 -0
  108. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/resume_preview.py +0 -0
  109. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/resume_selector.py +0 -0
  110. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
  111. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
  112. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/selection_menu.py +0 -0
  113. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/startup.py +0 -0
  114. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/statusline/__init__.py +0 -0
  115. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/statusline/picker.py +0 -0
  116. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/text_effects.py +0 -0
  117. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/tips.py +0 -0
  118. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/tool_fold.py +0 -0
  119. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/tool_result_formatters.py +0 -0
  120. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/tool_result_store.py +0 -0
  121. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
  122. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/transcript_viewer.py +0 -0
  123. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
  124. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
  125. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui_parts/history_sync.py +0 -0
  126. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
  127. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui_parts/render_panels.py +0 -0
  128. {comate_cli-0.7.1 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
  129. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/config/__init__.py +0 -0
  130. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/conftest.py +0 -0
  131. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
  132. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/statusline/__init__.py +0 -0
  133. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_animator_shuffle.py +0 -0
  134. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_app_mcp_preload.py +0 -0
  135. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_app_preflight_gate.py +0 -0
  136. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_app_print_mode.py +0 -0
  137. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_app_usage_line.py +0 -0
  138. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_btw_slash_command.py +0 -0
  139. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_cli_project_root.py +0 -0
  140. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_compact_command_semantics.py +0 -0
  141. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_completion_context_activation.py +0 -0
  142. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_completion_status_panel.py +0 -0
  143. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_context_command.py +0 -0
  144. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_custom_slash_commands.py +0 -0
  145. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_discover_tab.py +0 -0
  146. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_errors_tab.py +0 -0
  147. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_event_renderer.py +0 -0
  148. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_event_renderer_boundary.py +0 -0
  149. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_event_renderer_e2e.py +0 -0
  150. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_event_renderer_log_boundary.py +0 -0
  151. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_event_renderer_log_queue.py +0 -0
  152. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_event_renderer_streaming.py +0 -0
  153. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_event_renderer_tool_fold.py +0 -0
  154. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_format_error.py +0 -0
  155. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_handle_error.py +0 -0
  156. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_history_printer.py +0 -0
  157. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_history_printer_log.py +0 -0
  158. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_history_printer_subtitle_position.py +0 -0
  159. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_history_printer_tool_fold.py +0 -0
  160. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_history_sync.py +0 -0
  161. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_history_sync_tool_fold.py +0 -0
  162. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_input_behavior.py +0 -0
  163. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_input_history.py +0 -0
  164. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_installed_tab.py +0 -0
  165. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_layout_coordinator.py +0 -0
  166. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_logging_adapter.py +0 -0
  167. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_logo.py +0 -0
  168. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_main_args.py +0 -0
  169. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_markdown_render.py +0 -0
  170. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_marketplaces_tab.py +0 -0
  171. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_mention_completer.py +0 -0
  172. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_path_context_hint.py +0 -0
  173. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_plugin_slash_commands.py +0 -0
  174. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_plugin_tui_components.py +0 -0
  175. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_question_key_bindings.py +0 -0
  176. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_question_view.py +0 -0
  177. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_resume_picker.py +0 -0
  178. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_resume_preview.py +0 -0
  179. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_resume_selector.py +0 -0
  180. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_rewind_command_semantics.py +0 -0
  181. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_rpc_protocol.py +0 -0
  182. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_rpc_stdio_bridge.py +0 -0
  183. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_selection_menu.py +0 -0
  184. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_session_query_token_summary.py +0 -0
  185. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_shutdown_noise_guard.py +0 -0
  186. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_shutdown_noise_integration.py +0 -0
  187. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_skills_slash_command.py +0 -0
  188. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_slash_argument_hint.py +0 -0
  189. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_slash_clear.py +0 -0
  190. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_slash_registry.py +0 -0
  191. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_status_bar_transient.py +0 -0
  192. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_task_panel_format.py +0 -0
  193. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_task_panel_key_bindings.py +0 -0
  194. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_task_panel_rendering.py +0 -0
  195. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_task_poll.py +0 -0
  196. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_tool_fold.py +0 -0
  197. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_tool_fold_panel.py +0 -0
  198. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_tool_result_formatters.py +0 -0
  199. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_tool_result_store.py +0 -0
  200. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_tool_result_viewer.py +0 -0
  201. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_tool_result_viewer_key_bindings.py +0 -0
  202. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_transcript_viewer.py +0 -0
  203. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_transcript_viewer_tool_fold.py +0 -0
  204. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_tui_elapsed_status.py +0 -0
  205. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_tui_esc_queue.py +0 -0
  206. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_tui_mcp_init_gate.py +0 -0
  207. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_tui_paste_newline_guard.py +0 -0
  208. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_tui_paste_placeholder.py +0 -0
  209. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_tui_queue_preview.py +0 -0
  210. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_tui_queue_sdk_source.py +0 -0
  211. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_tui_split_invariance.py +0 -0
  212. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_tui_team_messages.py +0 -0
  213. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_tui_thinking_display.py +0 -0
  214. {comate_cli-0.7.1 → comate_cli-0.7.3}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: comate-cli
3
- Version: 0.7.1
3
+ Version: 0.7.3
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,18 @@ uv tool install comate-cli
42
42
  comate
43
43
  ```
44
44
 
45
+ ## MCP
46
+
47
+ `comate mcp` supports Claude Code compatible MCP configuration commands for common add/list/get/remove flows.
48
+
49
+ ```bash
50
+ comate mcp add firecrawl npx -y firecrawl-mcp
51
+ comate mcp add -t http sentry https://mcp.sentry.dev/mcp -H "Authorization: Bearer ..."
52
+ comate mcp add-json ctx '{"type":"http","url":"https://example.test/mcp"}'
53
+ ```
54
+
55
+ Supported scopes are `local` (default), `project`, and `user`. Comate stores MCP configuration under `.agent` / `~/.agent`; it does not read or modify Claude Code configuration files.
56
+
45
57
  ## 排错 / Troubleshooting
46
58
 
47
59
  ### 看不到错误信息但行为异常
@@ -15,6 +15,18 @@ uv tool install comate-cli
15
15
  comate
16
16
  ```
17
17
 
18
+ ## MCP
19
+
20
+ `comate mcp` supports Claude Code compatible MCP configuration commands for common add/list/get/remove flows.
21
+
22
+ ```bash
23
+ comate mcp add firecrawl npx -y firecrawl-mcp
24
+ comate mcp add -t http sentry https://mcp.sentry.dev/mcp -H "Authorization: Bearer ..."
25
+ comate mcp add-json ctx '{"type":"http","url":"https://example.test/mcp"}'
26
+ ```
27
+
28
+ Supported scopes are `local` (default), `project`, and `user`. Comate stores MCP configuration under `.agent` / `~/.agent`; it does not read or modify Claude Code configuration files.
29
+
18
30
  ## 排错 / Troubleshooting
19
31
 
20
32
  ### 看不到错误信息但行为异常
@@ -2,15 +2,19 @@ from __future__ import annotations
2
2
 
3
3
  import argparse
4
4
  import asyncio
5
+ import json
5
6
  import sys
7
+ from pathlib import Path
6
8
  from typing import Any
7
9
 
8
10
  from comate_agent_sdk.mcp import (
9
11
  McpConfigError,
10
12
  collect_mcp_server_health,
11
13
  load_effective_servers,
14
+ load_effective_servers_with_sources,
12
15
  load_scope_servers,
13
16
  scope_mcp_path,
17
+ validate_server_config,
14
18
  write_mcp_servers_to_path,
15
19
  )
16
20
  from comate_agent_sdk.mcp.types import McpServerConfig
@@ -22,6 +26,8 @@ from comate_cli.terminal_agent.figures import (
22
26
  ELLIPSIS,
23
27
  )
24
28
 
29
+ _WRITABLE_SCOPES: tuple[str, ...] = ("user", "project", "local")
30
+
25
31
 
26
32
  class McpCliError(ValueError):
27
33
  def __init__(self, message: str, *, exit_code: int = 2) -> None:
@@ -56,21 +62,24 @@ def _build_parser() -> argparse.ArgumentParser:
56
62
 
57
63
  add_parser = subparsers.add_parser("add", help="Add an MCP server to comate")
58
64
  add_parser.add_argument(
65
+ "-s",
59
66
  "--scope",
60
- choices=("user", "project"),
61
- default="user",
62
- help="Config scope (default: user)",
67
+ choices=("local", "user", "project"),
68
+ default="local",
69
+ help="Config scope (default: local)",
63
70
  )
64
71
  add_parser.add_argument(
72
+ "-t",
65
73
  "--transport",
66
- choices=("http", "stdio"),
74
+ choices=("stdio", "http", "sse"),
67
75
  help="Server transport (default: stdio)",
68
76
  )
69
77
  add_parser.add_argument(
78
+ "-H",
70
79
  "--header",
71
80
  action="append",
72
81
  default=[],
73
- help='HTTP header in format "Key: Value" (can repeat)',
82
+ help='HTTP/SSE header in format "Key: Value" (can repeat)',
74
83
  )
75
84
  add_parser.add_argument(
76
85
  "-e",
@@ -79,14 +88,43 @@ def _build_parser() -> argparse.ArgumentParser:
79
88
  default=[],
80
89
  help='Environment variable in format "KEY=VALUE" (can repeat)',
81
90
  )
91
+ add_parser.add_argument(
92
+ "--force",
93
+ action="store_true",
94
+ default=False,
95
+ help="Overwrite if server already exists in the same scope",
96
+ )
97
+ add_parser.add_argument("--client-id", dest="client_id")
98
+ add_parser.add_argument("--client-secret", action="store_true", default=False)
99
+ add_parser.add_argument("--callback-port")
82
100
  add_parser.add_argument("name")
83
101
  add_parser.add_argument("command_or_url", nargs="?")
84
102
 
103
+ add_json_parser = subparsers.add_parser("add-json", help="Add an MCP server from a JSON config")
104
+ add_json_parser.add_argument(
105
+ "-s",
106
+ "--scope",
107
+ choices=("local", "user", "project"),
108
+ default="local",
109
+ help="Config scope (default: local)",
110
+ )
111
+ add_json_parser.add_argument(
112
+ "--force",
113
+ action="store_true",
114
+ default=False,
115
+ help="Overwrite if server already exists in the same scope",
116
+ )
117
+ add_json_parser.add_argument("--client-id", dest="client_id")
118
+ add_json_parser.add_argument("--client-secret", action="store_true", default=False)
119
+ add_json_parser.add_argument("--callback-port")
120
+ add_json_parser.add_argument("name")
121
+ add_json_parser.add_argument("json")
122
+
85
123
  get_parser = subparsers.add_parser("get", help="Get details about an MCP server")
86
124
  get_parser.add_argument("name")
87
125
  get_parser.add_argument(
88
126
  "--scope",
89
- choices=("user", "project", "effective"),
127
+ choices=("local", "user", "project", "effective"),
90
128
  default="effective",
91
129
  help="Read from specific scope or merged effective view (default: effective)",
92
130
  )
@@ -94,7 +132,7 @@ def _build_parser() -> argparse.ArgumentParser:
94
132
  list_parser = subparsers.add_parser("list", help="List configured MCP servers")
95
133
  list_parser.add_argument(
96
134
  "--scope",
97
- choices=("user", "project", "effective"),
135
+ choices=("local", "user", "project", "effective"),
98
136
  default="effective",
99
137
  help="List specific scope or merged effective view (default: effective)",
100
138
  )
@@ -103,24 +141,24 @@ def _build_parser() -> argparse.ArgumentParser:
103
141
  remove_parser.add_argument("name")
104
142
  remove_parser.add_argument(
105
143
  "--scope",
106
- choices=("user", "project"),
107
- default="user",
108
- help="Delete from scope (default: user)",
144
+ choices=("local", "user", "project"),
145
+ default=None,
146
+ help="Delete from scope; omitted means auto-detect",
109
147
  )
110
148
 
111
149
  return parser
112
150
 
113
151
 
114
152
  def _scope_label(scope: str) -> str:
153
+ if scope == "local":
154
+ return "Local config (private to you in this project)"
115
155
  if scope == "project":
116
156
  return "Project config (private to this project)"
117
157
  return "User config (~/.agent/.mcp.json)"
118
158
 
119
159
 
120
160
  def _effective_source_label(source: str) -> str:
121
- if source == "project":
122
- return "Project config (private to this project)"
123
- return "User config (~/.agent/.mcp.json)"
161
+ return _scope_label(source)
124
162
 
125
163
 
126
164
  def _parse_header_entries(entries: list[str]) -> dict[str, str]:
@@ -161,6 +199,102 @@ def _split_trailing_command_args(argv: list[str]) -> tuple[list[str], list[str]]
161
199
  return list(argv[:separator_index]), list(argv[separator_index + 1 :])
162
200
 
163
201
 
202
+ def _normalize_add_unknown_args(unknown: list[str]) -> list[str]:
203
+ if unknown and unknown[0] == "--":
204
+ return unknown[1:]
205
+ return list(unknown)
206
+
207
+
208
+ def _split_add_args(argv: list[str]) -> tuple[list[str], list[str]]:
209
+ if not argv or argv[0] != "add":
210
+ return list(argv), []
211
+
212
+ tokens = list(argv[1:])
213
+ if "--" in tokens:
214
+ separator_index = tokens.index("--")
215
+ before_separator = tokens[:separator_index]
216
+ after_separator = tokens[separator_index + 1 :]
217
+ else:
218
+ before_separator = tokens
219
+ after_separator = []
220
+
221
+ option_args: list[str] = []
222
+ positional_args: list[str] = []
223
+ extra_args: list[str] = []
224
+ positional_count = 0
225
+ value_options = {
226
+ "-s",
227
+ "--scope",
228
+ "-t",
229
+ "--transport",
230
+ "-H",
231
+ "--header",
232
+ "-e",
233
+ "--env",
234
+ "--client-id",
235
+ "--callback-port",
236
+ }
237
+ flag_options = {"--force", "--client-secret"}
238
+ remote_tail_value_options = {"-H", "--header", "--client-id", "--callback-port"}
239
+ remote_tail_flag_options = {"--client-secret"}
240
+ transport = "stdio"
241
+
242
+ index = 0
243
+ while index < len(before_separator):
244
+ token = before_separator[index]
245
+ if positional_count >= 2:
246
+ if transport in {"http", "sse"} and token in remote_tail_value_options:
247
+ option_args.append(token)
248
+ if index + 1 < len(before_separator):
249
+ option_args.append(before_separator[index + 1])
250
+ index += 2
251
+ continue
252
+ if transport in {"http", "sse"} and token in remote_tail_flag_options:
253
+ option_args.append(token)
254
+ index += 1
255
+ continue
256
+ if transport in {"http", "sse"} and any(
257
+ token.startswith(f"{option}=")
258
+ for option in remote_tail_value_options
259
+ if option.startswith("--")
260
+ ):
261
+ option_args.append(token)
262
+ index += 1
263
+ continue
264
+ extra_args.extend(before_separator[index:])
265
+ break
266
+
267
+ if positional_count < 2:
268
+ if token in value_options:
269
+ option_args.append(token)
270
+ if index + 1 < len(before_separator):
271
+ option_args.append(before_separator[index + 1])
272
+ if token in {"-t", "--transport"}:
273
+ transport = before_separator[index + 1].strip().lower()
274
+ index += 2
275
+ continue
276
+ if any(token.startswith(f"{option}=") for option in value_options if option.startswith("--")):
277
+ option_args.append(token)
278
+ if token.startswith("--transport="):
279
+ transport = token.split("=", 1)[1].strip().lower()
280
+ index += 1
281
+ continue
282
+ if token in flag_options:
283
+ option_args.append(token)
284
+ index += 1
285
+ continue
286
+ if token.startswith("-"):
287
+ option_args.extend(before_separator[index:])
288
+ return ["add", *option_args, *positional_args], [*extra_args, *after_separator]
289
+
290
+ positional_args.append(token)
291
+ positional_count += 1
292
+ index += 1
293
+
294
+ extra_args.extend(after_separator)
295
+ return ["add", *option_args, *positional_args], extra_args
296
+
297
+
164
298
  def _build_add_server_config(args: argparse.Namespace) -> McpServerConfig:
165
299
  transport = str(args.transport or "stdio").strip().lower()
166
300
  header_entries = [str(item) for item in list(args.header or [])]
@@ -168,16 +302,26 @@ def _build_add_server_config(args: argparse.Namespace) -> McpServerConfig:
168
302
  command_or_url = str(args.command_or_url or "").strip()
169
303
  remainder = [str(item) for item in list(getattr(args, "extra_args", []) or [])]
170
304
 
171
- if transport == "http":
305
+ if (
306
+ getattr(args, "client_id", None)
307
+ or bool(getattr(args, "client_secret", False))
308
+ or getattr(args, "callback_port", None)
309
+ ):
310
+ raise McpCliError(
311
+ "OAuth flags are not supported by comate mcp add yet. "
312
+ "Use headers for token-based HTTP/SSE MCP servers, or add a Comate-native oauth object via .mcp.json."
313
+ )
314
+
315
+ if transport in {"http", "sse"}:
172
316
  if env_entries:
173
317
  raise McpCliError("--env is only supported for stdio transport")
174
318
  if not command_or_url:
175
- raise McpCliError("HTTP transport requires <commandOrUrl> as URL")
319
+ raise McpCliError(f"{transport.upper()} transport requires <commandOrUrl> as URL")
176
320
  if remainder:
177
- raise McpCliError("HTTP transport does not accept trailing command args")
321
+ raise McpCliError(f"{transport.upper()} transport does not accept trailing command args")
178
322
  headers = _parse_header_entries(header_entries)
179
323
  cfg: dict[str, Any] = {
180
- "type": "http",
324
+ "type": transport,
181
325
  "url": command_or_url,
182
326
  }
183
327
  if headers:
@@ -185,7 +329,7 @@ def _build_add_server_config(args: argparse.Namespace) -> McpServerConfig:
185
329
  return cfg # type: ignore[return-value]
186
330
 
187
331
  if header_entries:
188
- raise McpCliError("--header is only supported for http transport")
332
+ raise McpCliError("--header is only supported for http/sse transport")
189
333
  stdio_args = list(remainder)
190
334
  if not command_or_url and stdio_args:
191
335
  command_or_url = stdio_args.pop(0)
@@ -194,6 +338,7 @@ def _build_add_server_config(args: argparse.Namespace) -> McpServerConfig:
194
338
 
195
339
  env = _parse_env_entries(env_entries)
196
340
  cfg = {
341
+ "type": "stdio",
197
342
  "command": command_or_url,
198
343
  }
199
344
  if stdio_args:
@@ -203,6 +348,14 @@ def _build_add_server_config(args: argparse.Namespace) -> McpServerConfig:
203
348
  return cfg # type: ignore[return-value]
204
349
 
205
350
 
351
+ def _has_oauth_flags(args: argparse.Namespace) -> bool:
352
+ return bool(
353
+ getattr(args, "client_id", None)
354
+ or bool(getattr(args, "client_secret", False))
355
+ or getattr(args, "callback_port", None)
356
+ )
357
+
358
+
206
359
  def _load_servers_by_scope(*, scope: str, project_root: PathInput | None) -> dict[str, McpServerConfig]:
207
360
  if scope == "effective":
208
361
  return load_effective_servers(project_root=project_root)
@@ -214,13 +367,7 @@ def _read_effective_server_with_source(
214
367
  name: str,
215
368
  project_root: PathInput | None,
216
369
  ) -> tuple[McpServerConfig, str] | None:
217
- user_servers = load_scope_servers(scope="user", project_root=project_root)
218
- project_servers = load_scope_servers(scope="project", project_root=project_root)
219
- if name in project_servers:
220
- return project_servers[name], "project"
221
- if name in user_servers:
222
- return user_servers[name], "user"
223
- return None
370
+ return load_effective_servers_with_sources(project_root=project_root).get(name)
224
371
 
225
372
 
226
373
  def _render_health_status(
@@ -250,6 +397,49 @@ def _format_server_endpoint(cfg: McpServerConfig) -> str:
250
397
  return " ".join([command, *str_args]).strip()
251
398
 
252
399
 
400
+ def _write_server_to_scope(
401
+ *,
402
+ name: str,
403
+ config: McpServerConfig,
404
+ scope: str,
405
+ project_root: PathInput | None,
406
+ force: bool = False,
407
+ ) -> tuple[Path, bool]:
408
+ servers = load_scope_servers(
409
+ scope=scope,
410
+ project_root=project_root,
411
+ include_disabled=True,
412
+ ) # type: ignore[arg-type]
413
+ existed = name in servers
414
+ if existed and not force:
415
+ raise McpCliError(
416
+ f"MCP server '{name}' already exists in {scope} scope. "
417
+ f"Use --force to overwrite.",
418
+ exit_code=1,
419
+ )
420
+ servers[name] = config
421
+ path = scope_mcp_path(scope=scope, project_root=project_root) # type: ignore[arg-type]
422
+ write_mcp_servers_to_path(path=path, servers=servers)
423
+ return path, existed
424
+
425
+
426
+ def _find_scopes_containing_server(
427
+ *,
428
+ name: str,
429
+ project_root: PathInput | None,
430
+ ) -> list[str]:
431
+ found: list[str] = []
432
+ for scope in _WRITABLE_SCOPES:
433
+ servers = load_scope_servers(
434
+ scope=scope,
435
+ project_root=project_root,
436
+ include_disabled=True,
437
+ ) # type: ignore[arg-type]
438
+ if name in servers:
439
+ found.append(scope)
440
+ return found
441
+
442
+
253
443
  def _cmd_add(args: argparse.Namespace, *, project_root: PathInput | None) -> None:
254
444
  scope = str(args.scope)
255
445
  name = str(args.name).strip()
@@ -257,12 +447,48 @@ def _cmd_add(args: argparse.Namespace, *, project_root: PathInput | None) -> Non
257
447
  raise McpCliError("Server name cannot be empty")
258
448
 
259
449
  config = _build_add_server_config(args)
260
- servers = load_scope_servers(scope=scope, project_root=project_root) # type: ignore[arg-type]
261
- existed = name in servers
262
- servers[name] = config
450
+ path, existed = _write_server_to_scope(
451
+ name=name,
452
+ config=config,
453
+ scope=scope,
454
+ project_root=project_root,
455
+ force=bool(getattr(args, "force", False)),
456
+ )
263
457
 
264
- path = scope_mcp_path(scope=scope, project_root=project_root) # type: ignore[arg-type]
265
- write_mcp_servers_to_path(path=path, servers=servers)
458
+ action = "Updated" if existed else "Added"
459
+ sys.stdout.write(
460
+ f"{action} MCP server '{name}' in {scope} scope: {path.expanduser()}\n"
461
+ )
462
+
463
+
464
+ def _cmd_add_json(args: argparse.Namespace, *, project_root: PathInput | None) -> None:
465
+ if _has_oauth_flags(args):
466
+ raise McpCliError(
467
+ "OAuth flags are not supported by comate mcp add-json yet. "
468
+ "Use headers for token-based HTTP/SSE MCP servers, or add a Comate-native oauth object via .mcp.json."
469
+ )
470
+
471
+ scope = str(args.scope)
472
+ name = str(args.name).strip()
473
+ if not name:
474
+ raise McpCliError("Server name cannot be empty")
475
+
476
+ try:
477
+ raw = json.loads(str(args.json))
478
+ except json.JSONDecodeError as exc:
479
+ raise McpCliError(f"Invalid JSON: {exc}") from exc
480
+ if not isinstance(raw, dict):
481
+ raise McpCliError("MCP server JSON must be an object")
482
+
483
+ target_path = scope_mcp_path(scope=scope, project_root=project_root) # type: ignore[arg-type]
484
+ config = validate_server_config(alias=name, cfg=raw, path=target_path)
485
+ path, existed = _write_server_to_scope(
486
+ name=name,
487
+ config=config,
488
+ scope=scope,
489
+ project_root=project_root,
490
+ force=bool(getattr(args, "force", False)),
491
+ )
266
492
 
267
493
  action = "Updated" if existed else "Added"
268
494
  sys.stdout.write(
@@ -271,12 +497,34 @@ def _cmd_add(args: argparse.Namespace, *, project_root: PathInput | None) -> Non
271
497
 
272
498
 
273
499
  def _cmd_remove(args: argparse.Namespace, *, project_root: PathInput | None) -> None:
274
- scope = str(args.scope)
275
500
  name = str(args.name).strip()
276
501
  if not name:
277
502
  raise McpCliError("Server name cannot be empty")
278
503
 
279
- servers = load_scope_servers(scope=scope, project_root=project_root) # type: ignore[arg-type]
504
+ explicit_scope = getattr(args, "scope", None)
505
+ if explicit_scope is None:
506
+ found = _find_scopes_containing_server(name=name, project_root=project_root)
507
+ if not found:
508
+ raise McpCliError(f"MCP server '{name}' not found.", exit_code=1)
509
+ if len(found) > 1:
510
+ scope_list = ", ".join(found)
511
+ suggestions = "; ".join(
512
+ f"comate mcp remove {name!r} --scope {scope}" for scope in found
513
+ )
514
+ raise McpCliError(
515
+ f"MCP server '{name}' exists in multiple scopes: {scope_list}. "
516
+ f"Specify one: {suggestions}",
517
+ exit_code=1,
518
+ )
519
+ scope = found[0]
520
+ else:
521
+ scope = str(explicit_scope)
522
+
523
+ servers = load_scope_servers(
524
+ scope=scope,
525
+ project_root=project_root,
526
+ include_disabled=True,
527
+ ) # type: ignore[arg-type]
280
528
  if name not in servers:
281
529
  raise McpCliError(
282
530
  f"MCP server '{name}' not found in {scope} scope.",
@@ -334,6 +582,7 @@ def _cmd_get(args: argparse.Namespace, *, project_root: PathInput | None) -> Non
334
582
  raise McpCliError(f"MCP server '{name}' not found.", exit_code=1)
335
583
  cfg, source = resolved
336
584
  scope_line = _effective_source_label(source)
585
+ remove_scope = source
337
586
  else:
338
587
  servers = load_scope_servers(scope=scope, project_root=project_root) # type: ignore[arg-type]
339
588
  cfg = servers.get(name)
@@ -343,6 +592,7 @@ def _cmd_get(args: argparse.Namespace, *, project_root: PathInput | None) -> Non
343
592
  exit_code=1,
344
593
  )
345
594
  scope_line = _scope_label(scope)
595
+ remove_scope = scope
346
596
 
347
597
  health_rows = asyncio.run(collect_mcp_server_health(servers={name: cfg}))
348
598
  status = f"{CROSS_MARK} Not connected"
@@ -380,10 +630,6 @@ def _cmd_get(args: argparse.Namespace, *, project_root: PathInput | None) -> Non
380
630
  for key in sorted(env.keys()):
381
631
  lines.append(f" {key}: {env[key]}")
382
632
 
383
- if scope == "effective":
384
- remove_scope = "project" if "Project config" in scope_line else "user"
385
- else:
386
- remove_scope = scope
387
633
  lines.append("")
388
634
  lines.append(
389
635
  f'To remove this server, run: comate mcp remove "{name}" --scope {remove_scope}'
@@ -393,8 +639,14 @@ def _cmd_get(args: argparse.Namespace, *, project_root: PathInput | None) -> Non
393
639
 
394
640
  def run_mcp_command(argv: list[str], *, project_root: PathInput | None = None) -> None:
395
641
  parser = _build_parser()
396
- parser_argv, trailing_args = _split_trailing_command_args(argv)
397
- parsed = parser.parse_args(parser_argv)
642
+ if argv and argv[0] == "add":
643
+ parser_argv, extra_args = _split_add_args(argv)
644
+ parsed = parser.parse_args(parser_argv)
645
+ setattr(parsed, "extra_args", _normalize_add_unknown_args(extra_args))
646
+ trailing_args: list[str] = []
647
+ else:
648
+ parser_argv, trailing_args = _split_trailing_command_args(argv)
649
+ parsed = parser.parse_args(parser_argv)
398
650
  command = str(getattr(parsed, "command", "") or "").strip()
399
651
  if not command:
400
652
  parser.print_help(sys.stdout)
@@ -402,11 +654,10 @@ def run_mcp_command(argv: list[str], *, project_root: PathInput | None = None) -
402
654
  if command != "add" and trailing_args:
403
655
  joined = " ".join(["--", *trailing_args])
404
656
  raise McpCliError(f"Unrecognized arguments: {joined}")
405
- if command == "add":
406
- setattr(parsed, "extra_args", list(trailing_args))
407
657
 
408
658
  handlers = {
409
659
  "add": _cmd_add,
660
+ "add-json": _cmd_add_json,
410
661
  "list": _cmd_list,
411
662
  "get": _cmd_get,
412
663
  "remove": _cmd_remove,