comate-cli 0.8.6__tar.gz → 0.9.0__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.8.6 → comate_cli-0.9.0}/.gitignore +1 -0
- comate_cli-0.9.0/CHANGELOG.md +137 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/PKG-INFO +1 -1
- comate_cli-0.9.0/comate_cli/distribution.py +83 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/main.py +67 -17
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/mcp_cli.py +82 -24
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/app.py +89 -18
- comate_cli-0.9.0/comate_cli/terminal_agent/builtin_commands/implement-plan.md +39 -0
- comate_cli-0.9.0/comate_cli/terminal_agent/builtin_commands/learn.md +100 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/custom_slash_commands.py +99 -19
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/event_renderer.py +121 -5
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/logo.py +38 -3
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/preflight.py +15 -3
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/question_view.py +2 -2
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/status_bar.py +2 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/tool_result_formatters.py +7 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/tui.py +29 -3
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/tui_parts/history_sync.py +27 -2
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/tui_parts/render_panels.py +71 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/update_check.py +311 -61
- comate_cli-0.9.0/comate_cli/terminal_agent/update_messages.py +242 -0
- comate_cli-0.9.0/comate_cli/terminal_agent/windows_updater.py +254 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/pyproject.toml +1 -1
- comate_cli-0.9.0/tests/test_app_profile_threading.py +244 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_custom_slash_commands.py +74 -0
- comate_cli-0.9.0/tests/test_distribution_contract.py +184 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_event_renderer_boundary.py +14 -10
- comate_cli-0.9.0/tests/test_event_renderer_stream_staging.py +186 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_event_renderer_streaming.py +79 -71
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_event_renderer_tool_fold.py +3 -1
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_history_sync.py +45 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_logo.py +115 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_main_args.py +147 -12
- comate_cli-0.9.0/tests/test_main_profile_entry.py +65 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_mcp_cli.py +232 -10
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_preflight.py +57 -22
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_question_view.py +6 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_skills_slash_command.py +42 -1
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_status_bar.py +1 -0
- comate_cli-0.9.0/tests/test_stream_staging_panel.py +119 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tool_result_formatters.py +19 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_update_check.py +647 -3
- comate_cli-0.9.0/tests/test_windows_updater.py +314 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/uv.lock +2 -2
- comate_cli-0.8.6/CHANGELOG.md +0 -50
- {comate_cli-0.8.6 → comate_cli-0.9.0}/CONTEXT.md +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/README.md +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/__init__.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/__main__.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/__init__.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/animations.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/assistant_render.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/builtin_commands/.keep +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/codenames.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/config/__init__.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/config/model.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/config/picker.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/config/picker_state.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/config/store.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/env_utils.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/error_display.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/figures.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/file_ref_hint.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/fragment_utils.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/goal_resume_view.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/history_printer.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/input_geometry.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/logging_adapter.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/markdown_render.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/mention_completer.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/message_style.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/models.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/path_context_hint.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/plugins/operation_state.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/preflight_wizard.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/print_mode.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/recap/__init__.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/recap/controller.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/resume_picker.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/resume_preview.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/resume_selector.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/selection_menu.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/slash_commands.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/startup.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/startup_profile.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/statusline/__init__.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/statusline/model.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/statusline/picker.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/statusline/picker_state.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/statusline/store.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/text_effects.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/tips.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/tool_fold.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/tool_result_store.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/tool_view.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/transcript_viewer.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/tui_parts/commands.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/tui_parts/key_bindings.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/dist-comate/.gitignore +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/dist-comate/comate_cli-0.7.10-py3-none-any.whl +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/dist-comate/comate_cli-0.7.10.tar.gz +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/dist-hico/.gitignore +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/dist-hico/hico_cli-0.7.17-py3-none-any.whl +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/dist-hico/hico_cli-0.7.17.tar.gz +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/docs/cli-rpc-stdio-guide.md +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/config/__init__.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/config/test_model.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/config/test_picker_state.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/config/test_picker_ui.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/config/test_roundtrip.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/config/test_store_load.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/config/test_store_save.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/conftest.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/recap/test_idle_recap_controller.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/recap/test_recap_history_rendering.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/recap/test_tui_idle_recap_integration.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/statusline/__init__.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/statusline/test_model.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/statusline/test_picker_state.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/statusline/test_store.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_animator_shuffle.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_app_mcp_preload.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_app_preflight_gate.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_app_print_mode.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_app_shutdown.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_app_startup_latency.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_app_token_cost_config.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_app_usage_line.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_btw_slash_command.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_cli_project_root.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_compact_command_semantics.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_completion_context_activation.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_completion_status_panel.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_context_command.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_discover_tab.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_errors_tab.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_event_renderer.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_event_renderer_e2e.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_event_renderer_log_boundary.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_event_renderer_log_queue.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_event_renderer_tool_args_progress.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_file_ref_hint.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_format_error.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_goal_resume_tui.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_goal_resume_view.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_goal_slash_command.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_handle_error.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_history_printer.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_history_printer_log.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_history_printer_subtitle_position.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_history_printer_tool_fold.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_history_sync_tool_fold.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_input_behavior.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_input_history.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_installed_tab.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_interrupt_exit_semantics.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_layout_coordinator.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_logging_adapter.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_markdown_render.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_marketplaces_tab.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_mcp_slash_command.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_mention_completer.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_model_switch_command.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_path_context_hint.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_plugin_operation_state.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_plugin_slash_commands.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_plugin_tui_components.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_preflight_copilot.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_question_key_bindings.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_resume_picker.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_resume_preview.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_resume_selector.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_rewind_command_semantics.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_rpc_protocol.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_rpc_stdio_bridge.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_selection_menu.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_session_query_token_summary.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_shutdown_noise_guard.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_shutdown_noise_integration.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_slash_argument_hint.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_slash_clear.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_slash_completer.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_slash_registry.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_startup_import_budget.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_startup_profile.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_status_bar_transient.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_task_panel_format.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_task_panel_key_bindings.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_task_panel_rendering.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_task_poll.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tips.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tool_fold.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tool_fold_panel.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tool_result_store.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tool_result_viewer.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tool_result_viewer_key_bindings.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tool_view.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_transcript_viewer.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_transcript_viewer_tool_fold.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tui_builtin_slash_chain.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tui_elapsed_status.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tui_esc_queue.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tui_llm_retry_panel.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tui_local_interactive_barrier.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tui_mcp_init_gate.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tui_paste_newline_guard.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tui_paste_placeholder.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tui_provider_error_shield.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tui_queue_preview.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tui_queue_sdk_source.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tui_scheduled_fire_log.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tui_send_undo_window.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tui_split_invariance.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tui_startup_latency.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tui_team_messages.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tui_thinking_display.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_usage_command.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tests/test_wrap_user_text.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tools/logo_lab.py +0 -0
- {comate_cli-0.8.6 → comate_cli-0.9.0}/tools/nico_preview.py +0 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Windows 上 Ctrl+C 退出时 `ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server`
|
|
8
|
+
和 traceback 漏到终端的问题。修复包含两部分:`app.py` finally 顺序调整,
|
|
9
|
+
以及 SDK stdio MCP 关闭路径切换到主动信号升级链。
|
|
10
|
+
- Windows 上 Ctrl+C 退出时 `Exception ignored in: <BaseSubprocessTransport.__del__>`
|
|
11
|
+
和 `ValueError: I/O operation on closed pipe` 漏到终端的问题。现在由
|
|
12
|
+
`_ShutdownNoiseGuard` catch-all 接管 `sys.unraisablehook` 并写入 agent log。
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- 新增 `/clear` slash command:清空当前 leader session 的 conversation,
|
|
17
|
+
保留 session_id、team membership、inbox 与 TaskStore。成功后会清屏、
|
|
18
|
+
重打 logo、重置 TUI transcript view,并刷新 status bar;busy 或
|
|
19
|
+
compacting 状态会拒绝执行。
|
|
20
|
+
- `_ShutdownNoiseGuard` 扩展为四合一接管器:SIGINT、`sys.unraisablehook`、
|
|
21
|
+
`sys.excepthook`、`warnings.showwarning`。捕获到的噪音统一路由到独立的
|
|
22
|
+
`comate.noise` logger(`propagate=False`),写入 `~/.comate/logs/agent.log`。
|
|
23
|
+
- 新增 `COMATE_DEBUG_NOISE=1` 环境变量逃生口,开发排障时可关闭噪音接管器,
|
|
24
|
+
查看旧版本的原始 stderr 输出。
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- TUI 分支退出时先执行 `_graceful_shutdown(...)`,再关闭 `logging_session`,
|
|
29
|
+
让现有 logging 屏蔽机制覆盖 MCP teardown 全程。
|
|
30
|
+
|
|
31
|
+
## 0.9.0 - 2026-07-02
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- 新增 `DistributionProfile` 白标扩展面,调用方可注入产品名、命令名、包名、
|
|
36
|
+
system role、provider presets、logo renderer、更新源与安装参数。
|
|
37
|
+
- CLI 入口、preflight、logo、更新检查、resume hint、MCP 子命令帮助文案等运行时路径
|
|
38
|
+
支持通过发行版 profile 接管用户可见身份。
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- `UpdateTarget` 改为由 `fetch_latest` 回调和 `install_args` 描述更新源。
|
|
43
|
+
直接构造 `UpdateTarget` 的下游需要从旧字段迁移到新契约;这是对该内部扩展面的
|
|
44
|
+
破坏性变更。
|
|
45
|
+
- 更新命令现在会透传 profile 提供的安装参数,支持发行版自定义安装源。
|
|
46
|
+
|
|
47
|
+
### Removed
|
|
48
|
+
|
|
49
|
+
- 公开仓库中的发行版专用示例、测试夹具和历史文档已中性化或移出仓库;构建产物目录
|
|
50
|
+
`packages/comate_cli/dist-*/` 已加入忽略规则。
|
|
51
|
+
|
|
52
|
+
## 0.8.6 - 2026-06-24
|
|
53
|
+
|
|
54
|
+
### `/loop` 定时循环能力升级
|
|
55
|
+
|
|
56
|
+
本周期对 `/loop` 做了一次提示词驱动的重构,修复了一个影响中文用户的核心 bug,
|
|
57
|
+
并收紧了调度规则。
|
|
58
|
+
|
|
59
|
+
#### Fixed
|
|
60
|
+
|
|
61
|
+
- **中文「每隔N分钟」不再静默回退为 10 分钟**:之前 `/loop 每隔1分钟 检查构建状态`
|
|
62
|
+
这类中文自然语言措辞,会因为未命中代码里的英文正则而静默回退为默认 10 分钟,
|
|
63
|
+
调度完全不符合预期。现在间隔解析从"代码硬正则"改为提示词驱动,把规则和原始输入
|
|
64
|
+
交给模型按用户意图(任意语言)自行解析。中文「每隔1分钟」「每两小时」、英文
|
|
65
|
+
"every couple of hours" 都能被正确识别。
|
|
66
|
+
- **`/loop` 运行数次后神秘停止**:根因是 TUI 状态栏刷新抛异常时会"杀死" event pump,
|
|
67
|
+
进而关闭 scheduled prompt scheduler。现在状态栏刷新失败被隔离为局部防护(带日志),
|
|
68
|
+
不再波及 event pump 与 `/loop` 调度消费。
|
|
69
|
+
|
|
70
|
+
#### Changed
|
|
71
|
+
|
|
72
|
+
- **间隔→cron 转换规则收紧**:为避免 cron 在边界重置导致节奏不均匀,规则现在只允许
|
|
73
|
+
能整除单位的间隔,其他值会自动向上取整并明确告知取整结果:
|
|
74
|
+
- 分钟(能整除 60):1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30(如 `7m → 10m`)
|
|
75
|
+
- 小时(能整除 24):1, 2, 3, 4, 6, 8, 12(如 `5h → 6h`;13~24h → `1d`)
|
|
76
|
+
- 天:仅 `1d`(禁止多天,`Nd, N>1` 会在月末重置,无法表达"每 N 天")
|
|
77
|
+
- 显式禁止 `7m`、`5h`、`2d` 等会让节奏"漂移"的写法。
|
|
78
|
+
- **CronCreate 使用规则精简**:删除四条容易让模型误判触发窗口/续跑语义的规则段落,
|
|
79
|
+
追加 recurring 与 one-shot 的可复用 cron 示例模板,减少模型凭空构造错误 cron。
|
|
80
|
+
|
|
81
|
+
### Added
|
|
82
|
+
|
|
83
|
+
- **空闲 Recap(会话摘要回顾)**:回到终端时自动生成"离开期间发生了什么"摘要并渲染到
|
|
84
|
+
scrollback,带 label 标签与禁用提示,方便恢复上下文。
|
|
85
|
+
- **`/learn` slash 命令**:引导把刚解决的问题沉淀为可复用知识。
|
|
86
|
+
- **`/implement-plan` slash 命令**:一键进入计划执行模式。
|
|
87
|
+
- **内置 default custom commands 加载链**:开箱即用一组默认 custom slash 命令。
|
|
88
|
+
- **工具调用流式进度**:Loading 行实时展示工具参数解析进度(token 估算),告别"干等"。
|
|
89
|
+
- **LLM 重试动态面板**:主 agent LLM 重试状态可视化,底部面板动态展示重试进度,SDK
|
|
90
|
+
原始重试噪声收敛到统一面板。
|
|
91
|
+
- **`Ctrl+J` 手动换行**:多行输入时显式插入换行。
|
|
92
|
+
- **`@path` 提示行统一**:抽出为单一事实源并在 staging 界面同步显示。
|
|
93
|
+
- **Logo 支持 2x 放大**;统一圆点 marker 为呼吸点动画;tool signature 路径自动相对化。
|
|
94
|
+
|
|
95
|
+
### Changed(体验优化)
|
|
96
|
+
|
|
97
|
+
- 退出汇总输出精简、token 字段标签重命名。
|
|
98
|
+
- 时长显示数字与单位间加空格(如 `12 s`)。
|
|
99
|
+
- scrollback diff 展示优化。
|
|
100
|
+
- 错误 tool result 截断/展开提示保持 dim 样式。
|
|
101
|
+
|
|
102
|
+
### Fixed(稳定性)
|
|
103
|
+
|
|
104
|
+
- **Windows 状态栏 git 命令卡住 TUI 事件泵**。
|
|
105
|
+
- **provider 异常击穿 prompt_toolkit 防护**:LLM provider 抛出未处理异常时不再击穿到 TUI 层。
|
|
106
|
+
- **LLM stream 清理的无界等待**。
|
|
107
|
+
- **启动更新检查改为真正异步 HTTP**:不再占用默认线程池导致退出卡死;移除 24h 限频机制。
|
|
108
|
+
- **memory prefetch 退出噪声**兜底屏蔽。
|
|
109
|
+
|
|
110
|
+
### Fixed(渲染与配置)
|
|
111
|
+
|
|
112
|
+
- scrollback diff 背景未铺满内容行、diff 背景在 tab 行上的宽度计算错误。
|
|
113
|
+
- 代码块末行背景填充保留,避免渲染断裂。
|
|
114
|
+
- Bash scrollback 命令摘要单行化,避免多行折叠错乱。
|
|
115
|
+
- marketplace 详情页更新反馈不正确。
|
|
116
|
+
- 移除 CLI 的上下文压缩阈值配置(改由 SDK 安全窗口自动管理)。
|
|
117
|
+
|
|
118
|
+
## 0.7.0a1 - 2026-05-19 (alpha)
|
|
119
|
+
|
|
120
|
+
> 这是一个 **alpha 预发布版本**,用于内部 / 早期测试。
|
|
121
|
+
> `pip install comate-cli` 默认不会安装到本版本,需要显式 `pip install comate-cli==0.7.0a1` 或 `pip install --pre comate-cli`。
|
|
122
|
+
|
|
123
|
+
### Breaking Changes
|
|
124
|
+
|
|
125
|
+
- 依赖 `comate-agent-sdk>=0.8.0a1`,与旧 SDK 不兼容。
|
|
126
|
+
- 旧版本生成的 resume jsonl / context 快照不保证可以正常回放,**建议从空 session 重新开始**。
|
|
127
|
+
|
|
128
|
+
### 累积变更
|
|
129
|
+
|
|
130
|
+
自 0.6.x 起累积大量 TUI 与会话能力改进,涵盖:
|
|
131
|
+
|
|
132
|
+
- Resume picker 重做:搜索、跨 cwd 切换、预览模式、视觉抛光
|
|
133
|
+
- Auto compact 期间显式提示 `Compacting context`
|
|
134
|
+
- Team / subagent 事件透出与渲染统一
|
|
135
|
+
- 与 SDK 0.8.0a2 配套的 tool envelope / token 账本展示
|
|
136
|
+
|
|
137
|
+
详细变更请参考 git log。
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Distribution profile contract for whitelabel CLI packages.
|
|
2
|
+
|
|
3
|
+
Private overlay packages can construct a DistributionProfile and pass it to
|
|
4
|
+
comate_cli.main.main(profile=...) to customize branding, presets, update
|
|
5
|
+
sources, logo rendering, and the system role. This module is the only public
|
|
6
|
+
contract for that extension surface.
|
|
7
|
+
|
|
8
|
+
Design:
|
|
9
|
+
docs/superpowers/specs/2026-07-02-cli-distribution-whitelabel-design.md
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import TYPE_CHECKING, Awaitable, Callable, Protocol
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from comate_agent_sdk.facade.config.provider_presets import ProviderPreset
|
|
20
|
+
from rich.console import Console
|
|
21
|
+
|
|
22
|
+
REQUIRED_PROVIDER_LEVELS: tuple[str, ...] = ("LOW", "MID", "HIGH")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class LatestRelease:
|
|
27
|
+
"""Result returned by an update source."""
|
|
28
|
+
|
|
29
|
+
version: str
|
|
30
|
+
release_notes_url: str | None = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
LatestVersionFetcher = Callable[[], Awaitable["LatestRelease | None"]]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True, slots=True)
|
|
37
|
+
class UpdateSource:
|
|
38
|
+
fetch_latest: LatestVersionFetcher
|
|
39
|
+
install_args: tuple[str, ...] = ()
|
|
40
|
+
release_notes_url: str | None = None
|
|
41
|
+
|
|
42
|
+
def __post_init__(self) -> None:
|
|
43
|
+
if not callable(self.fetch_latest):
|
|
44
|
+
raise ValueError("UpdateSource.fetch_latest must be callable")
|
|
45
|
+
object.__setattr__(self, "install_args", tuple(self.install_args))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class LogoRenderer(Protocol):
|
|
49
|
+
def __call__(self, console: "Console", *, project_root: Path | None = None) -> None: ...
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True, slots=True)
|
|
53
|
+
class DistributionProfile:
|
|
54
|
+
product_name: str = "Comate"
|
|
55
|
+
command_name: str = "comate"
|
|
56
|
+
package_name: str = "comate-cli"
|
|
57
|
+
provider_presets: tuple["ProviderPreset", ...] | None = None
|
|
58
|
+
update: UpdateSource | None = None
|
|
59
|
+
render_logo: LogoRenderer | None = None
|
|
60
|
+
system_role: str | None = None
|
|
61
|
+
|
|
62
|
+
def __post_init__(self) -> None:
|
|
63
|
+
for field_name in ("product_name", "command_name", "package_name"):
|
|
64
|
+
if not str(getattr(self, field_name)).strip():
|
|
65
|
+
raise ValueError(f"DistributionProfile.{field_name} must be non-empty")
|
|
66
|
+
|
|
67
|
+
if self.provider_presets is None:
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
object.__setattr__(self, "provider_presets", tuple(self.provider_presets))
|
|
71
|
+
|
|
72
|
+
if not self.provider_presets:
|
|
73
|
+
raise ValueError("DistributionProfile.provider_presets must be non-empty when provided")
|
|
74
|
+
|
|
75
|
+
for preset in self.provider_presets:
|
|
76
|
+
missing = [level for level in REQUIRED_PROVIDER_LEVELS if level not in preset.models]
|
|
77
|
+
if missing:
|
|
78
|
+
raise ValueError(
|
|
79
|
+
f"provider preset {preset.preset_id!r} missing levels: {', '.join(missing)}"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
DEFAULT_PROFILE = DistributionProfile()
|
|
@@ -5,6 +5,7 @@ import asyncio
|
|
|
5
5
|
import logging
|
|
6
6
|
import os
|
|
7
7
|
import signal
|
|
8
|
+
from pathlib import Path
|
|
8
9
|
import subprocess
|
|
9
10
|
import sys
|
|
10
11
|
import time
|
|
@@ -32,7 +33,7 @@ PRINT_OUTPUT_FORMAT_JSON = "json"
|
|
|
32
33
|
PRINT_OUTPUT_FORMATS = frozenset({PRINT_OUTPUT_FORMAT_TEXT, PRINT_OUTPUT_FORMAT_JSON})
|
|
33
34
|
PRINT_OUTPUT_FORMAT_UNSUPPORTED = frozenset({"stream-json"})
|
|
34
35
|
|
|
35
|
-
ParsedArgs = tuple[bool, str | None, bool, str | None, str]
|
|
36
|
+
ParsedArgs = tuple[bool, str | None, bool, str | None, str, tuple[str, ...]]
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
class _TerminalStateGuard:
|
|
@@ -205,13 +206,17 @@ class _ShutdownNoiseGuard:
|
|
|
205
206
|
return log
|
|
206
207
|
|
|
207
208
|
|
|
208
|
-
def _usage_text() -> str:
|
|
209
|
+
def _usage_text(command_name: str = "comate") -> str:
|
|
209
210
|
return (
|
|
210
211
|
"Usage:\n"
|
|
211
|
-
"
|
|
212
|
-
"
|
|
213
|
-
"
|
|
214
|
-
"
|
|
212
|
+
f" {command_name} [--rpc-stdio] [--add-dir <path>]...\n"
|
|
213
|
+
f" {command_name} -p [--output-format text|json] <prompt>\n"
|
|
214
|
+
f" {command_name} resume [<session_id>] [--rpc-stdio]\n"
|
|
215
|
+
f" {command_name} mcp <subcommand> [options]\n"
|
|
216
|
+
"\n"
|
|
217
|
+
"Options:\n"
|
|
218
|
+
" --add-dir <path> Grant tool read/write access to an extra directory\n"
|
|
219
|
+
" (repeatable). Applies to subagents too."
|
|
215
220
|
)
|
|
216
221
|
|
|
217
222
|
|
|
@@ -221,6 +226,7 @@ def _parse_args(argv: list[str]) -> ParsedArgs:
|
|
|
221
226
|
output_format = PRINT_OUTPUT_FORMAT_TEXT
|
|
222
227
|
output_format_explicit = False
|
|
223
228
|
positionals: list[str] = []
|
|
229
|
+
add_dirs: list[str] = []
|
|
224
230
|
|
|
225
231
|
index = 0
|
|
226
232
|
while index < len(argv):
|
|
@@ -253,6 +259,19 @@ def _parse_args(argv: list[str]) -> ParsedArgs:
|
|
|
253
259
|
output_format = value
|
|
254
260
|
index += 1
|
|
255
261
|
continue
|
|
262
|
+
if arg == "--add-dir" or arg.startswith("--add-dir="):
|
|
263
|
+
if arg == "--add-dir":
|
|
264
|
+
index += 1
|
|
265
|
+
if index >= len(argv):
|
|
266
|
+
raise _ArgumentError("--add-dir requires a path")
|
|
267
|
+
value = argv[index]
|
|
268
|
+
else:
|
|
269
|
+
value = arg.partition("=")[2]
|
|
270
|
+
if not value:
|
|
271
|
+
raise _ArgumentError("--add-dir requires a path")
|
|
272
|
+
add_dirs.append(value)
|
|
273
|
+
index += 1
|
|
274
|
+
continue
|
|
256
275
|
if arg.startswith("-"):
|
|
257
276
|
raise _ArgumentError(f"Unknown option: {arg}")
|
|
258
277
|
positionals.append(arg)
|
|
@@ -266,11 +285,11 @@ def _parse_args(argv: list[str]) -> ParsedArgs:
|
|
|
266
285
|
if rpc_stdio:
|
|
267
286
|
raise _ArgumentError("-p/--print and --rpc-stdio are mutually exclusive")
|
|
268
287
|
print_prompt = " ".join(positionals) if positionals else ""
|
|
269
|
-
return rpc_stdio, None, False, print_prompt, output_format
|
|
288
|
+
return rpc_stdio, None, False, print_prompt, output_format, tuple(add_dirs)
|
|
270
289
|
|
|
271
290
|
# Non -p mode: original logic
|
|
272
291
|
if not positionals:
|
|
273
|
-
return rpc_stdio, None, False, None, output_format
|
|
292
|
+
return rpc_stdio, None, False, None, output_format, tuple(add_dirs)
|
|
274
293
|
|
|
275
294
|
command = positionals[0]
|
|
276
295
|
if command != "resume":
|
|
@@ -281,14 +300,33 @@ def _parse_args(argv: list[str]) -> ParsedArgs:
|
|
|
281
300
|
raise _ArgumentError(
|
|
282
301
|
"resume without <session_id> does not support --rpc-stdio"
|
|
283
302
|
)
|
|
284
|
-
return rpc_stdio, None, True, None, output_format
|
|
303
|
+
return rpc_stdio, None, True, None, output_format, tuple(add_dirs)
|
|
285
304
|
|
|
286
305
|
if len(positionals) == 2:
|
|
287
|
-
return rpc_stdio, positionals[1], False, None, output_format
|
|
306
|
+
return rpc_stdio, positionals[1], False, None, output_format, tuple(add_dirs)
|
|
288
307
|
|
|
289
308
|
raise _ArgumentError("resume accepts at most one <session_id>")
|
|
290
309
|
|
|
291
310
|
|
|
311
|
+
def _validate_add_dirs(raw_dirs: tuple[str, ...]) -> tuple[Path, ...]:
|
|
312
|
+
"""Resolve and validate --add-dir paths (must exist and be directories).
|
|
313
|
+
|
|
314
|
+
Raises _ArgumentError (fail-fast) so a typo'd directory never silently
|
|
315
|
+
grants nothing: a granted dir is a permission boundary, so we reject
|
|
316
|
+
rather than skip. Sensitive/over-broad roots are left to the SDK path
|
|
317
|
+
guard at runtime.
|
|
318
|
+
"""
|
|
319
|
+
resolved: list[Path] = []
|
|
320
|
+
for raw in raw_dirs:
|
|
321
|
+
path = Path(raw).expanduser().resolve()
|
|
322
|
+
if not path.exists():
|
|
323
|
+
raise _ArgumentError(f"--add-dir path does not exist: {raw}")
|
|
324
|
+
if not path.is_dir():
|
|
325
|
+
raise _ArgumentError(f"--add-dir path is not a directory: {raw}")
|
|
326
|
+
resolved.append(path)
|
|
327
|
+
return tuple(resolved)
|
|
328
|
+
|
|
329
|
+
|
|
292
330
|
def _read_stdin_posix_idle_drain(
|
|
293
331
|
stdin: object,
|
|
294
332
|
*,
|
|
@@ -350,18 +388,22 @@ def _read_stdin_if_ready(
|
|
|
350
388
|
return str(read()) if callable(read) else ""
|
|
351
389
|
|
|
352
390
|
|
|
353
|
-
def main(argv: list[str] | None = None) -> None:
|
|
391
|
+
def main(argv: list[str] | None = None, *, profile=None) -> None:
|
|
392
|
+
from comate_cli.distribution import DEFAULT_PROFILE
|
|
393
|
+
|
|
394
|
+
profile = profile or DEFAULT_PROFILE
|
|
395
|
+
command_name = profile.command_name
|
|
354
396
|
run_argv = list(argv) if argv is not None else sys.argv[1:]
|
|
355
397
|
|
|
356
398
|
if run_argv in (["--help"], ["-h"]):
|
|
357
|
-
sys.stdout.write(f"{_usage_text()}\n")
|
|
399
|
+
sys.stdout.write(f"{_usage_text(command_name=command_name)}\n")
|
|
358
400
|
return
|
|
359
401
|
|
|
360
402
|
if run_argv and run_argv[0] == "mcp":
|
|
361
403
|
from comate_cli.mcp_cli import McpCliError, run_mcp_command
|
|
362
404
|
|
|
363
405
|
try:
|
|
364
|
-
run_mcp_command(run_argv[1:])
|
|
406
|
+
run_mcp_command(run_argv[1:], command_name=command_name)
|
|
365
407
|
return
|
|
366
408
|
except McpCliError as exc:
|
|
367
409
|
sys.stderr.write(f"{exc}\n")
|
|
@@ -379,11 +421,17 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
379
421
|
_app_import_done_perf = time.perf_counter()
|
|
380
422
|
|
|
381
423
|
try:
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
424
|
+
(
|
|
425
|
+
rpc_stdio,
|
|
426
|
+
resume_session_id,
|
|
427
|
+
resume_select,
|
|
428
|
+
print_prompt,
|
|
429
|
+
print_output_format,
|
|
430
|
+
add_dirs_raw,
|
|
431
|
+
) = _parse_args(run_argv)
|
|
432
|
+
add_dirs = _validate_add_dirs(add_dirs_raw)
|
|
385
433
|
except _ArgumentError as exc:
|
|
386
|
-
sys.stderr.write(f"{exc}\n{_usage_text()}\n")
|
|
434
|
+
sys.stderr.write(f"{exc}\n{_usage_text(command_name=command_name)}\n")
|
|
387
435
|
raise SystemExit(2) from exc
|
|
388
436
|
|
|
389
437
|
# Assemble print mode message
|
|
@@ -404,9 +452,11 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
404
452
|
resume_select=resume_select,
|
|
405
453
|
print_message=print_message,
|
|
406
454
|
print_output_format=print_output_format,
|
|
455
|
+
add_dirs=add_dirs,
|
|
407
456
|
process_start_perf=_PROCESS_START_PERF,
|
|
408
457
|
app_import_start_perf=_app_import_start_perf,
|
|
409
458
|
app_import_done_perf=_app_import_done_perf,
|
|
459
|
+
profile=profile,
|
|
410
460
|
)
|
|
411
461
|
)
|
|
412
462
|
except KeyboardInterrupt:
|
|
@@ -17,6 +17,7 @@ from comate_agent_sdk.mcp import (
|
|
|
17
17
|
validate_server_config,
|
|
18
18
|
write_mcp_servers_to_path,
|
|
19
19
|
)
|
|
20
|
+
from comate_agent_sdk.mcp.cwd import format_stdio_cwd_resolution, resolve_stdio_cwd
|
|
20
21
|
from comate_agent_sdk.mcp.types import McpServerConfig
|
|
21
22
|
from comate_agent_sdk.utils.paths import PathInput
|
|
22
23
|
|
|
@@ -40,27 +41,27 @@ class _McpArgumentParser(argparse.ArgumentParser):
|
|
|
40
41
|
raise McpCliError(message)
|
|
41
42
|
|
|
42
43
|
|
|
43
|
-
def _build_parser() -> argparse.ArgumentParser:
|
|
44
|
+
def _build_parser(command_name: str = "comate") -> argparse.ArgumentParser:
|
|
44
45
|
parser = _McpArgumentParser(
|
|
45
|
-
prog="
|
|
46
|
+
prog=f"{command_name} mcp",
|
|
46
47
|
description="Configure and manage MCP servers",
|
|
47
48
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
48
49
|
epilog=(
|
|
49
50
|
"Examples:\n"
|
|
50
51
|
" # Add HTTP server:\n"
|
|
51
|
-
"
|
|
52
|
+
f" {command_name} mcp add --transport http sentry https://mcp.sentry.dev/mcp\n\n"
|
|
52
53
|
" # Add HTTP server with headers:\n"
|
|
53
|
-
"
|
|
54
|
+
f" {command_name} mcp add --transport http corridor https://app.corridor.dev/api/mcp "
|
|
54
55
|
'--header "Authorization: Bearer ..."\n\n'
|
|
55
56
|
" # Add stdio server with environment variables:\n"
|
|
56
|
-
"
|
|
57
|
+
f" {command_name} mcp add -e API_KEY=xxx my-server -- npx my-mcp-server\n\n"
|
|
57
58
|
" # Add stdio server with subprocess flags:\n"
|
|
58
|
-
"
|
|
59
|
+
f" {command_name} mcp add my-server -- my-command --some-flag arg1"
|
|
59
60
|
),
|
|
60
61
|
)
|
|
61
62
|
subparsers = parser.add_subparsers(dest="command")
|
|
62
63
|
|
|
63
|
-
add_parser = subparsers.add_parser("add", help="Add an MCP server to
|
|
64
|
+
add_parser = subparsers.add_parser("add", help=f"Add an MCP server to {command_name}")
|
|
64
65
|
add_parser.add_argument(
|
|
65
66
|
"-s",
|
|
66
67
|
"--scope",
|
|
@@ -295,7 +296,11 @@ def _split_add_args(argv: list[str]) -> tuple[list[str], list[str]]:
|
|
|
295
296
|
return ["add", *option_args, *positional_args], extra_args
|
|
296
297
|
|
|
297
298
|
|
|
298
|
-
def _build_add_server_config(
|
|
299
|
+
def _build_add_server_config(
|
|
300
|
+
args: argparse.Namespace,
|
|
301
|
+
*,
|
|
302
|
+
command_name: str = "comate",
|
|
303
|
+
) -> McpServerConfig:
|
|
299
304
|
transport = str(args.transport or "stdio").strip().lower()
|
|
300
305
|
header_entries = [str(item) for item in list(args.header or [])]
|
|
301
306
|
env_entries = [str(item) for item in list(args.env or [])]
|
|
@@ -308,8 +313,8 @@ def _build_add_server_config(args: argparse.Namespace) -> McpServerConfig:
|
|
|
308
313
|
or getattr(args, "callback_port", None)
|
|
309
314
|
):
|
|
310
315
|
raise McpCliError(
|
|
311
|
-
"OAuth flags are not supported by
|
|
312
|
-
"Use headers for token-based HTTP/SSE MCP servers, or add a
|
|
316
|
+
f"OAuth flags are not supported by {command_name} mcp add yet. "
|
|
317
|
+
"Use headers for token-based HTTP/SSE MCP servers, or add a native oauth object via .mcp.json."
|
|
313
318
|
)
|
|
314
319
|
|
|
315
320
|
if transport in {"http", "sse"}:
|
|
@@ -397,6 +402,21 @@ def _format_server_endpoint(cfg: McpServerConfig) -> str:
|
|
|
397
402
|
return " ".join([command, *str_args]).strip()
|
|
398
403
|
|
|
399
404
|
|
|
405
|
+
def _format_stdio_cwd_line(
|
|
406
|
+
cfg: McpServerConfig,
|
|
407
|
+
*,
|
|
408
|
+
project_root: PathInput | None,
|
|
409
|
+
) -> str | None:
|
|
410
|
+
server_type = str(cfg.get("type", "stdio")).strip().lower() # type: ignore[attr-defined]
|
|
411
|
+
if server_type != "stdio":
|
|
412
|
+
return None
|
|
413
|
+
try:
|
|
414
|
+
resolution = resolve_stdio_cwd(cfg, project_root=project_root, create=False)
|
|
415
|
+
except ValueError as exc:
|
|
416
|
+
return f"<invalid cwd: {exc}>"
|
|
417
|
+
return format_stdio_cwd_resolution(resolution)
|
|
418
|
+
|
|
419
|
+
|
|
400
420
|
def _write_server_to_scope(
|
|
401
421
|
*,
|
|
402
422
|
name: str,
|
|
@@ -440,13 +460,18 @@ def _find_scopes_containing_server(
|
|
|
440
460
|
return found
|
|
441
461
|
|
|
442
462
|
|
|
443
|
-
def _cmd_add(
|
|
463
|
+
def _cmd_add(
|
|
464
|
+
args: argparse.Namespace,
|
|
465
|
+
*,
|
|
466
|
+
project_root: PathInput | None,
|
|
467
|
+
command_name: str = "comate",
|
|
468
|
+
) -> None:
|
|
444
469
|
scope = str(args.scope)
|
|
445
470
|
name = str(args.name).strip()
|
|
446
471
|
if not name:
|
|
447
472
|
raise McpCliError("Server name cannot be empty")
|
|
448
473
|
|
|
449
|
-
config = _build_add_server_config(args)
|
|
474
|
+
config = _build_add_server_config(args, command_name=command_name)
|
|
450
475
|
path, existed = _write_server_to_scope(
|
|
451
476
|
name=name,
|
|
452
477
|
config=config,
|
|
@@ -461,11 +486,16 @@ def _cmd_add(args: argparse.Namespace, *, project_root: PathInput | None) -> Non
|
|
|
461
486
|
)
|
|
462
487
|
|
|
463
488
|
|
|
464
|
-
def _cmd_add_json(
|
|
489
|
+
def _cmd_add_json(
|
|
490
|
+
args: argparse.Namespace,
|
|
491
|
+
*,
|
|
492
|
+
project_root: PathInput | None,
|
|
493
|
+
command_name: str = "comate",
|
|
494
|
+
) -> None:
|
|
465
495
|
if _has_oauth_flags(args):
|
|
466
496
|
raise McpCliError(
|
|
467
|
-
"OAuth flags are not supported by
|
|
468
|
-
"Use headers for token-based HTTP/SSE MCP servers, or add a
|
|
497
|
+
f"OAuth flags are not supported by {command_name} mcp add-json yet. "
|
|
498
|
+
"Use headers for token-based HTTP/SSE MCP servers, or add a native oauth object via .mcp.json."
|
|
469
499
|
)
|
|
470
500
|
|
|
471
501
|
scope = str(args.scope)
|
|
@@ -496,7 +526,12 @@ def _cmd_add_json(args: argparse.Namespace, *, project_root: PathInput | None) -
|
|
|
496
526
|
)
|
|
497
527
|
|
|
498
528
|
|
|
499
|
-
def _cmd_remove(
|
|
529
|
+
def _cmd_remove(
|
|
530
|
+
args: argparse.Namespace,
|
|
531
|
+
*,
|
|
532
|
+
project_root: PathInput | None,
|
|
533
|
+
command_name: str = "comate",
|
|
534
|
+
) -> None:
|
|
500
535
|
name = str(args.name).strip()
|
|
501
536
|
if not name:
|
|
502
537
|
raise McpCliError("Server name cannot be empty")
|
|
@@ -509,7 +544,7 @@ def _cmd_remove(args: argparse.Namespace, *, project_root: PathInput | None) ->
|
|
|
509
544
|
if len(found) > 1:
|
|
510
545
|
scope_list = ", ".join(found)
|
|
511
546
|
suggestions = "; ".join(
|
|
512
|
-
f"
|
|
547
|
+
f"{command_name} mcp remove {name!r} --scope {scope}" for scope in found
|
|
513
548
|
)
|
|
514
549
|
raise McpCliError(
|
|
515
550
|
f"MCP server '{name}' exists in multiple scopes: {scope_list}. "
|
|
@@ -547,7 +582,9 @@ def _cmd_list(args: argparse.Namespace, *, project_root: PathInput | None) -> No
|
|
|
547
582
|
return
|
|
548
583
|
|
|
549
584
|
sys.stdout.write("Checking MCP server health...\n\n")
|
|
550
|
-
health_rows = asyncio.run(
|
|
585
|
+
health_rows = asyncio.run(
|
|
586
|
+
collect_mcp_server_health(servers=servers, project_root=project_root)
|
|
587
|
+
)
|
|
551
588
|
health_by_alias = {row.alias: row for row in health_rows}
|
|
552
589
|
|
|
553
590
|
for alias in sorted(servers.keys()):
|
|
@@ -565,9 +602,17 @@ def _cmd_list(args: argparse.Namespace, *, project_root: PathInput | None) -> No
|
|
|
565
602
|
else f"{CROSS_MARK} Unknown"
|
|
566
603
|
)
|
|
567
604
|
sys.stdout.write(f"{alias}: {endpoint} ({server_type}) - {status}\n")
|
|
605
|
+
cwd_line = _format_stdio_cwd_line(cfg, project_root=project_root)
|
|
606
|
+
if cwd_line:
|
|
607
|
+
sys.stdout.write(f" cwd: {cwd_line}\n")
|
|
568
608
|
|
|
569
609
|
|
|
570
|
-
def _cmd_get(
|
|
610
|
+
def _cmd_get(
|
|
611
|
+
args: argparse.Namespace,
|
|
612
|
+
*,
|
|
613
|
+
project_root: PathInput | None,
|
|
614
|
+
command_name: str = "comate",
|
|
615
|
+
) -> None:
|
|
571
616
|
name = str(args.name).strip()
|
|
572
617
|
scope = str(args.scope)
|
|
573
618
|
if not name:
|
|
@@ -594,7 +639,9 @@ def _cmd_get(args: argparse.Namespace, *, project_root: PathInput | None) -> Non
|
|
|
594
639
|
scope_line = _scope_label(scope)
|
|
595
640
|
remove_scope = scope
|
|
596
641
|
|
|
597
|
-
health_rows = asyncio.run(
|
|
642
|
+
health_rows = asyncio.run(
|
|
643
|
+
collect_mcp_server_health(servers={name: cfg}, project_root=project_root)
|
|
644
|
+
)
|
|
598
645
|
status = f"{CROSS_MARK} Not connected"
|
|
599
646
|
if health_rows:
|
|
600
647
|
row = health_rows[0]
|
|
@@ -624,6 +671,9 @@ def _cmd_get(args: argparse.Namespace, *, project_root: PathInput | None) -> Non
|
|
|
624
671
|
raw_args = cfg.get("args") or [] # type: ignore[attr-defined]
|
|
625
672
|
if isinstance(raw_args, list) and raw_args:
|
|
626
673
|
lines.append(f" Args: {' '.join(str(item) for item in raw_args)}")
|
|
674
|
+
cwd_line = _format_stdio_cwd_line(cfg, project_root=project_root)
|
|
675
|
+
if cwd_line:
|
|
676
|
+
lines.append(f" Cwd: {cwd_line}")
|
|
627
677
|
env = cfg.get("env") or {} # type: ignore[attr-defined]
|
|
628
678
|
if isinstance(env, dict) and env:
|
|
629
679
|
lines.append(" Env:")
|
|
@@ -632,13 +682,18 @@ def _cmd_get(args: argparse.Namespace, *, project_root: PathInput | None) -> Non
|
|
|
632
682
|
|
|
633
683
|
lines.append("")
|
|
634
684
|
lines.append(
|
|
635
|
-
f'To remove this server, run:
|
|
685
|
+
f'To remove this server, run: {command_name} mcp remove "{name}" --scope {remove_scope}'
|
|
636
686
|
)
|
|
637
687
|
sys.stdout.write("\n".join(lines) + "\n")
|
|
638
688
|
|
|
639
689
|
|
|
640
|
-
def run_mcp_command(
|
|
641
|
-
|
|
690
|
+
def run_mcp_command(
|
|
691
|
+
argv: list[str],
|
|
692
|
+
*,
|
|
693
|
+
project_root: PathInput | None = None,
|
|
694
|
+
command_name: str = "comate",
|
|
695
|
+
) -> None:
|
|
696
|
+
parser = _build_parser(command_name=command_name)
|
|
642
697
|
if argv and argv[0] == "add":
|
|
643
698
|
parser_argv, extra_args = _split_add_args(argv)
|
|
644
699
|
parsed = parser.parse_args(parser_argv)
|
|
@@ -667,6 +722,9 @@ def run_mcp_command(argv: list[str], *, project_root: PathInput | None = None) -
|
|
|
667
722
|
raise McpCliError(f"Unknown mcp command: {command}")
|
|
668
723
|
|
|
669
724
|
try:
|
|
670
|
-
|
|
725
|
+
if command in {"add", "add-json", "get", "remove"}:
|
|
726
|
+
handler(parsed, project_root=project_root, command_name=command_name)
|
|
727
|
+
else:
|
|
728
|
+
handler(parsed, project_root=project_root)
|
|
671
729
|
except McpConfigError as exc:
|
|
672
730
|
raise McpCliError(str(exc), exit_code=1) from exc
|