From a656c5787d7325c9d83b576702a9dd764a96ddf5 Mon Sep 17 00:00:00 2001 From: Jeffrey Phillips Freeman Date: Thu, 2 Apr 2026 19:50:08 +0000 Subject: [PATCH 1/3] fix(v3.7.0): resolve issue #1476 - tool list missing columns --- src/cleveragents/cli/commands/tool.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cleveragents/cli/commands/tool.py b/src/cleveragents/cli/commands/tool.py index 9c46b5c99..1512cb7a1 100644 --- a/src/cleveragents/cli/commands/tool.py +++ b/src/cleveragents/cli/commands/tool.py @@ -419,6 +419,8 @@ def list_tools( # Rich table table = Table(title=f"Tools ({len(tools)} total)") table.add_column("Name", style="cyan") + table.add_column("Read-Only", style="cyan") + table.add_column("Writes", style="yellow") table.add_column("Type", style="blue") table.add_column("Source", style="magenta") table.add_column("Description", style="dim") -- 2.52.0 From e6f31e6391629107661e7d77d31fda0dad35142f Mon Sep 17 00:00:00 2001 From: HAL9000 Date: Wed, 29 Apr 2026 17:22:22 +0000 Subject: [PATCH 2/3] fix(cli): align tool list rich output with spec (Read-Only/Writes columns and Summary panel) Replaced the broken PR implementation with a complete spec-compliant fix: - Removed Description and Timeout columns (not in spec) - Reordered columns to spec order: Name, Type, Source, Read-Only, Writes - Fixed column/row count mismatch: add_row now passes exactly 5 values - Added capability-based Read-Only/Writes computation (read_only/writes fields) - Renders checkmark (tick) or em dash based on capability metadata - Added Summary panel with Total, Tools, Validations, Read-Only, Writes, Namespaces counts - Added OK status message listing tool count ISSUES CLOSED: #1476 --- src/cleveragents/cli/commands/tool.py | 74 ++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 14 deletions(-) diff --git a/src/cleveragents/cli/commands/tool.py b/src/cleveragents/cli/commands/tool.py index 1512cb7a1..a89936f59 100644 --- a/src/cleveragents/cli/commands/tool.py +++ b/src/cleveragents/cli/commands/tool.py @@ -38,7 +38,7 @@ code: | |----------------------------|------------------------------------------| | ``Config file error`` | File not found or not readable | | ``Schema validation error``| YAML does not match Tool schema | -| ``Duplicate tool`` | Tool name already registered | +| ``Duplicate tool`` | Tool name already registered | Based on implementation_plan.md -- Task C1.tool.cli. """ @@ -416,31 +416,77 @@ def list_tools( console.print(format_output(data, fmt)) return - # Rich table + # Rich table — spec-compliant: Name, Type, Source, Read-Only, Writes table = Table(title=f"Tools ({len(tools)} total)") table.add_column("Name", style="cyan") - table.add_column("Read-Only", style="cyan") - table.add_column("Writes", style="yellow") table.add_column("Type", style="blue") table.add_column("Source", style="magenta") - table.add_column("Description", style="dim") - table.add_column("Timeout", justify="right") + table.add_column("Read-Only", justify="center", style="green") + table.add_column("Writes", justify="center", style="yellow") + + read_only_count: int = 0 + writes_count: int = 0 + tool_count: int = 0 + validation_count: int = 0 + namespaces: set[str] = set() for tool in tools: spec = _tool_spec_dict(tool) - desc = str(spec.get("description", "")) - if len(desc) > 40: - desc = desc[:37] + "..." + name_val = str(spec.get("name", "")) + tool_type_val = str(spec.get("tool_type", "tool")) + source_val = str(spec.get("source", "")) + + # Determine Read-Only / Writes from capability metadata + capability = spec.get("capability", {}) + if isinstance(capability, dict): + is_read_only: bool = capability.get("read_only", False) + has_writes: bool = capability.get("writes", False) + else: + is_read_only = False + has_writes = False + read_only_str: str = "\u2713" if is_read_only else "\u2014" + writes_str: str = "\u2713" if has_writes else "\u2014" + table.add_row( - str(spec.get("name", "")), - str(spec.get("tool_type", "tool")), - str(spec.get("source", "")), - desc, - str(spec.get("timeout", 300)), + name_val, + tool_type_val, + source_val, + read_only_str, + writes_str, ) + # Accumulate summary statistics + namespaces.add( + name_val.split("/", 1)[0] if "/" in name_val else name_val + ) + if tool_type_val == "validation": + validation_count += 1 + else: + tool_count += 1 + if is_read_only: + read_only_count += 1 + if has_writes: + writes_count += 1 + console.print(table) + # Summary panel + summary_lines = [ + f" Total: {len(tools)}", + f" Tools: {tool_count}", + f" Validations: {validation_count}", + f" Read-Only: {read_only_count}", + f" Writes: {writes_count}", + f" Namespaces: {len(namespaces)}", + ] + summary_panel = Panel( + "\n".join(summary_lines), + title="Summary", + expand=False, + ) + console.print(summary_panel) + console.print(f"\n\u2713 OK {len(tools)} tools listed\n") + except CleverAgentsError as exc: console.print(f"[red]Error:[/red] {exc.message}") raise typer.Abort() from exc -- 2.52.0 From b65f0db8d01db01c1f85b7a964bc00c41ff27da9 Mon Sep 17 00:00:00 2001 From: CleverThis Date: Sat, 30 May 2026 02:15:53 -0400 Subject: [PATCH 3/3] fix(cli): add Read-Only and Writes columns to tool list output - Apply ruff format to tool.py (flatten single-line namespaces.add call) - Add Behave BDD tests verifying 5-column table, capability rendering (checkmark/dash), and Summary panel presence - Add CHANGELOG.md entry ISSUES CLOSED: #1476 --- CHANGELOG.md | 8 ++++++++ features/steps/tool_cli_steps.py | 18 ++++++++++++++++++ features/tool_cli.feature | 20 ++++++++++++++++++++ src/cleveragents/cli/commands/tool.py | 4 +--- 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c382817f0..dca5c2ea6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ Changed `wf10_batch.robot` to be less likely to create files, and `plan_generation_graph.robot` to give more test answers. ## [Unreleased] +- **fix(cli): add Read-Only and Writes columns to tool list output** (#1476): Rewrote + `list_tools()` in `src/cleveragents/cli/commands/tool.py` to render exactly the 5 + spec-required columns (Name, Type, Source, Read-Only, Writes), removed the legacy + Description and Timeout columns, computes read-only/writes status from `capability` + metadata (rendering `✓`/`—`), and added a Summary panel showing Total, Tools, + Validations, Read-Only, Writes, and Namespaces counts. Adds Behave BDD tests in + `features/tool_cli.feature` verifying correct column names, capability rendering, and + Summary panel presence. - **Fix actor compiler to read LSP bindings from typed `lsp_binding` field** (#1488): Fixed `_extract_lsp_bindings()` in `actor/compiler.py` to read from `node.lsp_binding` (the typed `NodeLspBinding` Pydantic field on `NodeDefinition`) as the primary path, with diff --git a/features/steps/tool_cli_steps.py b/features/steps/tool_cli_steps.py index 77ea46562..a97cb7536 100644 --- a/features/steps/tool_cli_steps.py +++ b/features/steps/tool_cli_steps.py @@ -195,6 +195,13 @@ def step_no_mocked_tools(context: Context) -> None: context.mock_service.list_tools.return_value = [] +@given("there is a mocked read-only tool in the registry") +def step_mocked_read_only_tool(context: Context) -> None: + tool = _make_mock_tool("local/read-only-tool", "tool", "custom") + tool["capability"] = {"read_only": True, "writes": False, "checkpointable": False} + context.mock_service.list_tools.return_value = [tool] + + @given('there is a mocked tool with name "{name}"') def step_mocked_tool_exists(context: Context, name: str) -> None: context.mock_service.get_tool.return_value = _make_mock_tool(name) @@ -632,6 +639,17 @@ def step_tool_list_empty(context: Context) -> None: assert "No tools found" in context.tool_result.output +@then("the Rich tool list output contains all five spec columns") +def step_rich_tool_list_columns(context: Context) -> None: + assert context.tool_result is not None + assert context.tool_result.exit_code == 0 + output = context.tool_result.output + for col in ("Name", "Type", "Source", "Read-Only", "Writes"): + assert col in output, ( + f"Expected column '{col}' in Rich table output. Got: {output}" + ) + + @then("the tool CLI should show the tool details") def step_tool_show_details(context: Context) -> None: assert context.tool_result is not None diff --git a/features/tool_cli.feature b/features/tool_cli.feature index b2885e857..f02607e21 100644 --- a/features/tool_cli.feature +++ b/features/tool_cli.feature @@ -113,6 +113,26 @@ Feature: Tool and Validation CLI commands When I run tool CLI list with format "table" Then the tool CLI list should succeed with results + Scenario: Rich table list has exactly the five spec columns + Given there are mocked tools in the registry + When I run tool CLI list + Then the Rich tool list output contains all five spec columns + + Scenario: Rich table list renders checkmark for read-only capability + Given there is a mocked read-only tool in the registry + When I run tool CLI list + Then the tool CLI output should contain "✓" + + Scenario: Rich table list renders dash for non-read-only capability + Given there are mocked tools in the registry + When I run tool CLI list + Then the tool CLI output should contain "—" + + Scenario: Rich table list shows a Summary panel + Given there are mocked tools in the registry + When I run tool CLI list + Then the tool CLI output should contain "Summary" + # Tool show command tests Scenario: Show tool by name Given there is a mocked tool with name "local/test-tool" diff --git a/src/cleveragents/cli/commands/tool.py b/src/cleveragents/cli/commands/tool.py index a89936f59..5cb96e252 100644 --- a/src/cleveragents/cli/commands/tool.py +++ b/src/cleveragents/cli/commands/tool.py @@ -456,9 +456,7 @@ def list_tools( ) # Accumulate summary statistics - namespaces.add( - name_val.split("/", 1)[0] if "/" in name_val else name_val - ) + namespaces.add(name_val.split("/", 1)[0] if "/" in name_val else name_val) if tool_type_val == "validation": validation_count += 1 else: -- 2.52.0