ayder-cli 2.5.2__tar.gz → 2.5.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.
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/PKG-INFO +2 -1
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/README.md +1 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/docs/GATE.md +1 -1
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/docs/PROJECT_STRUCTURE.md +8 -6
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/pyproject.toml +1 -1
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/prompts.py +1 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/themes/ayder.py +17 -25
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/themes/claude.py +17 -25
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tui/__init__.py +4 -3
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tui/app.py +21 -23
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tui/screens.py +6 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tui/widgets.py +123 -17
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/test_prompts_environment.py +81 -1
- ayder_cli-2.5.3/tests/tui/test_follow_mode.py +132 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tui/test_help_screen.py +26 -0
- ayder_cli-2.5.3/tests/tui/test_inline_sync_output.py +77 -0
- ayder_cli-2.5.3/tests/tui/test_mouse_support.py +20 -0
- ayder_cli-2.5.3/tests/tui/test_panel_layout.py +490 -0
- ayder_cli-2.5.3/tests/tui/test_real_turn_follow.py +260 -0
- ayder_cli-2.5.3/tests/tui/test_status_bar_cwd.py +185 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/uv.lock +1 -1
- ayder_cli-2.5.2/tests/tui/test_follow_mode.py +0 -38
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/.github/workflows/ci.yml +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/.github/workflows/python-package.yml +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/.gitignore +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/.python-version +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/AGENTS.md +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/LICENSE +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/docs/README_AGENT_WORKFLOW.md +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/docs/README_OLLAMA.md +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/docs/cc.png +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/docs/config.toml.example +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/scripts/check_exception_handlers.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/scripts/check_log_arguments.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/scripts/check_logging_imports.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/scripts/exception_baseline.txt +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/scripts/find_percent_logging.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/scripts/log_argument_baseline.txt +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/scripts/logging_gates.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/__init__.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/__main__.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/agents/__init__.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/agents/callbacks.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/agents/config.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/agents/registry.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/agents/run.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/agents/runner.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/agents/tool.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/agents/worktree.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/application/README.md +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/application/__init__.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/application/execution_policy.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/application/message_contract.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/application/runtime_factory.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/application/validation.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/cli.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/cli_callbacks.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/cli_runner.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/console.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/core/cache_monitor.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/core/config.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/core/config_migration.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/core/context.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/core/context_manager.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/core/context_manager_factory.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/core/default_context_manager.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/core/ollama_context_manager.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/core/reasoning.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/core/result.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/core/session.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/core/skills.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/diagnostics.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/log.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/logging_config.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/loops/__init__.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/loops/chat_loop.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/masking.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/parser.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/process_manager.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/__init__.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/base.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/evren.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/impl/claude.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/impl/deepseek.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/impl/gemini.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/impl/glm.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/impl/ollama.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/impl/ollama_drivers/__init__.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/impl/ollama_drivers/_errors.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/impl/ollama_drivers/base.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/impl/ollama_drivers/deepseek.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/impl/ollama_drivers/generic_native.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/impl/ollama_drivers/generic_xml.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/impl/ollama_drivers/matrix.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/impl/ollama_drivers/minimax.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/impl/ollama_drivers/qwen3.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/impl/ollama_drivers/registry.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/impl/ollama_inspector.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/impl/openai.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/impl/qwen.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/orchestrator.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/providers/retry.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/services/__init__.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/services/interactions.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/services/messaging_inbox.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/skills/development-workflow/SKILL.md +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/themes/__init__.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/__init__.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/__init__.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/background_process_tool.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/context.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/context_definitions.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/filesystem.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/filesystem_definitions.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/note_tool.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/notes.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/notes_definitions.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/process_manager_definitions.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/search.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/search_definitions.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/shell.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/shell_definitions.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/skill.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/skill_definitions.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/task_definitions.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/task_tool.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/tasks.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/utils_tools.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/utils_tools_definitions.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/web.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/builtins/web_definitions.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/definition.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/execution.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/hooks.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/mcp.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/mcp_client.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/normalization.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/plugin_api.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/plugin_github.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/plugin_manager.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/plugin_status.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/registry.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/schemas.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tools/utils.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tui/adapter.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tui/commands.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tui/helpers.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tui/keybindings.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tui/parser.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tui/rendering.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tui/theme_manager.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/tui/types.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/ui.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/src/ayder_cli/version.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/COVERAGE.md +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/__init__.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/agents/__init__.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/agents/test_agent_consolidated.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/agents/test_agent_severity.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/agents/test_agent_tools.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/agents/test_callbacks.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/agents/test_config.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/agents/test_integration.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/agents/test_queue.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/agents/test_registry.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/agents/test_run.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/agents/test_runner.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/agents/test_runner_provider_unavailable.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/agents/test_tool.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/agents/test_worktree_helpers.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/agents/test_worktree_isolation.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/application/test_argument_canonicalization.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/application/test_execution_result_truthfulness.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/application/test_message_contract.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/application/test_runtime_factory.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/client/test_main.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/conftest.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/convergence/__init__.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/convergence/test_execution_policy_parity.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/convergence/test_runtime_wiring.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/convergence/test_validation_path.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_asyncio_bridge.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_cache_monitor.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_config.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_config_coverage.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_config_migration.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_config_provider_logging.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_config_retry.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_config_skills.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_config_v2.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_context_manager_factory.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_default_config_evren.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_default_context_manager.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_default_context_manager_should_compact.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_default_context_newest_unit.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_default_context_skill_resupply.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_event_emission.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_event_stream.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_exception_guarantee.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_exception_hooks.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_log_facade.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_logging_config_keys.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_logging_sinks.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_loguru_caplog.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_max_output_tokens_default.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_ollama_compaction_persistence.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_ollama_context_manager.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_ollama_context_manager_cache_monitor.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_ollama_context_manager_max_history.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_ollama_skill_resupply.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_parameter_aliasing.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_parser.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_reasoning_config.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_scratch_dir.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_session.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_session_id_threading.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_sink_masking.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_skills.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/core/test_skills_builtin_sync.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/loops/__init__.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/loops/test_base.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/loops/test_chat_loop_guards.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/loops/test_chat_loop_hook.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/loops/test_chat_loop_tool_truncation.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/loops/test_loop_severity.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/loops/test_required_args_aliases.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/loops/test_tool_argument_integrity.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/loops/test_tool_batch_cancellation.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/loops/test_tool_batch_ordering.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/__init__.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/ollama_drivers/__init__.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/ollama_drivers/test_base.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/ollama_drivers/test_deepseek.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/ollama_drivers/test_errors.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/ollama_drivers/test_fallback.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/ollama_drivers/test_generic_native.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/ollama_drivers/test_generic_xml.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/ollama_drivers/test_matrix.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/ollama_drivers/test_minimax.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/ollama_drivers/test_provider_integration.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/ollama_drivers/test_qwen3.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/ollama_drivers/test_registry.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/test_claude_stream_tool_calls.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/test_evren.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/test_gemini_function_calls.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/test_glm_async.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/test_ollama_inspector.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/test_ollama_native.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/test_ollama_parallel_tool_call_ids.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/test_ollama_xml.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/test_ollama_xml_autoroute.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/test_ollama_xml_usage.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/test_openai_list_models.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/test_orchestrator_capabilities.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/test_provider_channel_binding.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/test_provider_severity.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/test_qwen_async.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/test_reasoning_effort.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/test_retry_classifier.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/test_retry_integration.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/test_retrying_provider.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/providers/test_skill_load_prefix.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/scripts/test_exception_gate.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/scripts/test_log_argument_gate.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/scripts/test_logging_policy_gates.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/scripts/test_loguru_private_shape.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/services/test_boundary.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/services/test_interaction_sink.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/services/test_messaging_inbox.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/test_agents_md.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/test_cli.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/test_cli_callbacks.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/test_context.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/test_env_manager.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/test_logging_config.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/test_notes.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/test_process_manager.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/test_prompts_general.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/test_tasks.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/__init__.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_agent_notes.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_background_process_tool.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_bash_process_safety.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_bash_tool.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_context_storage_confinement.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_definition_discovery.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_di_injection.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_error_labels.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_file_editor_hardening.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_file_editor_size_limit.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_file_tool_output.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_impl.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_impl_coverage.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_mcp.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_mcp_client.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_note_tool.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_notes_api.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_notes_confinement.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_parallel_safe.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_path_security.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_plugin_api.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_plugin_github.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_plugin_loading.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_plugin_manager.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_plugin_status.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_registry.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_registry_coverage.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_result.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_schemas.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_search_codebase.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_search_hardening.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_skill_catalog_and_filter.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_skill_scope_wiring.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_skill_tool.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_task_integrity.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_task_path_confinement.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_task_tool.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_tool_severity.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_unexpected_args.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_utils.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tools/test_web.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tui/__init__.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tui/test_agent_list_screen.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tui/test_do_clear_coordination.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tui/test_evren_terms.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tui/test_input_box.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tui/test_keybindings.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tui/test_multi_select_screen.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tui/test_multiline_input.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tui/test_paste.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tui/test_peer_message_turn.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tui/test_pending_compact_consumer.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tui/test_pull_delivery.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tui/test_resume.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tui/test_status_bar_badges.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tui/test_themes.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tui/test_turn_consumer.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/tui/test_widgets.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/ui/test_confirm_screen.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/ui/test_diff_preview.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/ui/test_plugin_autoenable.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/ui/test_provider_switch.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/ui/test_run_tui_startup.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/ui/test_tui_assistant_sanitization.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/ui/test_tui_chat_loop.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/ui/test_tui_commands_agent.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/ui/test_tui_commands_effort.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/ui/test_tui_commands_logging.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/ui/test_tui_commands_plugin.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/ui/test_tui_commands_provider.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/ui/test_tui_commands_skill.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/ui/test_tui_helpers.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/ui/test_tui_system_prompt_no_structure.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/ui/test_tui_temporal_command.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/ui/test_tui_widgets.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/ui/test_ui.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/ui/test_ui_coverage.py +0 -0
- {ayder_cli-2.5.2 → ayder_cli-2.5.3}/tests/ui/test_ui_verbose.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ayder-cli
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.3
|
|
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
|
|
@@ -535,6 +535,7 @@ inside the Ollama summary.
|
|
|
535
535
|
| `Ctrl+L` | Clear chat |
|
|
536
536
|
| `Ctrl+O` | Toggle tool panel |
|
|
537
537
|
| `Ctrl+T` | Toggle thinking/reasoning blocks |
|
|
538
|
+
| `Shift` + drag | Select text with the mouse (Option in iTerm2 and Terminal.app) |
|
|
538
539
|
| `PageUp` / `PageDown` | Scroll chat view |
|
|
539
540
|
| `Tab` | Auto-complete slash commands |
|
|
540
541
|
|
|
@@ -492,6 +492,7 @@ inside the Ollama summary.
|
|
|
492
492
|
| `Ctrl+L` | Clear chat |
|
|
493
493
|
| `Ctrl+O` | Toggle tool panel |
|
|
494
494
|
| `Ctrl+T` | Toggle thinking/reasoning blocks |
|
|
495
|
+
| `Shift` + drag | Select text with the mouse (Option in iTerm2 and Terminal.app) |
|
|
495
496
|
| `PageUp` / `PageDown` | Scroll chat view |
|
|
496
497
|
| `Tab` | Auto-complete slash commands |
|
|
497
498
|
|
|
@@ -12,7 +12,7 @@ Input: the candidate SHA.
|
|
|
12
12
|
gates and the test suite, and stops at the first failing task.
|
|
13
13
|
4. Copy the final pytest summary line (`=== N passed, M skipped in …s ===`) verbatim.
|
|
14
14
|
|
|
15
|
-
Last measured: `
|
|
15
|
+
Last measured: `3026 passed, 2 skipped` on `feat/tui-tweaks` (2026-11-13, release 2.5.3).
|
|
16
16
|
|
|
17
17
|
## Report format
|
|
18
18
|
|
|
@@ -329,11 +329,13 @@ AyderApp (Textual App)
|
|
|
329
329
|
├── CSS (from theme_manager.py)
|
|
330
330
|
├── Compose:
|
|
331
331
|
│ ├── Banner (Static)
|
|
332
|
+
│ ├── ToolPanel (custom widget) ← Tool calls; hidden until Ctrl+O, opens at the top (fixed 10% height)
|
|
333
|
+
│ ├── ThinkingPanel (custom widget) ← Reasoning stream; Ctrl+T, opens at the top (fixed 30% height)
|
|
334
|
+
│ ├── AgentPanel (custom widget) ← Agent runs; Ctrl+G, opens at the top (fixed 10% height)
|
|
332
335
|
│ ├── ChatView (custom widget) ← Messages displayed here
|
|
333
|
-
│ ├── ToolPanel (custom widget) ← Tool calls shown here
|
|
334
336
|
│ ├── ActivityBar (custom widget) ← Status indicators
|
|
335
337
|
│ ├── CLIInputBar (custom widget) ← User input
|
|
336
|
-
│ └── StatusBar (custom widget) ←
|
|
338
|
+
│ └── StatusBar (custom widget) ← Working directory (~ for home, shortened from the left), model, effort, mode, context usage, files
|
|
337
339
|
│
|
|
338
340
|
├── Screens (Modal):
|
|
339
341
|
│ ├── CLIConfirmScreen ← Tool confirmation with diff
|
|
@@ -574,15 +576,15 @@ The TUI module provides a clean, terminal-like interface with:
|
|
|
574
576
|
- Simple text output with prefixes
|
|
575
577
|
- Minimal borders and chrome design
|
|
576
578
|
- Slash command auto-completion
|
|
577
|
-
- Status bar with contextual information
|
|
578
|
-
-
|
|
579
|
+
- Status bar with contextual information, starting with the working directory (home shown as `~`; a long path is shortened from the left to at most a third of the bar, never below `…/<last component>`, regardless of the other items, which may overflow the bar's right edge)
|
|
580
|
+
- Keyboard-first operation, with mouse support enabled
|
|
579
581
|
|
|
580
582
|
It exposes key components like AyderApp, widgets (ChatView, ToolPanel), and screens (CLIConfirmScreen) for building the terminal interface.
|
|
581
583
|
|
|
582
584
|
### TUI Application File Summary (src/ayder_cli/tui/app.py)
|
|
583
585
|
|
|
584
586
|
AyderApp is the main Textual application implementing a chat-style interface:
|
|
585
|
-
- **Layout**:
|
|
587
|
+
- **Layout**: ToolPanel, ThinkingPanel and AgentPanel (hidden by default; they open at the top, above the chat, at a fixed 10% / 30% / 10% of the screen height with their content scrolling inside, and toggling prints no chat message), ChatView, ActivityBar, CLIInputBar, StatusBar
|
|
586
588
|
- **Keybindings**: Ctrl+Q (quit), Ctrl+C/X (cancel), Ctrl+L (clear), Ctrl+O (toggle tools)
|
|
587
589
|
- **Features**:
|
|
588
590
|
- Async processing with worker threads
|
|
@@ -594,7 +596,7 @@ AyderApp is the main Textual application implementing a chat-style interface:
|
|
|
594
596
|
The application manages:
|
|
595
597
|
- LLM provider setup and configuration
|
|
596
598
|
- Tool registry initialization with middleware
|
|
597
|
-
- System prompt construction (base prompt + environment section + tool prompts + skill catalog; no project tree). The environment section (`prompts.build_environment_prompt`) gives the absolute working directory and the session start date and time (ISO 8601, local time with UTC offset). It is built once per session and reused on `/model` rebuilds. A resumed session (`--resume`) restores the provider it last used (the session file stores it; a provider no longer configured falls back to the default with a warning) and its `--system-prompt` text, keeps its history, and rebuilds the system prompt for the current session, so the scratch directory, start time, tool prompts and agent capability prompts are current; `/model` rebuilds keep the capability prompts.
|
|
599
|
+
- System prompt construction (base prompt + environment section + tool prompts + skill catalog; no project tree). The environment section (`prompts.build_environment_prompt`) gives the absolute working directory and the session start date and time (ISO 8601, local time with UTC offset). It also tells the model that replies render as Markdown (paragraphs separated by a blank line, no fixed-width wrapping). It is built once per session and reused on `/model` rebuilds. A resumed session (`--resume`) restores the provider it last used (the session file stores it; a provider no longer configured falls back to the default with a warning) and its `--system-prompt` text, keeps its history, and rebuilds the system prompt for the current session, so the scratch directory, start time, tool prompts and agent capability prompts are current; `/model` rebuilds keep the capability prompts.
|
|
598
600
|
- Process manager for background operations
|
|
599
601
|
|
|
600
602
|
### Chat Loop Summary (src/ayder_cli/loops/chat_loop.py)
|
|
@@ -57,6 +57,7 @@ Session start: {started_at}{scratch_line}
|
|
|
57
57
|
Your workspace is {workspace}.
|
|
58
58
|
Outside it, read only paths the user names or the task requires, and create,
|
|
59
59
|
modify or initialize nothing (including `git init`) without the user's consent.{scratch_rule}
|
|
60
|
+
Replies are rendered as Markdown. Separate paragraphs with a blank line; do not wrap lines at a fixed width.
|
|
60
61
|
"""
|
|
61
62
|
|
|
62
63
|
# REASON: Models put temporary files in /tmp by habit. A per-session directory
|
|
@@ -659,16 +659,15 @@ BannerWidget .banner-version {
|
|
|
659
659
|
color: $success;
|
|
660
660
|
}
|
|
661
661
|
|
|
662
|
-
/* ToolPanel - The panel that
|
|
663
|
-
|
|
662
|
+
/* ToolPanel - The panel that opens at the top of the screen, above the chat view, showing running tools.
|
|
663
|
+
Fixed at 10% of the screen height from the moment it opens, whatever it holds;
|
|
664
|
+
its content scrolls inside. Targets 'ToolPanel' in widgets.py. */
|
|
664
665
|
ToolPanel {
|
|
665
666
|
layout: vertical;
|
|
666
|
-
height:
|
|
667
|
-
min-height: 6;
|
|
668
|
-
max-height: 24%;
|
|
667
|
+
height: 10%;
|
|
669
668
|
background: $background;
|
|
670
|
-
border-
|
|
671
|
-
padding:
|
|
669
|
+
border-bottom: solid ansi_bright_black;
|
|
670
|
+
padding: 0 1;
|
|
672
671
|
overflow-y: scroll;
|
|
673
672
|
display: none;
|
|
674
673
|
}
|
|
@@ -688,14 +687,14 @@ ToolPanel .tool-item.completed {
|
|
|
688
687
|
}
|
|
689
688
|
|
|
690
689
|
/* AgentPanel - Scrollable panel for agent run history.
|
|
691
|
-
Toggled with Ctrl+G.
|
|
690
|
+
Toggled with Ctrl+G. Fixed at 10% of the screen height; content scrolls inside.
|
|
691
|
+
Targets 'AgentPanel' in widgets.py. */
|
|
692
692
|
AgentPanel {
|
|
693
693
|
layout: vertical;
|
|
694
|
-
height:
|
|
695
|
-
max-height: 40%;
|
|
694
|
+
height: 10%;
|
|
696
695
|
background: $background;
|
|
697
|
-
border-
|
|
698
|
-
padding:
|
|
696
|
+
border-bottom: solid ansi_bright_black;
|
|
697
|
+
padding: 0 1;
|
|
699
698
|
overflow-y: scroll;
|
|
700
699
|
display: none;
|
|
701
700
|
}
|
|
@@ -734,15 +733,15 @@ AgentPanel .agent-detail {
|
|
|
734
733
|
}
|
|
735
734
|
|
|
736
735
|
/* ThinkingPanel - Scrollable panel showing the model's reasoning stream.
|
|
737
|
-
Toggled with Ctrl+T.
|
|
738
|
-
long-form
|
|
736
|
+
Toggled with Ctrl+T. Fixed at 30% of the screen height, three times the
|
|
737
|
+
tool/agent panels, because reasoning is long-form; content scrolls inside.
|
|
738
|
+
Targets 'ThinkingPanel' in widgets.py. */
|
|
739
739
|
ThinkingPanel {
|
|
740
740
|
layout: vertical;
|
|
741
|
-
height:
|
|
742
|
-
max-height: 60%;
|
|
741
|
+
height: 30%;
|
|
743
742
|
background: $background;
|
|
744
|
-
border-
|
|
745
|
-
padding:
|
|
743
|
+
border-bottom: solid ansi_bright_black;
|
|
744
|
+
padding: 0 1;
|
|
746
745
|
overflow-y: scroll;
|
|
747
746
|
display: none;
|
|
748
747
|
}
|
|
@@ -778,21 +777,14 @@ Screen:inline {
|
|
|
778
777
|
}
|
|
779
778
|
|
|
780
779
|
#tool-panel {
|
|
781
|
-
height: auto;
|
|
782
|
-
min-height: 0;
|
|
783
|
-
max-height: 20%;
|
|
784
780
|
width: 100%;
|
|
785
781
|
}
|
|
786
782
|
|
|
787
783
|
#agent-panel {
|
|
788
|
-
min-height: 0;
|
|
789
|
-
max-height: 40%;
|
|
790
784
|
width: 100%;
|
|
791
785
|
}
|
|
792
786
|
|
|
793
787
|
#thinking-panel {
|
|
794
|
-
min-height: 0;
|
|
795
|
-
max-height: 60%;
|
|
796
788
|
width: 100%;
|
|
797
789
|
}
|
|
798
790
|
|
|
@@ -596,16 +596,15 @@ BannerWidget .banner-version {
|
|
|
596
596
|
color: #5cb870;
|
|
597
597
|
}
|
|
598
598
|
|
|
599
|
-
/* ToolPanel - The panel that
|
|
600
|
-
|
|
599
|
+
/* ToolPanel - The panel that opens at the top of the screen, above the chat view, showing running tools.
|
|
600
|
+
Fixed at 10% of the screen height from the moment it opens, whatever it holds;
|
|
601
|
+
its content scrolls inside. Targets 'ToolPanel' in widgets.py. */
|
|
601
602
|
ToolPanel {
|
|
602
603
|
layout: vertical;
|
|
603
|
-
height:
|
|
604
|
-
min-height: 6;
|
|
605
|
-
max-height: 24%;
|
|
604
|
+
height: 10%;
|
|
606
605
|
background: #0a0a18;
|
|
607
|
-
border-
|
|
608
|
-
padding:
|
|
606
|
+
border-bottom: solid #333350;
|
|
607
|
+
padding: 0 1;
|
|
609
608
|
overflow-y: scroll;
|
|
610
609
|
display: none;
|
|
611
610
|
}
|
|
@@ -625,14 +624,14 @@ ToolPanel .tool-item.completed {
|
|
|
625
624
|
}
|
|
626
625
|
|
|
627
626
|
/* AgentPanel - Scrollable panel for agent run history.
|
|
628
|
-
Toggled with Ctrl+G.
|
|
627
|
+
Toggled with Ctrl+G. Fixed at 10% of the screen height; content scrolls inside.
|
|
628
|
+
Targets 'AgentPanel' in widgets.py. */
|
|
629
629
|
AgentPanel {
|
|
630
630
|
layout: vertical;
|
|
631
|
-
height:
|
|
632
|
-
max-height: 40%;
|
|
631
|
+
height: 10%;
|
|
633
632
|
background: #0a0a18;
|
|
634
|
-
border-
|
|
635
|
-
padding:
|
|
633
|
+
border-bottom: solid #333350;
|
|
634
|
+
padding: 0 1;
|
|
636
635
|
overflow-y: scroll;
|
|
637
636
|
display: none;
|
|
638
637
|
}
|
|
@@ -671,15 +670,15 @@ AgentPanel .agent-detail {
|
|
|
671
670
|
}
|
|
672
671
|
|
|
673
672
|
/* ThinkingPanel - Scrollable panel showing the model's reasoning stream.
|
|
674
|
-
Toggled with Ctrl+T.
|
|
675
|
-
long-form
|
|
673
|
+
Toggled with Ctrl+T. Fixed at 30% of the screen height, three times the
|
|
674
|
+
tool/agent panels, because reasoning is long-form; content scrolls inside.
|
|
675
|
+
Targets 'ThinkingPanel' in widgets.py. */
|
|
676
676
|
ThinkingPanel {
|
|
677
677
|
layout: vertical;
|
|
678
|
-
height:
|
|
679
|
-
max-height: 60%;
|
|
678
|
+
height: 30%;
|
|
680
679
|
background: #0a0a18;
|
|
681
|
-
border-
|
|
682
|
-
padding:
|
|
680
|
+
border-bottom: solid #333350;
|
|
681
|
+
padding: 0 1;
|
|
683
682
|
overflow-y: scroll;
|
|
684
683
|
display: none;
|
|
685
684
|
}
|
|
@@ -715,21 +714,14 @@ Screen:inline {
|
|
|
715
714
|
}
|
|
716
715
|
|
|
717
716
|
#tool-panel {
|
|
718
|
-
height: auto;
|
|
719
|
-
min-height: 0;
|
|
720
|
-
max-height: 20%;
|
|
721
717
|
width: 100%;
|
|
722
718
|
}
|
|
723
719
|
|
|
724
720
|
#agent-panel {
|
|
725
|
-
min-height: 0;
|
|
726
|
-
max-height: 40%;
|
|
727
721
|
width: 100%;
|
|
728
722
|
}
|
|
729
723
|
|
|
730
724
|
#thinking-panel {
|
|
731
|
-
min-height: 0;
|
|
732
|
-
max-height: 60%;
|
|
733
725
|
width: 100%;
|
|
734
726
|
}
|
|
735
727
|
|
|
@@ -81,10 +81,11 @@ def run_tui(
|
|
|
81
81
|
raise SystemExit(1)
|
|
82
82
|
|
|
83
83
|
try:
|
|
84
|
-
app.run(inline=True, mouse=
|
|
84
|
+
app.run(inline=True, mouse=True)
|
|
85
85
|
finally:
|
|
86
|
-
#
|
|
87
|
-
# leave it on if the shutdown path doesn't
|
|
86
|
+
# Mouse is enabled for the session; make sure reporting is switched off
|
|
87
|
+
# on exit — Textual's driver may leave it on if the shutdown path doesn't
|
|
88
|
+
# fully complete (e.g. Ctrl+Q or a crash).
|
|
88
89
|
sys.stdout.write(
|
|
89
90
|
"\033[?1000l" # disable mouse click tracking
|
|
90
91
|
"\033[?1002l" # disable mouse button-event tracking
|
|
@@ -10,7 +10,7 @@ from textual.app import App, ComposeResult, InvalidThemeError
|
|
|
10
10
|
from textual.css.query import NoMatches
|
|
11
11
|
from textual.widgets import Static
|
|
12
12
|
from textual.timer import Timer
|
|
13
|
-
from textual import on
|
|
13
|
+
from textual import messages, on
|
|
14
14
|
from collections.abc import Awaitable, Callable
|
|
15
15
|
from dataclasses import dataclass
|
|
16
16
|
from pathlib import Path
|
|
@@ -208,7 +208,7 @@ class AyderApp(App):
|
|
|
208
208
|
# switches App.theme (the palette) live — handy for previewing palettes on
|
|
209
209
|
# the ayder layout before pinning one via [ui] palette.
|
|
210
210
|
ENABLE_COMMAND_PALETTE = True
|
|
211
|
-
ENABLE_MOUSE_SUPPORT =
|
|
211
|
+
ENABLE_MOUSE_SUPPORT = True
|
|
212
212
|
|
|
213
213
|
BINDINGS = [
|
|
214
214
|
("ctrl+q", "quit", "Quit"),
|
|
@@ -721,10 +721,11 @@ class AyderApp(App):
|
|
|
721
721
|
|
|
722
722
|
def compose(self) -> ComposeResult:
|
|
723
723
|
"""Compose the UI layout - terminal style with scrolling content."""
|
|
724
|
-
|
|
724
|
+
# Toggle panels open at the top; the chat stays next to the input bar.
|
|
725
725
|
yield ToolPanel(id="tool-panel")
|
|
726
726
|
yield ThinkingPanel(id="thinking-panel")
|
|
727
727
|
yield AgentPanel(id="agent-panel")
|
|
728
|
+
yield ChatView(id="chat-view")
|
|
728
729
|
yield ActivityBar(id="activity-bar")
|
|
729
730
|
yield CLIInputBar(commands=self.commands, id="input-bar")
|
|
730
731
|
yield StatusBar(
|
|
@@ -734,6 +735,17 @@ class AyderApp(App):
|
|
|
734
735
|
id="status-bar",
|
|
735
736
|
)
|
|
736
737
|
|
|
738
|
+
def _on_terminal_supports_synchronized_output(
|
|
739
|
+
self, message: messages.TerminalSupportsSynchronizedOutput
|
|
740
|
+
) -> None:
|
|
741
|
+
# EXPERIMENT, judged live by the operator: textual 8.2.7 leaves synchronized
|
|
742
|
+
# output (DEC 2026) off in inline mode, yet App._display already writes each
|
|
743
|
+
# inline frame as one sequence between _begin_update/_end_update, so the
|
|
744
|
+
# terminal can apply a whole frame atomically. Relies on private textual
|
|
745
|
+
# 8.2.7 API (exact pin); drop this if it does not help.
|
|
746
|
+
if self._driver is not None:
|
|
747
|
+
self._sync_available = True
|
|
748
|
+
|
|
737
749
|
def on_mount(self) -> None:
|
|
738
750
|
"""Called when app is mounted."""
|
|
739
751
|
install_asyncio_handler(asyncio.get_running_loop())
|
|
@@ -776,7 +788,6 @@ class AyderApp(App):
|
|
|
776
788
|
)
|
|
777
789
|
spacer_h = max(0, viewport_h - content_h)
|
|
778
790
|
self._banner_spacer.styles.height = spacer_h
|
|
779
|
-
chat_view.scroll_end(animate=False)
|
|
780
791
|
|
|
781
792
|
@property
|
|
782
793
|
def _is_processing(self) -> bool:
|
|
@@ -1092,9 +1103,11 @@ class AyderApp(App):
|
|
|
1092
1103
|
def _after_turn_finished(self) -> None:
|
|
1093
1104
|
"""UI teardown after a turn fully exits; the consumer pulls the next one."""
|
|
1094
1105
|
self._set_inbox_status("idle")
|
|
1095
|
-
self._maybe_stop_activity_timer()
|
|
1096
1106
|
try:
|
|
1107
|
+
# Clear first: a text-only turn ends with the bar still "generating",
|
|
1108
|
+
# which keeps the stop check below from ever seeing an idle bar.
|
|
1097
1109
|
self.query_one("#activity-bar", ActivityBar).clear()
|
|
1110
|
+
self._maybe_stop_activity_timer()
|
|
1098
1111
|
self.query_one("#input-bar", CLIInputBar).focus_input()
|
|
1099
1112
|
except Exception: # noqa: BLE001 - teardown guard: post-turn UI cleanup is cosmetic and must not kill the app
|
|
1100
1113
|
logger.opt(exception=True).debug("Post-turn UI teardown failed")
|
|
@@ -1146,28 +1159,15 @@ class AyderApp(App):
|
|
|
1146
1159
|
|
|
1147
1160
|
def action_toggle_tools(self) -> None:
|
|
1148
1161
|
"""Toggle the tool context panel."""
|
|
1149
|
-
|
|
1150
|
-
visible = tool_panel.toggle()
|
|
1151
|
-
chat_view = self.query_one("#chat-view", ChatView)
|
|
1152
|
-
state = "visible" if visible else "hidden"
|
|
1153
|
-
chat_view.add_system_message(f"Tool panel {state} (Ctrl+O to toggle)")
|
|
1162
|
+
self.query_one("#tool-panel", ToolPanel).toggle()
|
|
1154
1163
|
|
|
1155
1164
|
def action_toggle_thinking(self) -> None:
|
|
1156
1165
|
"""Toggle the thinking/reasoning panel (mirrors Ctrl+O / Ctrl+G)."""
|
|
1157
|
-
|
|
1158
|
-
visible = thinking_panel.toggle()
|
|
1159
|
-
self._show_thinking = visible
|
|
1160
|
-
chat_view = self.query_one("#chat-view", ChatView)
|
|
1161
|
-
state = "visible" if visible else "hidden"
|
|
1162
|
-
chat_view.add_system_message(f"Thinking panel {state} (Ctrl+T to toggle)")
|
|
1166
|
+
self._show_thinking = self.query_one("#thinking-panel", ThinkingPanel).toggle()
|
|
1163
1167
|
|
|
1164
1168
|
def action_toggle_agents(self) -> None:
|
|
1165
1169
|
"""Toggle the agent panel."""
|
|
1166
|
-
|
|
1167
|
-
visible = agent_panel.toggle()
|
|
1168
|
-
chat_view = self.query_one("#chat-view", ChatView)
|
|
1169
|
-
state = "visible" if visible else "hidden"
|
|
1170
|
-
chat_view.add_system_message(f"Agent panel {state} (Ctrl+G to toggle)")
|
|
1170
|
+
self.query_one("#agent-panel", AgentPanel).toggle()
|
|
1171
1171
|
|
|
1172
1172
|
def action_show_help(self) -> None:
|
|
1173
1173
|
"""Show help modal with keybindings."""
|
|
@@ -1187,8 +1187,6 @@ class AyderApp(App):
|
|
|
1187
1187
|
return
|
|
1188
1188
|
chat_view = self.query_one("#chat-view", ChatView)
|
|
1189
1189
|
chat_view.scroll_page_down(animate=False)
|
|
1190
|
-
if chat_view.scroll_offset.y >= chat_view.max_scroll_y:
|
|
1191
|
-
chat_view.enable_follow_mode()
|
|
1192
1190
|
|
|
1193
1191
|
def _scroll_open_panel(self, motion: str) -> bool:
|
|
1194
1192
|
"""Scroll a visible scrollable panel — the agents (Ctrl+G) or thinking
|
|
@@ -741,6 +741,10 @@ class AgentListScreen(ModalScreen[str | None]):
|
|
|
741
741
|
self.dismiss(None)
|
|
742
742
|
|
|
743
743
|
|
|
744
|
+
# Mouse support captures the mouse, so the terminal's own selection needs a modifier.
|
|
745
|
+
SELECT_TEXT_HINT = "Hold Shift to select text with the mouse (Option in iTerm2 and Terminal.app)."
|
|
746
|
+
|
|
747
|
+
|
|
744
748
|
class CLIHelpScreen(ModalScreen[None]):
|
|
745
749
|
"""Centered help modal showing all keybindings, dynamically generated."""
|
|
746
750
|
|
|
@@ -758,6 +762,8 @@ class CLIHelpScreen(ModalScreen[None]):
|
|
|
758
762
|
for key, desc in entries:
|
|
759
763
|
lines.append(f" {key:<16}{desc}")
|
|
760
764
|
lines.append("")
|
|
765
|
+
lines.append(f" {SELECT_TEXT_HINT}")
|
|
766
|
+
lines.append("")
|
|
761
767
|
lines.append(" Press Esc to close")
|
|
762
768
|
return "\n".join(lines)
|
|
763
769
|
|
|
@@ -8,10 +8,13 @@ from typing import Any
|
|
|
8
8
|
from textual.app import ComposeResult
|
|
9
9
|
from textual.containers import Horizontal, VerticalScroll, Container
|
|
10
10
|
from textual.css.query import NoMatches
|
|
11
|
+
from textual.geometry import Size
|
|
11
12
|
from textual.message import Message
|
|
12
13
|
from textual.reactive import reactive
|
|
13
14
|
from textual.suggester import SuggestFromList
|
|
15
|
+
from textual.widget import Widget
|
|
14
16
|
from textual.widgets import Static, Input, Label, TextArea
|
|
17
|
+
from rich.cells import cell_len
|
|
15
18
|
from rich.text import Text
|
|
16
19
|
from rich.markdown import Markdown
|
|
17
20
|
from rich.spinner import Spinner
|
|
@@ -52,15 +55,58 @@ class ChatView(VerticalScroll):
|
|
|
52
55
|
super().__init__(**kwargs)
|
|
53
56
|
self._message_widgets: list[Static] = []
|
|
54
57
|
self._thinking_visible: bool = False
|
|
55
|
-
|
|
58
|
+
|
|
59
|
+
def on_mount(self) -> None:
|
|
60
|
+
"""Anchor to the bottom: follow the end of the chat.
|
|
61
|
+
|
|
62
|
+
Textual re-pins an anchored widget to its end inside the same layout
|
|
63
|
+
pass as any resize or new content, so a panel opening or a message
|
|
64
|
+
arriving never draws a frame at a stale offset. Scrolling up releases
|
|
65
|
+
the anchor; scrolling back to the end re-arms it.
|
|
66
|
+
"""
|
|
67
|
+
self.anchor()
|
|
68
|
+
|
|
69
|
+
def _size_updated(
|
|
70
|
+
self, size: Size, virtual_size: Size, container_size: Size, layout: bool = True
|
|
71
|
+
) -> bool:
|
|
72
|
+
# Textual pins an anchored widget using the pre-resize container_size, so
|
|
73
|
+
# the first frame after a resize (a panel opening) draws at a stale offset.
|
|
74
|
+
# Re-pin once the new size is in, then ask for another layout: the
|
|
75
|
+
# compositor placed the children with the stale offset, and a silent
|
|
76
|
+
# set_reactive does not move them (without it every open after the first
|
|
77
|
+
# left the newest rows cut off). Relies on private Widget/scrollbar
|
|
78
|
+
# attributes of textual 8.2.7; the draw-spy and painted-rows tests in
|
|
79
|
+
# test_panel_layout guard it.
|
|
80
|
+
updated = super()._size_updated(size, virtual_size, container_size, layout)
|
|
81
|
+
if updated and self._anchored and not self._anchor_released:
|
|
82
|
+
stale = self.scroll_y
|
|
83
|
+
end = self.max_scroll_y
|
|
84
|
+
self.set_reactive(Widget.scroll_y, end)
|
|
85
|
+
self.set_reactive(Widget.scroll_target_y, end)
|
|
86
|
+
self.vertical_scrollbar._reactive_position = end # type: ignore[attr-defined]
|
|
87
|
+
if end != stale:
|
|
88
|
+
self.refresh(layout=True)
|
|
89
|
+
return updated
|
|
90
|
+
|
|
91
|
+
def release_anchor(self) -> None:
|
|
92
|
+
# Textual re-arms an anchor only in watch_scroll_y, so a scroll that lands
|
|
93
|
+
# where it already is (PageDown at the end) would leave it released for
|
|
94
|
+
# good. Re-check once the scroll has landed; a user who scrolled up stays
|
|
95
|
+
# released because scroll_y is then below the end.
|
|
96
|
+
super().release_anchor()
|
|
97
|
+
self.call_after_refresh(self._reanchor_at_end)
|
|
98
|
+
|
|
99
|
+
def _reanchor_at_end(self) -> None:
|
|
100
|
+
if self.scroll_y >= self.max_scroll_y:
|
|
101
|
+
self.anchor()
|
|
56
102
|
|
|
57
103
|
def disable_follow_mode(self) -> None:
|
|
58
|
-
"""
|
|
59
|
-
self.
|
|
104
|
+
"""Stop following the end (user is reading history)."""
|
|
105
|
+
self.release_anchor()
|
|
60
106
|
|
|
61
107
|
def enable_follow_mode(self) -> None:
|
|
62
|
-
"""
|
|
63
|
-
self.
|
|
108
|
+
"""Follow the end again and jump to it."""
|
|
109
|
+
self.anchor()
|
|
64
110
|
|
|
65
111
|
def _create_text(
|
|
66
112
|
self, content: str, msg_type: MessageType, metadata: dict | None = None
|
|
@@ -137,8 +183,6 @@ class ChatView(VerticalScroll):
|
|
|
137
183
|
text = self._create_text(full_content, msg_type, metadata)
|
|
138
184
|
if text:
|
|
139
185
|
last_widget.update(text)
|
|
140
|
-
if self._follow_mode:
|
|
141
|
-
self.scroll_end(animate=False)
|
|
142
186
|
return
|
|
143
187
|
|
|
144
188
|
self.messages.append(
|
|
@@ -163,9 +207,6 @@ class ChatView(VerticalScroll):
|
|
|
163
207
|
if msg_type == MessageType.THINKING and not self._thinking_visible:
|
|
164
208
|
msg_widget.display = False
|
|
165
209
|
|
|
166
|
-
if self._follow_mode:
|
|
167
|
-
self.scroll_end(animate=False)
|
|
168
|
-
|
|
169
210
|
def add_user_message(self, content: str) -> None:
|
|
170
211
|
"""Add a user message."""
|
|
171
212
|
self.add_message(content, MessageType.USER)
|
|
@@ -241,6 +282,7 @@ class ToolPanel(Container):
|
|
|
241
282
|
widget = Static(spinner, classes="tool-item running")
|
|
242
283
|
self._tools[tool_call_id] = (widget, spinner, tool_name, args_str, False)
|
|
243
284
|
self.mount(widget)
|
|
285
|
+
self.call_after_refresh(self.scroll_end, animate=False)
|
|
244
286
|
|
|
245
287
|
def complete_tool(self, tool_call_id: str, result: str = "") -> None:
|
|
246
288
|
"""Mark a tool as completed."""
|
|
@@ -260,6 +302,7 @@ class ToolPanel(Container):
|
|
|
260
302
|
widget.remove_class("running")
|
|
261
303
|
widget.add_class("completed")
|
|
262
304
|
self._tools[tool_call_id] = (widget, None, tool_name, args_str, True)
|
|
305
|
+
self.call_after_refresh(self.scroll_end, animate=False)
|
|
263
306
|
|
|
264
307
|
def update_spinners(self) -> None:
|
|
265
308
|
"""Re-render running tool spinners (they auto-advance based on time)."""
|
|
@@ -298,8 +341,9 @@ class ThinkingPanel(Container):
|
|
|
298
341
|
Toggled with Ctrl+T, mirroring ToolPanel (Ctrl+O) and AgentPanel (Ctrl+G):
|
|
299
342
|
reasoning is streamed in silently while the ActivityBar spinner shows the
|
|
300
343
|
ambient "Thinking..." status, and the user opens the panel to read the full
|
|
301
|
-
chain.
|
|
302
|
-
|
|
344
|
+
chain. Its height is a fixed 30% of the screen (three times the other
|
|
345
|
+
panels) because reasoning is long-form; it shows the complete accumulated text,
|
|
346
|
+
scrolling inside the panel, rather than a tail.
|
|
303
347
|
"""
|
|
304
348
|
|
|
305
349
|
# Minimum seconds between re-renders while the panel is open. Reasoning
|
|
@@ -934,8 +978,6 @@ class _SubmitTextArea(TextArea):
|
|
|
934
978
|
return
|
|
935
979
|
chat_view = self.app.query_one("#chat-view", ChatView)
|
|
936
980
|
chat_view.scroll_page_down(animate=False)
|
|
937
|
-
if chat_view.scroll_offset.y >= chat_view.max_scroll_y:
|
|
938
|
-
chat_view.enable_follow_mode()
|
|
939
981
|
return
|
|
940
982
|
|
|
941
983
|
if event.key == "ctrl+pageup":
|
|
@@ -955,8 +997,6 @@ class _SubmitTextArea(TextArea):
|
|
|
955
997
|
return
|
|
956
998
|
chat_view = self.app.query_one("#chat-view", ChatView)
|
|
957
999
|
chat_view.scroll_down(animate=False)
|
|
958
|
-
if chat_view.scroll_offset.y >= chat_view.max_scroll_y:
|
|
959
|
-
chat_view.enable_follow_mode()
|
|
960
1000
|
return
|
|
961
1001
|
|
|
962
1002
|
if event.key == "home":
|
|
@@ -976,7 +1016,6 @@ class _SubmitTextArea(TextArea):
|
|
|
976
1016
|
return
|
|
977
1017
|
chat_view = self.app.query_one("#chat-view", ChatView)
|
|
978
1018
|
chat_view.enable_follow_mode()
|
|
979
|
-
chat_view.scroll_end(animate=False)
|
|
980
1019
|
return
|
|
981
1020
|
|
|
982
1021
|
self._reset_tab_cycle()
|
|
@@ -1147,6 +1186,69 @@ class CLIInputBar(Horizontal):
|
|
|
1147
1186
|
self._input.insert(text)
|
|
1148
1187
|
|
|
1149
1188
|
|
|
1189
|
+
_STATUS_SEP = " | "
|
|
1190
|
+
|
|
1191
|
+
|
|
1192
|
+
def _abbreviate_home(path: Path | str) -> str:
|
|
1193
|
+
"""Show a path with the home directory as ``~`` (``~`` itself for home)."""
|
|
1194
|
+
path = Path(path)
|
|
1195
|
+
try:
|
|
1196
|
+
relative = path.relative_to(Path.home())
|
|
1197
|
+
except (ValueError, RuntimeError): # not under home, or no home to compare to
|
|
1198
|
+
return str(path)
|
|
1199
|
+
return "~" if not relative.parts else f"~/{relative.as_posix()}"
|
|
1200
|
+
|
|
1201
|
+
|
|
1202
|
+
def _shorten_left(text: str, width: int) -> str:
|
|
1203
|
+
"""Fit a path into ``width`` cells, dropping leading components for ``…``.
|
|
1204
|
+
|
|
1205
|
+
``~/Vscode/ayder-cli/src`` in 15 cells becomes ``…/ayder-cli/src``. When even
|
|
1206
|
+
the last component is too wide, its tail is kept: ``…ongname``.
|
|
1207
|
+
"""
|
|
1208
|
+
if width <= 0:
|
|
1209
|
+
return ""
|
|
1210
|
+
if cell_len(text) <= width:
|
|
1211
|
+
return text
|
|
1212
|
+
parts = [part for part in text.split("/") if part]
|
|
1213
|
+
if width == 1 or not parts:
|
|
1214
|
+
return "…"
|
|
1215
|
+
kept: list[str] = []
|
|
1216
|
+
for part in reversed(parts):
|
|
1217
|
+
if cell_len("…/" + "/".join([part, *kept])) > width:
|
|
1218
|
+
break
|
|
1219
|
+
kept.insert(0, part)
|
|
1220
|
+
if kept:
|
|
1221
|
+
return "…/" + "/".join(kept)
|
|
1222
|
+
return "…" + parts[-1][-(width - 1):]
|
|
1223
|
+
|
|
1224
|
+
|
|
1225
|
+
class _CwdLabel(Label):
|
|
1226
|
+
"""The working directory, shortened from the left to the width it is given."""
|
|
1227
|
+
|
|
1228
|
+
def __init__(self, display: str, **kwargs):
|
|
1229
|
+
super().__init__("", **kwargs)
|
|
1230
|
+
self.display_text = display
|
|
1231
|
+
|
|
1232
|
+
def get_content_width(self, container: Size, viewport: Size) -> int:
|
|
1233
|
+
"""Width from the path and the bar alone, never from the other items.
|
|
1234
|
+
|
|
1235
|
+
The path is shortened from the left to at most a third of the bar, but
|
|
1236
|
+
never below ``…/<last component>`` (or the whole path, if shorter). The
|
|
1237
|
+
items on its right may overflow the bar; the cwd stays visible.
|
|
1238
|
+
"""
|
|
1239
|
+
sep = cell_len(_STATUS_SEP)
|
|
1240
|
+
last = self.display_text.rsplit("/", 1)[-1]
|
|
1241
|
+
budget = max(container.width // 3, cell_len(f"…/{last}"))
|
|
1242
|
+
shown = _shorten_left(self.display_text, min(budget, container.width - sep))
|
|
1243
|
+
return cell_len(shown) + sep
|
|
1244
|
+
|
|
1245
|
+
def render(self) -> Text:
|
|
1246
|
+
room = self.size.width - cell_len(_STATUS_SEP)
|
|
1247
|
+
if room < 1:
|
|
1248
|
+
return Text("")
|
|
1249
|
+
return Text(_shorten_left(self.display_text, room) + _STATUS_SEP)
|
|
1250
|
+
|
|
1251
|
+
|
|
1150
1252
|
class StatusBar(Horizontal):
|
|
1151
1253
|
"""
|
|
1152
1254
|
CLI-style status bar showing context info.
|
|
@@ -1157,9 +1259,11 @@ class StatusBar(Horizontal):
|
|
|
1157
1259
|
model: str = "default",
|
|
1158
1260
|
permissions: set | None = None,
|
|
1159
1261
|
effort: str = "default",
|
|
1262
|
+
cwd: Path | str | None = None,
|
|
1160
1263
|
**kwargs,
|
|
1161
1264
|
):
|
|
1162
1265
|
super().__init__(**kwargs)
|
|
1266
|
+
self._cwd_display = _abbreviate_home(cwd if cwd is not None else Path.cwd())
|
|
1163
1267
|
self.model = model
|
|
1164
1268
|
self.effort = effort
|
|
1165
1269
|
self.token_count = 0
|
|
@@ -1168,6 +1272,7 @@ class StatusBar(Horizontal):
|
|
|
1168
1272
|
|
|
1169
1273
|
def compose(self) -> ComposeResult:
|
|
1170
1274
|
mode_str = "".join(sorted(self._permissions))
|
|
1275
|
+
yield _CwdLabel(self._cwd_display, id="cwd-label")
|
|
1171
1276
|
yield Label(markup_or_plain(f"model: {self.model}"), id="model-label")
|
|
1172
1277
|
yield Label(f" | effort: {self.effort}", id="effort-label")
|
|
1173
1278
|
yield Label(markup_or_plain(f" | mode: {mode_str}"), id="mode-label")
|
|
@@ -1342,6 +1447,7 @@ class AgentPanel(Container):
|
|
|
1342
1447
|
)
|
|
1343
1448
|
self._entries[run_id] = entry
|
|
1344
1449
|
self.mount(status_widget)
|
|
1450
|
+
self.call_after_refresh(self.scroll_end, animate=False)
|
|
1345
1451
|
|
|
1346
1452
|
def complete_agent(self, run_id: int, summary: str, status: str = "completed") -> None:
|
|
1347
1453
|
"""Mark agent as completed with full summary. Lookup by run_id."""
|