ayder-cli 2.1.2__tar.gz → 2.1.4__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.
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/PKG-INFO +3 -1
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/docs/config.toml.example +6 -1
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/pyproject.toml +2 -1
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/core/config.py +4 -1
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/core/ollama_context_manager.py +2 -2
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/themes/claude.py +27 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/themes/original.py +21 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/definition.py +10 -4
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/plugin_manager.py +9 -4
- ayder_cli-2.1.4/src/ayder_cli/tools/plugin_status.py +33 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/app.py +81 -27
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/commands.py +38 -8
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/keybindings.py +3 -3
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/widgets.py +147 -14
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_config_coverage.py +13 -9
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_ollama_context_manager.py +15 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_plugin_manager.py +82 -0
- ayder_cli-2.1.4/tests/tools/test_plugin_status.py +36 -0
- ayder_cli-2.1.4/tests/tui/test_input_box.py +114 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_multiline_input.py +37 -40
- ayder_cli-2.1.4/tests/tui/test_status_bar_badges.py +70 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_widgets.py +77 -1
- ayder_cli-2.1.4/tests/ui/test_plugin_autoenable.py +44 -0
- ayder_cli-2.1.4/tests/ui/test_tui_commands_plugin.py +72 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/.github/workflows/ci.yml +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/.github/workflows/python-package.yml +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/.gitignore +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/.python-version +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/AGENTS.md +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/CHANGELOG.md +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/LICENSE +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/README.md +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/docs/PROJECT_STRUCTURE.md +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/docs/README_AGENT_WORKFLOW.md +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/docs/README_OLLAMA.md +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/docs/cc.png +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/__init__.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/__main__.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/agents/__init__.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/agents/callbacks.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/agents/config.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/agents/registry.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/agents/run.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/agents/runner.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/agents/tool.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/application/README.md +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/application/__init__.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/application/execution_policy.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/application/message_contract.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/application/runtime_factory.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/application/validation.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/cli.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/cli_callbacks.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/cli_runner.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/console.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/core/cache_monitor.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/core/config_migration.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/core/context.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/core/context_manager.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/core/context_manager_factory.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/core/default_context_manager.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/core/result.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/logging_config.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/loops/__init__.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/loops/chat_loop.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/parser.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/process_manager.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/prompts.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/__init__.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/base.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/claude.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/deepseek.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/gemini.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/glm.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/__init__.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/_errors.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/base.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/deepseek.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/generic_native.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/generic_xml.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/matrix.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/minimax.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/qwen3.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/registry.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_inspector.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/openai.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/qwen.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/orchestrator.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/retry.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/services/__init__.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/services/interactions.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/themes/__init__.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/__init__.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/__init__.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/context.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/context_definitions.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/filesystem.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/filesystem_definitions.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/notes.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/notes_definitions.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/process_manager_definitions.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/search.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/search_definitions.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/shell.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/shell_definitions.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/tasks.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/tasks_definitions.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/utils_tools.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/utils_tools_definitions.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/web.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/web_definitions.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/execution.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/hooks.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/normalization.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/plugin_api.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/plugin_github.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/registry.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/schemas.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/utils.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/__init__.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/adapter.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/helpers.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/parser.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/screens.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/theme_manager.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/types.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/ui.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/version.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/COVERAGE.md +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/__init__.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/agents/__init__.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/agents/test_agent_tools.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/agents/test_callbacks.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/agents/test_config.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/agents/test_integration.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/agents/test_registry.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/agents/test_run.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/agents/test_runner.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/agents/test_runner_provider_unavailable.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/agents/test_tool.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/application/test_message_contract.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/application/test_runtime_factory.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/client/test_main.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/convergence/__init__.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/convergence/test_execution_policy_parity.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/convergence/test_runtime_wiring.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/convergence/test_validation_path.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_cache_monitor.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_config.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_config_migration.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_config_provider_logging.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_config_retry.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_config_v2.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_context_manager_factory.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_default_context_manager.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_default_context_manager_should_compact.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_ollama_context_manager_cache_monitor.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_ollama_context_manager_max_history.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_parameter_aliasing.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_parser.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/loops/__init__.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/loops/test_base.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/loops/test_chat_loop_hook.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/loops/test_chat_loop_tool_truncation.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/__init__.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/__init__.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_base.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_deepseek.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_errors.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_fallback.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_generic_native.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_generic_xml.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_matrix.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_minimax.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_provider_integration.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_qwen3.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_registry.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_glm_async.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_ollama_inspector.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_ollama_native.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_ollama_xml.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_ollama_xml_autoroute.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_ollama_xml_usage.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_orchestrator_capabilities.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_qwen_async.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_retry_classifier.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_retry_integration.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_retrying_provider.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/services/test_boundary.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/services/test_interaction_sink.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/test_cli.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/test_cli_callbacks.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/test_context.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/test_env_manager.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/test_logging_config.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/test_notes.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/test_process_manager.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/test_tasks.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/__init__.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_agent_notes.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_definition_discovery.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_di_injection.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_impl.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_impl_coverage.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_path_security.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_plugin_api.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_plugin_github.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_plugin_loading.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_registry.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_registry_coverage.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_result.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_schemas.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_search_codebase.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_utils.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_web.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/__init__.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_agent_list_screen.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_do_clear_coordination.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_follow_mode.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_help_screen.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_keybindings.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_multi_select_screen.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_paste.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_pending_compact_consumer.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_pull_delivery.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_turn_consumer.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_confirm_screen.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_diff_preview.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_provider_switch.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_run_tui_startup.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_tui_assistant_sanitization.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_tui_chat_loop.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_tui_commands_agent.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_tui_commands_logging.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_tui_commands_provider.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_tui_helpers.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_tui_temporal_command.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_tui_widgets.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_ui.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_ui_coverage.py +0 -0
- {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_ui_verbose.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ayder-cli
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.4
|
|
4
4
|
Summary: AI agent for any LLMs
|
|
5
5
|
Project-URL: Homepage, https://github.com/ayder/ayder-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/ayder/ayder-cli.git
|
|
@@ -34,6 +34,8 @@ Provides-Extra: glm
|
|
|
34
34
|
Requires-Dist: zhipuai; extra == 'glm'
|
|
35
35
|
Provides-Extra: google
|
|
36
36
|
Requires-Dist: google-genai; extra == 'google'
|
|
37
|
+
Provides-Extra: mcp
|
|
38
|
+
Requires-Dist: mcp; extra == 'mcp'
|
|
37
39
|
Provides-Extra: qwen
|
|
38
40
|
Requires-Dist: dashscope; extra == 'qwen'
|
|
39
41
|
Description-Content-Type: text/markdown
|
|
@@ -82,7 +82,12 @@ verbose = false
|
|
|
82
82
|
prompt = "STANDARD" # MINIMAL | STANDARD | EXTENDED
|
|
83
83
|
max_background_processes = 5 # cap on concurrent background processes (e.g. test runs)
|
|
84
84
|
max_output_tokens = 8192 # orchestrator response cap
|
|
85
|
-
|
|
85
|
+
# Max conversation messages kept per turn. 0 = unlimited (default): the context
|
|
86
|
+
# manager's token budget + compaction are the bound, so the model uses its real
|
|
87
|
+
# window. A small fixed cap (the old default was 30) thrashes a tiny history
|
|
88
|
+
# window on a large-context model and wastes most of it. Set a finite number
|
|
89
|
+
# only if you want a hard message cap regardless of window size.
|
|
90
|
+
max_history_messages = 0
|
|
86
91
|
agent_timeout = 1800 # seconds before a background agent is cancelled (30 min; reasoning+coding can be slow)
|
|
87
92
|
# Tools the agents inherit. A coding harness needs file/shell/search (core),
|
|
88
93
|
# tasks/notes (metadata), background process control for test suites
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ayder-cli"
|
|
7
|
-
version = "2.1.
|
|
7
|
+
version = "2.1.4"
|
|
8
8
|
description = "AI agent for any LLMs"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -37,6 +37,7 @@ google = ["google-genai"]
|
|
|
37
37
|
qwen = ["dashscope"]
|
|
38
38
|
glm = ["zhipuai"]
|
|
39
39
|
all = ["ayder-cli[anthropic,google,qwen,glm]"]
|
|
40
|
+
mcp = ["mcp"]
|
|
40
41
|
|
|
41
42
|
[project.urls]
|
|
42
43
|
Homepage = "https://github.com/ayder/ayder-cli"
|
|
@@ -288,7 +288,10 @@ class Config(BaseModel):
|
|
|
288
288
|
logging_retention: str = Field(default="7 days")
|
|
289
289
|
max_background_processes: int = Field(default=5)
|
|
290
290
|
max_output_tokens: int = Field(default=4096)
|
|
291
|
-
|
|
291
|
+
# 0 = unlimited: let the context manager's token budget + compaction be the
|
|
292
|
+
# bound. The old fixed 30 predates large-context models and thrashes a tiny
|
|
293
|
+
# history window on a big context.
|
|
294
|
+
max_history_messages: int = Field(default=0)
|
|
292
295
|
prompt: str = Field(default="STANDARD")
|
|
293
296
|
chat_protocol: str = Field(default="ollama")
|
|
294
297
|
stop_sequences: list[str] = Field(default_factory=list)
|
|
@@ -237,8 +237,8 @@ class OllamaContextManager:
|
|
|
237
237
|
cache_state = f", cache={status.state}({status.hit_ratio:.0%})"
|
|
238
238
|
|
|
239
239
|
logger.info(
|
|
240
|
-
"Context: %d/%d tokens (%.0f%%), completion=%d, speed=%s%s",
|
|
241
|
-
self._real_prompt_tokens, ceiling, utilization,
|
|
240
|
+
"Context[%s]: %d/%d tokens (%.0f%%), completion=%d, speed=%s%s",
|
|
241
|
+
self._model or "?", self._real_prompt_tokens, ceiling, utilization,
|
|
242
242
|
self._real_completion_tokens, prompt_speed, cache_state,
|
|
243
243
|
)
|
|
244
244
|
|
|
@@ -633,6 +633,27 @@ AgentPanel .agent-detail {
|
|
|
633
633
|
padding: 0 0 0 4;
|
|
634
634
|
}
|
|
635
635
|
|
|
636
|
+
/* ThinkingPanel - Scrollable panel showing the model's reasoning stream.
|
|
637
|
+
Toggled with Ctrl+T. Taller than the tool/agent panels because reasoning is
|
|
638
|
+
long-form. Targets 'ThinkingPanel' in widgets.py. */
|
|
639
|
+
ThinkingPanel {
|
|
640
|
+
layout: vertical;
|
|
641
|
+
height: auto;
|
|
642
|
+
max-height: 60%;
|
|
643
|
+
background: #0a0a18;
|
|
644
|
+
border-top: solid #333350;
|
|
645
|
+
padding: 1 1;
|
|
646
|
+
overflow-y: scroll;
|
|
647
|
+
display: none;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
ThinkingPanel .thinking-content {
|
|
651
|
+
height: auto;
|
|
652
|
+
color: #8888aa;
|
|
653
|
+
text-style: italic;
|
|
654
|
+
padding: 0 1;
|
|
655
|
+
}
|
|
656
|
+
|
|
636
657
|
/* AyderApp - The root application screen. */
|
|
637
658
|
Screen {
|
|
638
659
|
layout: vertical;
|
|
@@ -669,6 +690,12 @@ Screen:inline {
|
|
|
669
690
|
width: 100%;
|
|
670
691
|
}
|
|
671
692
|
|
|
693
|
+
#thinking-panel {
|
|
694
|
+
min-height: 0;
|
|
695
|
+
max-height: 60%;
|
|
696
|
+
width: 100%;
|
|
697
|
+
}
|
|
698
|
+
|
|
672
699
|
#input-bar {
|
|
673
700
|
height: auto;
|
|
674
701
|
min-height: 1;
|
|
@@ -514,6 +514,27 @@ ToolPanel .tool-item.completed {
|
|
|
514
514
|
color: $success;
|
|
515
515
|
}
|
|
516
516
|
|
|
517
|
+
/* ThinkingPanel - Scrollable reasoning stream, toggled with Ctrl+T.
|
|
518
|
+
Taller than the tool/agent panels because reasoning is long-form. */
|
|
519
|
+
ThinkingPanel {
|
|
520
|
+
layout: vertical;
|
|
521
|
+
height: auto;
|
|
522
|
+
min-height: 6;
|
|
523
|
+
max-height: 24;
|
|
524
|
+
background: $surface-darken-1;
|
|
525
|
+
border-top: heavy $primary;
|
|
526
|
+
padding: 1 1;
|
|
527
|
+
overflow-y: scroll;
|
|
528
|
+
display: none;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
ThinkingPanel .thinking-content {
|
|
532
|
+
height: auto;
|
|
533
|
+
color: $text-muted;
|
|
534
|
+
text-style: italic;
|
|
535
|
+
padding: 0 1;
|
|
536
|
+
}
|
|
537
|
+
|
|
517
538
|
/* AyderApp - Main application */
|
|
518
539
|
Screen {
|
|
519
540
|
layout: vertical;
|
|
@@ -168,7 +168,15 @@ def _discover_definitions() -> Tuple["ToolDefinition", ...]:
|
|
|
168
168
|
|
|
169
169
|
_BUILTIN_DEFINITIONS: Tuple[ToolDefinition, ...] = _discover_definitions()
|
|
170
170
|
|
|
171
|
-
# Phase 1: Merge global plugins at import time
|
|
171
|
+
# Phase 1: Merge global plugins at import time.
|
|
172
|
+
#
|
|
173
|
+
# Discovery runs *inside this module's own body*, so a plugin whose definitions
|
|
174
|
+
# module reads ``TOOL_DEFINITIONS`` at import time (e.g. to avoid tool-name
|
|
175
|
+
# collisions, as mcp-tool does) would otherwise hit a circular import — the name
|
|
176
|
+
# isn't bound yet. Bind it provisionally to the builtins first; those are the
|
|
177
|
+
# only definitions that exist during discovery anyway. It is reassigned to the
|
|
178
|
+
# full set (builtins + plugins) immediately after discovery completes.
|
|
179
|
+
TOOL_DEFINITIONS: Tuple[ToolDefinition, ...] = _BUILTIN_DEFINITIONS
|
|
172
180
|
_GLOBAL_PLUGIN_DEFS: Tuple[ToolDefinition, ...] = ()
|
|
173
181
|
_PLUGIN_HANDLERS: Dict[str, Any] = {}
|
|
174
182
|
|
|
@@ -179,9 +187,7 @@ try:
|
|
|
179
187
|
except Exception as e:
|
|
180
188
|
logger.warning(f"Failed to load global plugins: {e}")
|
|
181
189
|
|
|
182
|
-
TOOL_DEFINITIONS
|
|
183
|
-
_BUILTIN_DEFINITIONS + _GLOBAL_PLUGIN_DEFS
|
|
184
|
-
)
|
|
190
|
+
TOOL_DEFINITIONS = _BUILTIN_DEFINITIONS + _GLOBAL_PLUGIN_DEFS
|
|
185
191
|
TOOL_DEFINITIONS_BY_NAME: Dict[str, ToolDefinition] = {
|
|
186
192
|
td.name: td for td in TOOL_DEFINITIONS
|
|
187
193
|
}
|
|
@@ -147,10 +147,15 @@ def install_plugin_from_local(
|
|
|
147
147
|
"Use --force to overwrite."
|
|
148
148
|
)
|
|
149
149
|
|
|
150
|
-
# Copy plugin files
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
150
|
+
# Copy plugin files. When the source IS the install target — e.g. reinstalling
|
|
151
|
+
# an already-installed plugin from its own directory with --force, which is
|
|
152
|
+
# exactly what the missing-dependency warning recommends — the files are
|
|
153
|
+
# already in place. Skip the copy so we don't rmtree the source out from under
|
|
154
|
+
# ourselves (the dependency reinstall + registry refresh below still run).
|
|
155
|
+
if source_dir.resolve() != target_dir.resolve():
|
|
156
|
+
if target_dir.exists():
|
|
157
|
+
shutil.rmtree(target_dir)
|
|
158
|
+
shutil.copytree(source_dir, target_dir)
|
|
154
159
|
|
|
155
160
|
# Update plugins.json
|
|
156
161
|
data = read_plugins_json(plugins_dir)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Plugin status-badge registry.
|
|
2
|
+
|
|
3
|
+
A lightweight, process-global registry that plugins use to publish a status
|
|
4
|
+
badge (a short label plus a colour) for display in the TUI status bar. The
|
|
5
|
+
mcp-tool plugin, for example, calls ``set_status("mcp", "MCP: news-digest",
|
|
6
|
+
"green")`` once it connects to its MCP servers, and ``set_status("mcp", "MCP",
|
|
7
|
+
"red")`` when configured servers are unreachable.
|
|
8
|
+
|
|
9
|
+
The TUI ``StatusBar`` reads :func:`get_all` to render the badges. Keeping this
|
|
10
|
+
in ayder core (rather than inside a plugin) means the badge survives even when
|
|
11
|
+
the plugin's own modules are loaded under throwaway names during discovery, and
|
|
12
|
+
gives the plugin a stable import target instead of a no-op fallback.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
# plugin name -> (label, colour). Colour is a Rich colour/style name ("green").
|
|
18
|
+
_status: dict[str, tuple[str, str]] = {}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def set_status(name: str, label: str, color: str) -> None:
|
|
22
|
+
"""Register or update a plugin's status badge."""
|
|
23
|
+
_status[name] = (label, color)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_all() -> dict[str, tuple[str, str]]:
|
|
27
|
+
"""Return a copy of all registered status badges (name -> (label, color))."""
|
|
28
|
+
return dict(_status)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def clear() -> None:
|
|
32
|
+
"""Remove all registered badges (primarily for tests)."""
|
|
33
|
+
_status.clear()
|
|
@@ -42,6 +42,7 @@ from ayder_cli.agents.tool import (
|
|
|
42
42
|
from ayder_cli.tui.widgets import (
|
|
43
43
|
AgentPanel,
|
|
44
44
|
ChatView,
|
|
45
|
+
ThinkingPanel,
|
|
45
46
|
ToolPanel,
|
|
46
47
|
ActivityBar,
|
|
47
48
|
CLIInputBar,
|
|
@@ -77,6 +78,8 @@ class AppCallbacks:
|
|
|
77
78
|
def on_thinking_start(self) -> None:
|
|
78
79
|
activity = self._app.query_one("#activity-bar", ActivityBar)
|
|
79
80
|
activity.set_thinking(True)
|
|
81
|
+
# Separate this reasoning phase from any earlier one in the same turn.
|
|
82
|
+
self._app.query_one("#thinking-panel", ThinkingPanel).start_phase()
|
|
80
83
|
self._app._start_activity_timer()
|
|
81
84
|
|
|
82
85
|
def on_thinking_stop(self) -> None:
|
|
@@ -90,8 +93,10 @@ class AppCallbacks:
|
|
|
90
93
|
chat_view.add_assistant_message(text)
|
|
91
94
|
|
|
92
95
|
def on_thinking_content(self, text: str) -> None:
|
|
93
|
-
|
|
94
|
-
|
|
96
|
+
# Reasoning streams silently into the dedicated panel (Ctrl+T to view),
|
|
97
|
+
# the same way tools stream into the tool panel. The ActivityBar spinner
|
|
98
|
+
# provides ambient "Thinking..." status in the meantime.
|
|
99
|
+
self._app.query_one("#thinking-panel", ThinkingPanel).add_thinking(text)
|
|
95
100
|
|
|
96
101
|
def on_token_usage(self, total_tokens: int) -> None:
|
|
97
102
|
# Show LIVE context-window fill (current / window size), not the
|
|
@@ -148,6 +153,29 @@ class AppCallbacks:
|
|
|
148
153
|
return ev is not None and ev.is_set()
|
|
149
154
|
|
|
150
155
|
|
|
156
|
+
def _enable_installed_plugin_tags(tool_tags, plugin_defs, status_names) -> frozenset | None:
|
|
157
|
+
"""Auto-enable installed plugins' capability tags at startup.
|
|
158
|
+
|
|
159
|
+
A plugin the user deliberately installed (e.g. mcp-tool) should work without a
|
|
160
|
+
manual /plugin toggle, so its capability tags are merged into the effective
|
|
161
|
+
``tool_tags``. Tags come from loaded plugin tool definitions and from any
|
|
162
|
+
status badge a plugin published (so an installed-but-disconnected plugin like
|
|
163
|
+
mcp-tool is still enabled). Builtin/optional tags stay governed by config.
|
|
164
|
+
|
|
165
|
+
``tool_tags`` of ``None`` means "no filter / everything enabled" — left as-is.
|
|
166
|
+
"""
|
|
167
|
+
if tool_tags is None:
|
|
168
|
+
return None
|
|
169
|
+
extra = {
|
|
170
|
+
tag
|
|
171
|
+
for td in plugin_defs
|
|
172
|
+
for tag in td.tags
|
|
173
|
+
if tag not in ("core", "metadata")
|
|
174
|
+
}
|
|
175
|
+
extra.update(name for name in status_names if name not in ("core", "metadata"))
|
|
176
|
+
return frozenset(tool_tags | extra) if extra else tool_tags
|
|
177
|
+
|
|
178
|
+
|
|
151
179
|
class AyderApp(App):
|
|
152
180
|
"""
|
|
153
181
|
Main CLI-style application for ayder-cli.
|
|
@@ -340,6 +368,13 @@ class AyderApp(App):
|
|
|
340
368
|
raw_tags = getattr(self.config, "tool_tags", ["core", "metadata"])
|
|
341
369
|
tool_tags_list = list(raw_tags) if raw_tags is not None else []
|
|
342
370
|
tool_tags = frozenset(tool_tags_list) if tool_tags_list else None
|
|
371
|
+
# Auto-enable installed plugins so they work without a manual /plugin toggle.
|
|
372
|
+
from ayder_cli.tools import plugin_status
|
|
373
|
+
from ayder_cli.tools.definition import _GLOBAL_PLUGIN_DEFS
|
|
374
|
+
|
|
375
|
+
tool_tags = _enable_installed_plugin_tags(
|
|
376
|
+
tool_tags, _GLOBAL_PLUGIN_DEFS, plugin_status.get_all().keys()
|
|
377
|
+
)
|
|
343
378
|
self.chat_loop = ChatLoop(
|
|
344
379
|
llm=self.llm,
|
|
345
380
|
registry=self.registry,
|
|
@@ -352,7 +387,7 @@ class AyderApp(App):
|
|
|
352
387
|
stop_sequences=stop_sequences,
|
|
353
388
|
permissions=self.permissions,
|
|
354
389
|
tool_tags=tool_tags,
|
|
355
|
-
max_history=getattr(self.config, 'max_history_messages',
|
|
390
|
+
max_history=getattr(self.config, 'max_history_messages', 0),
|
|
356
391
|
),
|
|
357
392
|
callbacks=self._callbacks,
|
|
358
393
|
context_manager=self.context_manager,
|
|
@@ -433,11 +468,19 @@ class AyderApp(App):
|
|
|
433
468
|
self._active_skill = skill_name
|
|
434
469
|
|
|
435
470
|
def _animate_activity(self) -> None:
|
|
436
|
-
"""Animate spinners in the activity bar and tool panel.
|
|
471
|
+
"""Animate spinners in the activity bar and tool panel.
|
|
472
|
+
|
|
473
|
+
Both updates are wrapped in a single ``batch_update`` so the 10fps tick
|
|
474
|
+
produces one repaint instead of two, and the tool panel is skipped when
|
|
475
|
+
it is empty — under load (streaming + tools + agents) this keeps the
|
|
476
|
+
per-tick redraw cost (and the flicker that comes with it) down.
|
|
477
|
+
"""
|
|
437
478
|
activity = self.query_one("#activity-bar", ActivityBar)
|
|
438
|
-
activity.update_spinners()
|
|
439
479
|
tool_panel = self.query_one("#tool-panel", ToolPanel)
|
|
440
|
-
|
|
480
|
+
with self.batch_update():
|
|
481
|
+
activity.update_spinners()
|
|
482
|
+
if tool_panel._tools:
|
|
483
|
+
tool_panel.update_spinners()
|
|
441
484
|
|
|
442
485
|
def _start_activity_timer(self) -> None:
|
|
443
486
|
"""Start the shared activity animation timer if not running."""
|
|
@@ -570,6 +613,7 @@ class AyderApp(App):
|
|
|
570
613
|
"""Compose the UI layout - terminal style with scrolling content."""
|
|
571
614
|
yield ChatView(id="chat-view")
|
|
572
615
|
yield ToolPanel(id="tool-panel")
|
|
616
|
+
yield ThinkingPanel(id="thinking-panel")
|
|
573
617
|
yield AgentPanel(id="agent-panel")
|
|
574
618
|
yield ActivityBar(id="activity-bar")
|
|
575
619
|
yield CLIInputBar(commands=self.commands, id="input-bar")
|
|
@@ -716,6 +760,8 @@ class AyderApp(App):
|
|
|
716
760
|
return
|
|
717
761
|
try:
|
|
718
762
|
self.query_one("#chat-view", ChatView).add_user_message(user_input) # echo now
|
|
763
|
+
# Start each turn's reasoning fresh in the thinking panel.
|
|
764
|
+
self.query_one("#thinking-panel", ThinkingPanel).clear()
|
|
719
765
|
except Exception:
|
|
720
766
|
pass
|
|
721
767
|
|
|
@@ -789,12 +835,13 @@ class AyderApp(App):
|
|
|
789
835
|
chat_view.add_system_message(f"Tool panel {state} (Ctrl+O to toggle)")
|
|
790
836
|
|
|
791
837
|
def action_toggle_thinking(self) -> None:
|
|
792
|
-
"""Toggle thinking/reasoning
|
|
793
|
-
|
|
838
|
+
"""Toggle the thinking/reasoning panel (mirrors Ctrl+O / Ctrl+G)."""
|
|
839
|
+
thinking_panel = self.query_one("#thinking-panel", ThinkingPanel)
|
|
840
|
+
visible = thinking_panel.toggle()
|
|
841
|
+
self._show_thinking = visible
|
|
794
842
|
chat_view = self.query_one("#chat-view", ChatView)
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
chat_view.add_system_message(f"Thinking blocks {state} (Ctrl+T to toggle)")
|
|
843
|
+
state = "visible" if visible else "hidden"
|
|
844
|
+
chat_view.add_system_message(f"Thinking panel {state} (Ctrl+T to toggle)")
|
|
798
845
|
|
|
799
846
|
def action_toggle_agents(self) -> None:
|
|
800
847
|
"""Toggle the agent panel."""
|
|
@@ -809,41 +856,48 @@ class AyderApp(App):
|
|
|
809
856
|
self.push_screen(CLIHelpScreen())
|
|
810
857
|
|
|
811
858
|
def action_scroll_chat_up(self) -> None:
|
|
812
|
-
"""PageUp: scroll
|
|
813
|
-
if self.
|
|
859
|
+
"""PageUp: scroll an open panel (agents/thinking) if any, else the chat view."""
|
|
860
|
+
if self._scroll_open_panel("page_up"):
|
|
814
861
|
return
|
|
815
862
|
chat_view = self.query_one("#chat-view", ChatView)
|
|
816
863
|
chat_view.disable_follow_mode()
|
|
817
864
|
chat_view.scroll_page_up(animate=False)
|
|
818
865
|
|
|
819
866
|
def action_scroll_chat_down(self) -> None:
|
|
820
|
-
"""PageDown: scroll
|
|
821
|
-
if self.
|
|
867
|
+
"""PageDown: scroll an open panel (agents/thinking) if any, else the chat view."""
|
|
868
|
+
if self._scroll_open_panel("page_down"):
|
|
822
869
|
return
|
|
823
870
|
chat_view = self.query_one("#chat-view", ChatView)
|
|
824
871
|
chat_view.scroll_page_down(animate=False)
|
|
825
872
|
if chat_view.scroll_offset.y >= chat_view.max_scroll_y:
|
|
826
873
|
chat_view.enable_follow_mode()
|
|
827
874
|
|
|
828
|
-
def
|
|
829
|
-
"""Scroll
|
|
875
|
+
def _scroll_open_panel(self, motion: str) -> bool:
|
|
876
|
+
"""Scroll a visible scrollable panel — the agents (Ctrl+G) or thinking
|
|
877
|
+
(Ctrl+T) panel — instead of the chat view.
|
|
830
878
|
|
|
831
879
|
``motion`` is a Widget.scroll_* suffix ('page_up', 'page_down', 'up',
|
|
832
|
-
'down', 'home', 'end'). Returns True when
|
|
833
|
-
|
|
880
|
+
'down', 'home', 'end'). Returns True when a panel handled the scroll so
|
|
881
|
+
the caller leaves the chat view alone; False otherwise (none open).
|
|
834
882
|
"""
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
883
|
+
for panel_id, panel_cls in (
|
|
884
|
+
("#agent-panel", AgentPanel),
|
|
885
|
+
("#thinking-panel", ThinkingPanel),
|
|
886
|
+
):
|
|
887
|
+
try:
|
|
888
|
+
panel = self.query_one(panel_id, panel_cls)
|
|
889
|
+
except Exception:
|
|
890
|
+
continue
|
|
891
|
+
if not panel.display:
|
|
892
|
+
continue
|
|
893
|
+
getattr(panel, f"scroll_{motion}")(animate=False)
|
|
894
|
+
return True
|
|
895
|
+
return False
|
|
843
896
|
|
|
844
897
|
def action_clear(self) -> None:
|
|
845
898
|
"""Clear chat history."""
|
|
846
899
|
chat_view = self.query_one("#chat-view", ChatView)
|
|
900
|
+
self.query_one("#thinking-panel", ThinkingPanel).clear()
|
|
847
901
|
do_clear(self, chat_view)
|
|
848
902
|
|
|
849
903
|
def on_app_focus(self) -> None:
|
|
@@ -988,18 +988,46 @@ def handle_context_stats(app: AyderApp, args: str, chat_view: ChatView) -> None:
|
|
|
988
988
|
|
|
989
989
|
|
|
990
990
|
|
|
991
|
+
def _available_plugin_tags(
|
|
992
|
+
tool_definitions, statuses: dict[str, tuple[str, str]]
|
|
993
|
+
) -> list[str]:
|
|
994
|
+
"""Capability tags to offer in /plugin.
|
|
995
|
+
|
|
996
|
+
Combines tags from currently-loaded tools with tags for installed plugins
|
|
997
|
+
that published a status badge but contribute no live tools right now (e.g.
|
|
998
|
+
the mcp-tool plugin when its MCP server is configured but unreachable) — so
|
|
999
|
+
an installed plugin stays visible/toggleable regardless of live state. The
|
|
1000
|
+
always-on ``core``/``metadata`` tags are excluded.
|
|
1001
|
+
"""
|
|
1002
|
+
tags = {
|
|
1003
|
+
tag
|
|
1004
|
+
for td in tool_definitions
|
|
1005
|
+
for tag in td.tags
|
|
1006
|
+
if tag not in ("core", "metadata")
|
|
1007
|
+
}
|
|
1008
|
+
tags.update(name for name in statuses if name not in ("core", "metadata"))
|
|
1009
|
+
return sorted(tags)
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
def _refresh_status_badges(app: "AyderApp") -> None:
|
|
1013
|
+
"""Re-render status-bar plugin badges after the enabled tags change, so a
|
|
1014
|
+
disabled plugin (e.g. mcp) greys out and a re-enabled one regains its colour."""
|
|
1015
|
+
try:
|
|
1016
|
+
from ayder_cli.tui.widgets import StatusBar
|
|
1017
|
+
|
|
1018
|
+
app.query_one("#status-bar", StatusBar).refresh_plugin_badges()
|
|
1019
|
+
except Exception:
|
|
1020
|
+
pass
|
|
1021
|
+
|
|
1022
|
+
|
|
991
1023
|
def handle_plugin(app: "AyderApp", args: str, chat_view: ChatView) -> None:
|
|
992
1024
|
"""Toggle dynamic tool plugins (e.g. venv, http, background, temporal)."""
|
|
993
1025
|
from ayder_cli.tools.definition import TOOL_DEFINITIONS
|
|
1026
|
+
from ayder_cli.tools import plugin_status
|
|
994
1027
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
for tag in td.tags:
|
|
999
|
-
if tag not in ("core", "metadata"):
|
|
1000
|
-
available_plugins.add(tag)
|
|
1001
|
-
|
|
1002
|
-
available_plugins_list = sorted(list(available_plugins))
|
|
1028
|
+
available_plugins_list = _available_plugin_tags(
|
|
1029
|
+
TOOL_DEFINITIONS, plugin_status.get_all()
|
|
1030
|
+
)
|
|
1003
1031
|
|
|
1004
1032
|
# Read current tags for the (eager) picker display; the MUTATION of
|
|
1005
1033
|
# chat_loop.config.tool_tags is deferred into prepare (run_loop=False).
|
|
@@ -1026,6 +1054,7 @@ def handle_plugin(app: "AyderApp", args: str, chat_view: ChatView) -> None:
|
|
|
1026
1054
|
tags.add(plugin_name)
|
|
1027
1055
|
status = "enabled"
|
|
1028
1056
|
app.chat_loop.config.tool_tags = frozenset(tags)
|
|
1057
|
+
_refresh_status_badges(app)
|
|
1029
1058
|
chat_view.add_system_message(f"Plugin '{plugin_name}' {status}.")
|
|
1030
1059
|
|
|
1031
1060
|
app.request_turn(prepare=_prepare, run_loop=False)
|
|
@@ -1048,6 +1077,7 @@ def handle_plugin(app: "AyderApp", args: str, chat_view: ChatView) -> None:
|
|
|
1048
1077
|
base_tags = {"core", "metadata"}
|
|
1049
1078
|
new_tags = base_tags | result
|
|
1050
1079
|
app.chat_loop.config.tool_tags = frozenset(new_tags)
|
|
1080
|
+
_refresh_status_badges(app)
|
|
1051
1081
|
enabled = sorted(result)
|
|
1052
1082
|
chat_view.add_system_message(
|
|
1053
1083
|
f"Plugins updated: {', '.join(enabled) if enabled else 'none enabled'}"
|
|
@@ -13,12 +13,12 @@ KEYBINDING_REGISTRY: list[tuple[str, str, str]] = [
|
|
|
13
13
|
("Home/End", "Top/Bottom of chat", "Navigation"),
|
|
14
14
|
# Chat
|
|
15
15
|
("Enter", "Send message", "Chat"),
|
|
16
|
-
("Shift+Enter", "New line", "Chat"),
|
|
17
|
-
("\u2191/\u2193", "
|
|
16
|
+
("Shift+Enter / Ctrl+J", "New line", "Chat"),
|
|
17
|
+
("\u2191/\u2193", "History (at first/last char) / move line", "Chat"),
|
|
18
18
|
("Tab", "Complete /command", "Chat"),
|
|
19
19
|
# Panels
|
|
20
20
|
("Ctrl+O", "Toggle Tools panel", "Panels"),
|
|
21
|
-
("Ctrl+T", "Toggle Thinking", "Panels"),
|
|
21
|
+
("Ctrl+T", "Toggle Thinking panel", "Panels"),
|
|
22
22
|
("Ctrl+G", "Toggle Agents panel", "Panels"),
|
|
23
23
|
# General
|
|
24
24
|
("Ctrl+L", "Clear chat", "General"),
|