fix(cli): add Read-Only and Writes columns to tool list output #1476 #1509
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,29 +416,75 @@ 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("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)
|
||||
|
HAL9000
commented
Suggestion: Add Behave unit tests in features/ for list_tools() Rich rendering - covering 5 correct columns, checkmark/dash rendering from capability dict values, and Summary panel counts. This is a hard blocker per issue #1476 acceptance criteria. Suggestion: Add Behave unit tests in features/ for list_tools() Rich rendering - covering 5 correct columns, checkmark/dash rendering from capability dict values, and Summary panel counts. This is a hard blocker per issue #1476 acceptance criteria.
|
||||
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}",
|
||||
|
HAL9000
commented
Note: Summary panel includes Read-Only and Writes counts beyond spec Total/Tools/Validations/Namespaces. Useful enhancement but worth documenting as spec departure. Note: Summary panel includes Read-Only and Writes counts beyond spec Total/Tools/Validations/Namespaces. Useful enhancement but worth documenting as spec departure.
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user
[RUNTIME BUG] These two
add_column()calls are added butadd_row()below (lines ~428-434) still only passes 5 values for what is now a 7-column table. Rich assigns values positionally, so every column after Name will display the wrong data.Required changes:
DescriptionandTimeoutcolumns (not in spec)"✓"/"—"renderingadd_row()to pass exactly 5 values matching the 5 columns