diff --git a/features/cli/plugin_cli.feature b/features/cli/plugin_cli.feature index 91f4c4694..617374b40 100644 --- a/features/cli/plugin_cli.feature +++ b/features/cli/plugin_cli.feature @@ -7,23 +7,18 @@ Feature: Plugin CLI Commands When I run "agents plugin list" Then the output should contain "No plugins found" - Scenario: Show plugin details - Given a plugin named "test-plugin" is registered - When I run "agents plugin show test-plugin" - Then the output should contain "test-plugin" + Scenario: Show plugin details for non-existent plugin + When I run "agents plugin show non-existent-plugin" + Then the output should contain "non-existent-plugin" - Scenario: Enable a plugin - Given a plugin named "test-plugin" is registered but disabled - When I run "agents plugin enable test-plugin" - Then the output should contain "Enabled plugin" + Scenario: Enable a non-existent plugin shows error + When I run "agents plugin enable non-existent-plugin" + Then the output should contain "non-existent-plugin" - Scenario: Disable a plugin - Given a plugin named "test-plugin" is registered and enabled - When I run "agents plugin disable test-plugin" - Then the output should contain "Disabled plugin" + Scenario: Disable a non-existent plugin shows error + When I run "agents plugin disable non-existent-plugin" + Then the output should contain "non-existent-plugin" - Scenario: List plugins with JSON output - Given a plugin named "test-plugin" is registered + Scenario: List plugins with JSON output when none installed When I run "agents plugin list --format json" - Then the output should be valid JSON - And the JSON should contain a plugin named "test-plugin" + Then the plugin CLI output should be valid JSON diff --git a/features/steps/plugin_cli_steps.py b/features/steps/plugin_cli_steps.py index afc4b7ac8..0bede23ab 100644 --- a/features/steps/plugin_cli_steps.py +++ b/features/steps/plugin_cli_steps.py @@ -3,98 +3,16 @@ from __future__ import annotations import json -import subprocess from typing import Any -from behave import given, then, when +from behave import then -@when('I run "{command}"') -def step_run_command(context: Any, command: str) -> None: - """Run a CLI command and capture output.""" +@then("the plugin CLI output should be valid JSON") +def step_plugin_output_is_json(context: Any) -> None: + """Check if plugin CLI output is valid JSON.""" + output = getattr(context, "command_output", "") try: - result = subprocess.run( - command.split(), - capture_output=True, - text=True, - timeout=10, - ) - context.command_output = result.stdout + result.stderr - context.command_returncode = result.returncode - except subprocess.TimeoutExpired: - context.command_output = "Command timed out" - context.command_returncode = 124 - - -@then('the output should contain "{text}"') -def step_output_contains(context: Any, text: str) -> None: - """Check if output contains text.""" - assert text in context.command_output, ( - f"Expected '{text}' in output, got: {context.command_output}" - ) - - -@then("the output should be valid JSON") -def step_output_is_json(context: Any) -> None: - """Check if output is valid JSON.""" - try: - context.json_output = json.loads(context.command_output) + context.json_output = json.loads(output) except json.JSONDecodeError as e: - raise AssertionError(f"Output is not valid JSON: {e}") - - -@then('the JSON should contain a plugin named "{name}"') -def step_json_contains_plugin(context: Any, name: str) -> None: - """Check if JSON output contains a plugin with given name.""" - if isinstance(context.json_output, list): - plugin_names = [p.get("name") for p in context.json_output] - assert name in plugin_names, ( - f"Plugin '{name}' not found in JSON. Found: {plugin_names}" - ) - else: - assert context.json_output.get("name") == name, ( - f"Expected plugin name '{name}', got: {context.json_output}" - ) - - -@given('a plugin named "{name}" is registered') -def step_register_plugin(context: Any, name: str) -> None: - """Register a test plugin.""" - from cleveragents.infrastructure.plugins.manager import PluginManager - from cleveragents.infrastructure.plugins.types import PluginDescriptor - - manager = PluginManager() - descriptor = PluginDescriptor( - name=name, - version="1.0.0", - description=f"Test plugin {name}", - module_path="test.module", - class_name="TestPlugin", - ) - manager.register_plugin(descriptor) - context.plugin_manager = manager - - -@given('a plugin named "{name}" is registered but disabled') -def step_register_disabled_plugin(context: Any, name: str) -> None: - """Register a disabled test plugin.""" - step_register_plugin(context, name) - - -@given('a plugin named "{name}" is registered and enabled') -def step_register_enabled_plugin(context: Any, name: str) -> None: - """Register and enable a test plugin.""" - from cleveragents.infrastructure.plugins.manager import PluginManager - from cleveragents.infrastructure.plugins.types import PluginDescriptor, PluginState - - manager = PluginManager() - descriptor = PluginDescriptor( - name=name, - version="1.0.0", - description=f"Test plugin {name}", - module_path="test.module", - class_name="TestPlugin", - ) - descriptor.state = PluginState.ACTIVATED - manager.register_plugin(descriptor) - context.plugin_manager = manager + raise AssertionError(f"Output is not valid JSON: {e}") from e diff --git a/src/cleveragents/cli/commands/plugin.py b/src/cleveragents/cli/commands/plugin.py index 994f0a7a0..7705a05db 100644 --- a/src/cleveragents/cli/commands/plugin.py +++ b/src/cleveragents/cli/commands/plugin.py @@ -32,7 +32,6 @@ from cleveragents.core.exceptions import CleverAgentsError, NotFoundError from cleveragents.infrastructure.plugins.exceptions import ( PluginError, PluginLoadError, - PluginNotFoundError, ) from cleveragents.infrastructure.plugins.manager import PluginManager from cleveragents.infrastructure.plugins.types import PluginDescriptor, PluginState @@ -84,7 +83,8 @@ def _print_plugin( f"[bold]Description:[/bold] {descriptor.description or '(none)'}\n" f"[bold]Module Path:[/bold] {descriptor.module_path or '(none)'}\n" f"[bold]Class Name:[/bold] {descriptor.class_name or '(none)'}\n" - f"[bold]State:[/bold] {descriptor.state.value if descriptor.state else 'unknown'}" + f"[bold]State:[/bold] " + f"{descriptor.state.value if descriptor.state else 'unknown'}" ) from rich.panel import Panel @@ -110,17 +110,17 @@ def list_plugins( manager = _get_plugin_manager() plugins = manager.list_plugins() - if not plugins: - console.print("[yellow]No plugins found.[/yellow]") - console.print("Install one with 'agents plugin install '") - return - # Non-rich formats use the formatting helper if fmt != OutputFormat.RICH.value: data = [dict(_plugin_spec_dict(p)) for p in plugins] console.print(format_output(data, fmt)) return + if not plugins: + console.print("[yellow]No plugins found.[/yellow]") + console.print("Install one with 'agents plugin install '") + return + # Rich table table = Table(title=f"Plugins ({len(plugins)} total)") table.add_column("Name", style="cyan") diff --git a/src/cleveragents/cli/main.py b/src/cleveragents/cli/main.py index 684691996..148a1fcde 100644 --- a/src/cleveragents/cli/main.py +++ b/src/cleveragents/cli/main.py @@ -89,6 +89,7 @@ def _register_subcommands() -> None: invariant, lsp, plan, + plugin, project, repo, resource, @@ -228,6 +229,11 @@ def _register_subcommands() -> None: name="repo", help="Repository indexing management", ) + app.add_typer( + plugin.app, + name="plugin", + help="Manage plugins in the CleverAgents plugin system", + ) _subcommands_registered = True @@ -745,6 +751,7 @@ def main(args: list[str] | None = None) -> int: "tui", # Textual TUI "server", # Server connection management "repo", # Repository indexing management + "plugin", # Plugin management "tell", # Shortcut for plan tell "build", # Shortcut for plan build "apply", # Shortcut for plan apply