comate-cli 0.7.2__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.
- {comate_cli-0.7.2 → comate_cli-0.7.3}/PKG-INFO +13 -1
- {comate_cli-0.7.2 → comate_cli-0.7.3}/README.md +12 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/mcp_cli.py +291 -40
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/app.py +109 -12
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/config/model.py +6 -2
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/config/picker.py +11 -2
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/config/picker_state.py +5 -1
- comate_cli-0.7.3/comate_cli/terminal_agent/config/store.py +387 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/event_renderer.py +6 -3
- comate_cli-0.7.3/comate_cli/terminal_agent/goal_resume_view.py +143 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/history_printer.py +3 -5
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/models.py +1 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/preflight.py +51 -2
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/slash_commands.py +6 -0
- comate_cli-0.7.3/comate_cli/terminal_agent/startup_profile.py +67 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/status_bar.py +107 -29
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/statusline/model.py +5 -1
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/statusline/picker_state.py +1 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/statusline/store.py +30 -5
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/tool_view.py +3 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui.py +147 -7
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui_parts/commands.py +213 -3
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui_parts/input_behavior.py +3 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui_parts/key_bindings.py +38 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui_parts/ui_mode.py +1 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/update_check.py +36 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/pyproject.toml +1 -1
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/config/test_model.py +16 -3
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/config/test_picker_state.py +17 -3
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/config/test_picker_ui.py +10 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/config/test_roundtrip.py +5 -3
- comate_cli-0.7.3/tests/config/test_store_load.py +377 -0
- comate_cli-0.7.3/tests/config/test_store_save.py +216 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/statusline/test_model.py +8 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/statusline/test_picker_state.py +10 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/statusline/test_store.py +39 -8
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_app_shutdown.py +1 -1
- comate_cli-0.7.3/tests/test_app_startup_latency.py +271 -0
- comate_cli-0.7.3/tests/test_app_token_cost_config.py +77 -0
- comate_cli-0.7.3/tests/test_goal_resume_tui.py +190 -0
- comate_cli-0.7.3/tests/test_goal_resume_view.py +92 -0
- comate_cli-0.7.3/tests/test_goal_slash_command.py +209 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_interrupt_exit_semantics.py +79 -1
- comate_cli-0.7.3/tests/test_mcp_cli.py +859 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_mcp_slash_command.py +110 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_preflight.py +207 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_preflight_copilot.py +9 -2
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_slash_completer.py +9 -4
- comate_cli-0.7.3/tests/test_startup_profile.py +216 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_status_bar.py +173 -3
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_tool_view.py +10 -0
- comate_cli-0.7.3/tests/test_tui_startup_latency.py +104 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_update_check.py +193 -55
- comate_cli-0.7.3/tests/test_usage_command.py +81 -0
- comate_cli-0.7.2/AGENTS.md +0 -115
- comate_cli-0.7.2/comate_cli/terminal_agent/config/store.py +0 -173
- comate_cli-0.7.2/docs/hooks.md +0 -1047
- comate_cli-0.7.2/docs/superpowers/plans/2026-04-03-phrase-shuffle.md +0 -187
- comate_cli-0.7.2/docs/superpowers/plans/2026-05-22-log-style-implementation-plan.md +0 -444
- comate_cli-0.7.2/docs/superpowers/specs/2026-04-01-conditional-diff-subtitle-design.md +0 -67
- comate_cli-0.7.2/docs/superpowers/specs/2026-04-03-phrase-shuffle-design.md +0 -55
- comate_cli-0.7.2/docs/superpowers/specs/2026-05-22-comate-cli-log-styling-design.md +0 -71
- comate_cli-0.7.2/docs/superpowers/specs/2026-05-22-log-style-optimization-design.md +0 -114
- comate_cli-0.7.2/report.md +0 -382
- comate_cli-0.7.2/tests/config/test_store_load.py +0 -112
- comate_cli-0.7.2/tests/config/test_store_save.py +0 -103
- comate_cli-0.7.2/tests/test_mcp_cli.py +0 -269
- {comate_cli-0.7.2 → comate_cli-0.7.3}/.gitignore +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/CHANGELOG.md +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/__init__.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/__main__.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/main.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/__init__.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/animations.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/assistant_render.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/codenames.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/config/__init__.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/env_utils.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/error_display.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/figures.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/fragment_utils.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/input_geometry.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/logging_adapter.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/logo.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/markdown_render.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/mention_completer.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/message_style.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/path_context_hint.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/question_view.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/resume_picker.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/resume_preview.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/resume_selector.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/selection_menu.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/startup.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/statusline/__init__.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/statusline/picker.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/text_effects.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/tips.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/tool_fold.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/tool_result_formatters.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/tool_result_store.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/transcript_viewer.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui_parts/history_sync.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui_parts/render_panels.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/config/__init__.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/conftest.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/statusline/__init__.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_animator_shuffle.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_app_mcp_preload.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_app_preflight_gate.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_app_print_mode.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_app_usage_line.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_btw_slash_command.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_cli_project_root.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_compact_command_semantics.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_completion_context_activation.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_completion_status_panel.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_context_command.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_custom_slash_commands.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_discover_tab.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_errors_tab.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_event_renderer.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_event_renderer_boundary.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_event_renderer_e2e.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_event_renderer_log_boundary.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_event_renderer_log_queue.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_event_renderer_streaming.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_event_renderer_tool_fold.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_format_error.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_handle_error.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_history_printer.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_history_printer_log.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_history_printer_subtitle_position.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_history_printer_tool_fold.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_history_sync.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_history_sync_tool_fold.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_input_behavior.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_input_history.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_installed_tab.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_layout_coordinator.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_logging_adapter.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_logo.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_main_args.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_markdown_render.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_marketplaces_tab.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_mention_completer.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_path_context_hint.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_plugin_slash_commands.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_plugin_tui_components.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_question_key_bindings.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_question_view.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_resume_picker.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_resume_preview.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_resume_selector.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_rewind_command_semantics.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_rpc_protocol.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_rpc_stdio_bridge.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_selection_menu.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_session_query_token_summary.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_shutdown_noise_guard.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_shutdown_noise_integration.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_skills_slash_command.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_slash_argument_hint.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_slash_clear.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_slash_registry.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_status_bar_transient.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_task_panel_format.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_task_panel_key_bindings.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_task_panel_rendering.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_task_poll.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_tool_fold.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_tool_fold_panel.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_tool_result_formatters.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_tool_result_store.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_tool_result_viewer.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_tool_result_viewer_key_bindings.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_transcript_viewer.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_transcript_viewer_tool_fold.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_tui_elapsed_status.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_tui_esc_queue.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_tui_mcp_init_gate.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_tui_paste_newline_guard.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_tui_paste_placeholder.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_tui_queue_preview.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_tui_queue_sdk_source.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_tui_split_invariance.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_tui_team_messages.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_tui_thinking_display.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
- {comate_cli-0.7.2 → comate_cli-0.7.3}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: comate-cli
|
|
3
|
-
Version: 0.7.
|
|
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="
|
|
62
|
-
help="Config scope (default:
|
|
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", "
|
|
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=
|
|
108
|
-
help="Delete from scope
|
|
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
|
-
|
|
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
|
|
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("
|
|
319
|
+
raise McpCliError(f"{transport.upper()} transport requires <commandOrUrl> as URL")
|
|
176
320
|
if remainder:
|
|
177
|
-
raise McpCliError("
|
|
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":
|
|
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
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
265
|
-
|
|
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
|
-
|
|
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
|
-
|
|
397
|
-
|
|
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,
|