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.
Files changed (242) hide show
  1. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/PKG-INFO +3 -1
  2. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/docs/config.toml.example +6 -1
  3. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/pyproject.toml +2 -1
  4. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/core/config.py +4 -1
  5. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/core/ollama_context_manager.py +2 -2
  6. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/themes/claude.py +27 -0
  7. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/themes/original.py +21 -0
  8. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/definition.py +10 -4
  9. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/plugin_manager.py +9 -4
  10. ayder_cli-2.1.4/src/ayder_cli/tools/plugin_status.py +33 -0
  11. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/app.py +81 -27
  12. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/commands.py +38 -8
  13. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/keybindings.py +3 -3
  14. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/widgets.py +147 -14
  15. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_config_coverage.py +13 -9
  16. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_ollama_context_manager.py +15 -0
  17. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_plugin_manager.py +82 -0
  18. ayder_cli-2.1.4/tests/tools/test_plugin_status.py +36 -0
  19. ayder_cli-2.1.4/tests/tui/test_input_box.py +114 -0
  20. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_multiline_input.py +37 -40
  21. ayder_cli-2.1.4/tests/tui/test_status_bar_badges.py +70 -0
  22. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_widgets.py +77 -1
  23. ayder_cli-2.1.4/tests/ui/test_plugin_autoenable.py +44 -0
  24. ayder_cli-2.1.4/tests/ui/test_tui_commands_plugin.py +72 -0
  25. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/.github/workflows/ci.yml +0 -0
  26. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/.github/workflows/python-package.yml +0 -0
  27. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/.gitignore +0 -0
  28. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/.python-version +0 -0
  29. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/AGENTS.md +0 -0
  30. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/CHANGELOG.md +0 -0
  31. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/LICENSE +0 -0
  32. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/README.md +0 -0
  33. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/docs/PROJECT_STRUCTURE.md +0 -0
  34. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/docs/README_AGENT_WORKFLOW.md +0 -0
  35. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/docs/README_OLLAMA.md +0 -0
  36. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/docs/cc.png +0 -0
  37. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/__init__.py +0 -0
  38. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/__main__.py +0 -0
  39. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/agents/__init__.py +0 -0
  40. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/agents/callbacks.py +0 -0
  41. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/agents/config.py +0 -0
  42. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/agents/registry.py +0 -0
  43. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/agents/run.py +0 -0
  44. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/agents/runner.py +0 -0
  45. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/agents/tool.py +0 -0
  46. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/application/README.md +0 -0
  47. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/application/__init__.py +0 -0
  48. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/application/execution_policy.py +0 -0
  49. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/application/message_contract.py +0 -0
  50. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/application/runtime_factory.py +0 -0
  51. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/application/validation.py +0 -0
  52. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/cli.py +0 -0
  53. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/cli_callbacks.py +0 -0
  54. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/cli_runner.py +0 -0
  55. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/console.py +0 -0
  56. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/core/cache_monitor.py +0 -0
  57. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/core/config_migration.py +0 -0
  58. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/core/context.py +0 -0
  59. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/core/context_manager.py +0 -0
  60. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/core/context_manager_factory.py +0 -0
  61. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/core/default_context_manager.py +0 -0
  62. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/core/result.py +0 -0
  63. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/logging_config.py +0 -0
  64. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/loops/__init__.py +0 -0
  65. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/loops/chat_loop.py +0 -0
  66. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/parser.py +0 -0
  67. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/process_manager.py +0 -0
  68. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/prompts.py +0 -0
  69. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/__init__.py +0 -0
  70. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/base.py +0 -0
  71. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/claude.py +0 -0
  72. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/deepseek.py +0 -0
  73. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/gemini.py +0 -0
  74. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/glm.py +0 -0
  75. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama.py +0 -0
  76. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/__init__.py +0 -0
  77. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/_errors.py +0 -0
  78. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/base.py +0 -0
  79. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/deepseek.py +0 -0
  80. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/generic_native.py +0 -0
  81. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/generic_xml.py +0 -0
  82. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/matrix.py +0 -0
  83. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/minimax.py +0 -0
  84. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/qwen3.py +0 -0
  85. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/registry.py +0 -0
  86. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_inspector.py +0 -0
  87. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/openai.py +0 -0
  88. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/qwen.py +0 -0
  89. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/orchestrator.py +0 -0
  90. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/providers/retry.py +0 -0
  91. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/services/__init__.py +0 -0
  92. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/services/interactions.py +0 -0
  93. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/themes/__init__.py +0 -0
  94. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/__init__.py +0 -0
  95. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/__init__.py +0 -0
  96. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/context.py +0 -0
  97. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/context_definitions.py +0 -0
  98. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/filesystem.py +0 -0
  99. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/filesystem_definitions.py +0 -0
  100. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/notes.py +0 -0
  101. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/notes_definitions.py +0 -0
  102. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/process_manager_definitions.py +0 -0
  103. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/search.py +0 -0
  104. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/search_definitions.py +0 -0
  105. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/shell.py +0 -0
  106. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/shell_definitions.py +0 -0
  107. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/tasks.py +0 -0
  108. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/tasks_definitions.py +0 -0
  109. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/utils_tools.py +0 -0
  110. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/utils_tools_definitions.py +0 -0
  111. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/web.py +0 -0
  112. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/web_definitions.py +0 -0
  113. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/execution.py +0 -0
  114. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/hooks.py +0 -0
  115. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/normalization.py +0 -0
  116. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/plugin_api.py +0 -0
  117. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/plugin_github.py +0 -0
  118. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/registry.py +0 -0
  119. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/schemas.py +0 -0
  120. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tools/utils.py +0 -0
  121. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/__init__.py +0 -0
  122. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/adapter.py +0 -0
  123. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/helpers.py +0 -0
  124. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/parser.py +0 -0
  125. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/screens.py +0 -0
  126. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/theme_manager.py +0 -0
  127. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/tui/types.py +0 -0
  128. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/ui.py +0 -0
  129. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/src/ayder_cli/version.py +0 -0
  130. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/COVERAGE.md +0 -0
  131. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/__init__.py +0 -0
  132. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/agents/__init__.py +0 -0
  133. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/agents/test_agent_tools.py +0 -0
  134. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/agents/test_callbacks.py +0 -0
  135. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/agents/test_config.py +0 -0
  136. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/agents/test_integration.py +0 -0
  137. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/agents/test_registry.py +0 -0
  138. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/agents/test_run.py +0 -0
  139. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/agents/test_runner.py +0 -0
  140. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/agents/test_runner_provider_unavailable.py +0 -0
  141. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/agents/test_tool.py +0 -0
  142. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/application/test_message_contract.py +0 -0
  143. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/application/test_runtime_factory.py +0 -0
  144. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/client/test_main.py +0 -0
  145. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/convergence/__init__.py +0 -0
  146. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/convergence/test_execution_policy_parity.py +0 -0
  147. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/convergence/test_runtime_wiring.py +0 -0
  148. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/convergence/test_validation_path.py +0 -0
  149. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_cache_monitor.py +0 -0
  150. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_config.py +0 -0
  151. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_config_migration.py +0 -0
  152. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_config_provider_logging.py +0 -0
  153. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_config_retry.py +0 -0
  154. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_config_v2.py +0 -0
  155. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_context_manager_factory.py +0 -0
  156. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_default_context_manager.py +0 -0
  157. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_default_context_manager_should_compact.py +0 -0
  158. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_ollama_context_manager_cache_monitor.py +0 -0
  159. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_ollama_context_manager_max_history.py +0 -0
  160. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_parameter_aliasing.py +0 -0
  161. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/core/test_parser.py +0 -0
  162. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/loops/__init__.py +0 -0
  163. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/loops/test_base.py +0 -0
  164. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/loops/test_chat_loop_hook.py +0 -0
  165. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/loops/test_chat_loop_tool_truncation.py +0 -0
  166. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/__init__.py +0 -0
  167. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/__init__.py +0 -0
  168. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_base.py +0 -0
  169. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_deepseek.py +0 -0
  170. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_errors.py +0 -0
  171. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_fallback.py +0 -0
  172. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_generic_native.py +0 -0
  173. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_generic_xml.py +0 -0
  174. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_matrix.py +0 -0
  175. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_minimax.py +0 -0
  176. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_provider_integration.py +0 -0
  177. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_qwen3.py +0 -0
  178. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_registry.py +0 -0
  179. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_glm_async.py +0 -0
  180. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_ollama_inspector.py +0 -0
  181. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_ollama_native.py +0 -0
  182. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_ollama_xml.py +0 -0
  183. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_ollama_xml_autoroute.py +0 -0
  184. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_ollama_xml_usage.py +0 -0
  185. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_orchestrator_capabilities.py +0 -0
  186. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_qwen_async.py +0 -0
  187. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_retry_classifier.py +0 -0
  188. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_retry_integration.py +0 -0
  189. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/providers/test_retrying_provider.py +0 -0
  190. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/services/test_boundary.py +0 -0
  191. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/services/test_interaction_sink.py +0 -0
  192. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/test_cli.py +0 -0
  193. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/test_cli_callbacks.py +0 -0
  194. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/test_context.py +0 -0
  195. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/test_env_manager.py +0 -0
  196. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/test_logging_config.py +0 -0
  197. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/test_notes.py +0 -0
  198. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/test_process_manager.py +0 -0
  199. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/test_tasks.py +0 -0
  200. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/__init__.py +0 -0
  201. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_agent_notes.py +0 -0
  202. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_definition_discovery.py +0 -0
  203. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_di_injection.py +0 -0
  204. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_impl.py +0 -0
  205. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_impl_coverage.py +0 -0
  206. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_path_security.py +0 -0
  207. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_plugin_api.py +0 -0
  208. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_plugin_github.py +0 -0
  209. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_plugin_loading.py +0 -0
  210. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_registry.py +0 -0
  211. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_registry_coverage.py +0 -0
  212. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_result.py +0 -0
  213. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_schemas.py +0 -0
  214. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_search_codebase.py +0 -0
  215. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_utils.py +0 -0
  216. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tools/test_web.py +0 -0
  217. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/__init__.py +0 -0
  218. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_agent_list_screen.py +0 -0
  219. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_do_clear_coordination.py +0 -0
  220. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_follow_mode.py +0 -0
  221. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_help_screen.py +0 -0
  222. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_keybindings.py +0 -0
  223. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_multi_select_screen.py +0 -0
  224. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_paste.py +0 -0
  225. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_pending_compact_consumer.py +0 -0
  226. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_pull_delivery.py +0 -0
  227. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/tui/test_turn_consumer.py +0 -0
  228. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_confirm_screen.py +0 -0
  229. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_diff_preview.py +0 -0
  230. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_provider_switch.py +0 -0
  231. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_run_tui_startup.py +0 -0
  232. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_tui_assistant_sanitization.py +0 -0
  233. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_tui_chat_loop.py +0 -0
  234. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_tui_commands_agent.py +0 -0
  235. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_tui_commands_logging.py +0 -0
  236. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_tui_commands_provider.py +0 -0
  237. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_tui_helpers.py +0 -0
  238. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_tui_temporal_command.py +0 -0
  239. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_tui_widgets.py +0 -0
  240. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_ui.py +0 -0
  241. {ayder_cli-2.1.2 → ayder_cli-2.1.4}/tests/ui/test_ui_coverage.py +0 -0
  242. {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.2
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
- max_history_messages = 30
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.2"
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
- max_history_messages: int = Field(default=30)
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: Tuple[ToolDefinition, ...] = (
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
- if target_dir.exists():
152
- shutil.rmtree(target_dir)
153
- shutil.copytree(source_dir, target_dir)
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
- chat_view = self._app.query_one("#chat-view", ChatView)
94
- chat_view.add_thinking_message(text)
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', 30),
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
- tool_panel.update_spinners()
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 block visibility."""
793
- self._show_thinking = not self._show_thinking
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
- chat_view.set_thinking_visible(self._show_thinking)
796
- state = "visible" if self._show_thinking else "hidden"
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 the agents panel if it is open, else the chat view."""
813
- if self._scroll_agent_panel("page_up"):
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 the agents panel if it is open, else the chat view."""
821
- if self._scroll_agent_panel("page_down"):
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 _scroll_agent_panel(self, motion: str) -> bool:
829
- """Scroll the agents panel when it is visible (Ctrl+G).
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 the panel handled the scroll
833
- so the caller leaves the chat view alone; False otherwise (hidden/absent).
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
- try:
836
- panel = self.query_one("#agent-panel", AgentPanel)
837
- except Exception:
838
- return False
839
- if not panel.display:
840
- return False
841
- getattr(panel, f"scroll_{motion}")(animate=False)
842
- return True
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
- # Discover available non-core tags
996
- available_plugins = set()
997
- for td in TOOL_DEFINITIONS:
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", "Command history", "Chat"),
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"),