fix(cli): promote --format to global CLI callback option per spec
CI / lint (pull_request) Successful in 21s
CI / quality (pull_request) Successful in 34s
CI / typecheck (pull_request) Successful in 51s
CI / security (pull_request) Successful in 1m0s
CI / build (pull_request) Successful in 19s
CI / helm (pull_request) Successful in 24s
CI / unit_tests (pull_request) Failing after 7m5s
CI / docker (pull_request) Has been skipped
CI / e2e_tests (pull_request) Successful in 16m51s
CI / integration_tests (pull_request) Successful in 23m16s
CI / coverage (pull_request) Successful in 10m39s
CI / status-check (pull_request) Failing after 1s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Successful in 56m46s
CI / lint (pull_request) Successful in 21s
CI / quality (pull_request) Successful in 34s
CI / typecheck (pull_request) Successful in 51s
CI / security (pull_request) Successful in 1m0s
CI / build (pull_request) Successful in 19s
CI / helm (pull_request) Successful in 24s
CI / unit_tests (pull_request) Failing after 7m5s
CI / docker (pull_request) Has been skipped
CI / e2e_tests (pull_request) Successful in 16m51s
CI / integration_tests (pull_request) Successful in 23m16s
CI / coverage (pull_request) Successful in 10m39s
CI / status-check (pull_request) Failing after 1s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Successful in 56m46s
Add `fmt: OutputFormat` parameter to `main_callback()` in
`src/cleveragents/cli/main.py` and store the selected format in
`ctx.obj["format"]` so all subcommands can read it without needing
their own per-command `--format` flag.
Remove per-command `--format` / `fmt` parameters from `version()`,
`info()`, and `diagnostics()` commands. These commands now read the
format from `ctx.obj.get("format", OutputFormat.RICH.value)`.
The specification states: "The framework supports six distinct output
formats, selectable via the global `--format` flag." This change
aligns the implementation with the spec by making `--format` a global
option on the root `agents` command (via the Typer callback).
All six formats (json, yaml, plain, rich, table, color) are supported
via the global flag and the `-f` shorthand.
Add Behave BDD scenarios covering global `--format` flag propagation
to subcommands for all six formats. Update Robot Framework integration
tests to exercise the global `--format` flag. Update existing tests
that used per-command `--format` for version/info/diagnostics to use
the global flag instead.
ISSUES CLOSED: #2908
This commit is contained in:
@@ -117,20 +117,20 @@ Feature: CLI consistency and UX polish
|
||||
# JSON/text format switching
|
||||
# -----------------------------------------------------------------
|
||||
|
||||
Scenario: Version command supports JSON format
|
||||
Scenario: Version command supports JSON format via global --format flag
|
||||
Given a CLI consistency test runner
|
||||
When I invoke the CLI with "version --format json"
|
||||
When I invoke the CLI with "--format json version"
|
||||
Then the invoked CLI exit code should be 0
|
||||
And the invoked CLI output should be valid JSON
|
||||
|
||||
Scenario: Version command supports YAML format
|
||||
Scenario: Version command supports YAML format via global --format flag
|
||||
Given a CLI consistency test runner
|
||||
When I invoke the CLI with "version --format yaml"
|
||||
When I invoke the CLI with "--format yaml version"
|
||||
Then the invoked CLI exit code should be 0
|
||||
And the invoked CLI output should contain "version:"
|
||||
|
||||
Scenario: Version command supports plain format
|
||||
Scenario: Version command supports plain format via global --format flag
|
||||
Given a CLI consistency test runner
|
||||
When I invoke the CLI with "version --format plain"
|
||||
When I invoke the CLI with "--format plain version"
|
||||
Then the invoked CLI exit code should be 0
|
||||
And the invoked CLI output should contain "version:"
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
Feature: Global --format flag propagation to subcommands
|
||||
As a developer using the CleverAgents CLI
|
||||
I want the --format flag to be a global option on the root `agents` command
|
||||
So that I can set the output format once and have it apply to all subcommands
|
||||
Per the specification: "The framework supports six distinct output formats,
|
||||
selectable via the global --format flag."
|
||||
|
||||
Background:
|
||||
Given the global format flag test runner is set up
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Subtask 1 & 2: main_callback accepts --format and stores in ctx.obj
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
Scenario: Global --format json flag is accepted by the root command
|
||||
When I invoke the CLI with global args "--format json version"
|
||||
Then the global format command should succeed
|
||||
And the global format output should be valid JSON
|
||||
|
||||
Scenario: Global --format yaml flag is accepted by the root command
|
||||
When I invoke the CLI with global args "--format yaml version"
|
||||
Then the global format command should succeed
|
||||
And the global format output should be valid YAML
|
||||
|
||||
Scenario: Global --format plain flag is accepted by the root command
|
||||
When I invoke the CLI with global args "--format plain version"
|
||||
Then the global format command should succeed
|
||||
And the global format output should contain plain text
|
||||
|
||||
Scenario: Global --format rich flag is accepted by the root command
|
||||
When I invoke the CLI with global args "--format rich version"
|
||||
Then the global format command should succeed
|
||||
|
||||
Scenario: Global --format table flag is accepted by the root command
|
||||
When I invoke the CLI with global args "--format table version"
|
||||
Then the global format command should succeed
|
||||
|
||||
Scenario: Global --format color flag is accepted by the root command
|
||||
When I invoke the CLI with global args "--format color version"
|
||||
Then the global format command should succeed
|
||||
|
||||
Scenario: Global -f shorthand flag is accepted by the root command
|
||||
When I invoke the CLI with global args "-f json version"
|
||||
Then the global format command should succeed
|
||||
And the global format output should be valid JSON
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Subtask 3: version, info, diagnostics read format from global ctx.obj
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
Scenario: version command reads format from global ctx.obj
|
||||
When I invoke the CLI with global args "--format json version"
|
||||
Then the global format command should succeed
|
||||
And the global format output should be valid JSON
|
||||
And the global format JSON output should contain key "version"
|
||||
|
||||
Scenario: info command reads format from global ctx.obj
|
||||
When I invoke the CLI with global args "--format json info"
|
||||
Then the global format command should succeed
|
||||
And the global format output should be valid JSON
|
||||
|
||||
Scenario: diagnostics command reads format from global ctx.obj
|
||||
When I invoke the CLI with global args "--format json diagnostics"
|
||||
Then the global format command should succeed
|
||||
And the global format output should be valid JSON
|
||||
|
||||
Scenario: version command defaults to rich format when no --format given
|
||||
When I invoke the CLI with global args "version"
|
||||
Then the global format command should succeed
|
||||
|
||||
Scenario: info command defaults to rich format when no --format given
|
||||
When I invoke the CLI with global args "info"
|
||||
Then the global format command should succeed
|
||||
|
||||
Scenario: diagnostics command defaults to rich format when no --format given
|
||||
When I invoke the CLI with global args "diagnostics"
|
||||
Then the global format command should succeed
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Subtask 4: ctx.obj["format"] is stored and accessible
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
Scenario: ctx.obj stores the selected format value json
|
||||
When I call main_callback directly with format "json"
|
||||
Then the context object should have format "json"
|
||||
|
||||
Scenario: ctx.obj stores yaml format value
|
||||
When I call main_callback directly with format "yaml"
|
||||
Then the context object should have format "yaml"
|
||||
|
||||
Scenario: ctx.obj stores plain format value
|
||||
When I call main_callback directly with format "plain"
|
||||
Then the context object should have format "plain"
|
||||
|
||||
Scenario: ctx.obj stores rich format value (default)
|
||||
When I call main_callback directly with format "rich"
|
||||
Then the context object should have format "rich"
|
||||
|
||||
Scenario: ctx.obj stores table format value
|
||||
When I call main_callback directly with format "table"
|
||||
Then the context object should have format "table"
|
||||
|
||||
Scenario: ctx.obj stores color format value
|
||||
When I call main_callback directly with format "color"
|
||||
Then the context object should have format "color"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# All six formats work end-to-end with version command
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
Scenario Outline: All six formats work with the version command via global flag
|
||||
When I invoke the CLI with global args "--format <format> version"
|
||||
Then the global format command should succeed
|
||||
|
||||
Examples:
|
||||
| format |
|
||||
| json |
|
||||
| yaml |
|
||||
| plain |
|
||||
| rich |
|
||||
| table |
|
||||
| color |
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# All six formats work end-to-end with info command
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
Scenario Outline: All six formats work with the info command via global flag
|
||||
When I invoke the CLI with global args "--format <format> info"
|
||||
Then the global format command should succeed
|
||||
|
||||
Examples:
|
||||
| format |
|
||||
| json |
|
||||
| yaml |
|
||||
| plain |
|
||||
| rich |
|
||||
| table |
|
||||
| color |
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# All six formats work end-to-end with diagnostics command
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
Scenario Outline: All six formats work with the diagnostics command via global flag
|
||||
When I invoke the CLI with global args "--format <format> diagnostics"
|
||||
Then the global format command should succeed
|
||||
|
||||
Examples:
|
||||
| format |
|
||||
| json |
|
||||
| yaml |
|
||||
| plain |
|
||||
| rich |
|
||||
| table |
|
||||
| color |
|
||||
@@ -0,0 +1,158 @@
|
||||
"""Step definitions for global --format flag propagation tests.
|
||||
|
||||
Tests that the --format flag is a global option on the root `agents` command
|
||||
(via the Typer callback), not a per-command option, per the specification.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
import yaml
|
||||
from behave import given, then, when
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from cleveragents.cli.main import app
|
||||
|
||||
|
||||
@given("the global format flag test runner is set up")
|
||||
def step_setup_runner(context: Any) -> None:
|
||||
"""Set up the test runner for global format flag tests."""
|
||||
context.runner = CliRunner()
|
||||
context.cli_result = None
|
||||
context.ctx_obj = {}
|
||||
|
||||
|
||||
def _invoke_with_mocked_system(runner: CliRunner, args: list[str]) -> Any:
|
||||
"""Invoke the CLI app with mocked system commands."""
|
||||
with (
|
||||
patch(
|
||||
"cleveragents.cli.commands.system.build_version_data",
|
||||
return_value={
|
||||
"version": "3.7.0",
|
||||
"python": "3.12.0",
|
||||
"platform": "linux",
|
||||
},
|
||||
),
|
||||
patch(
|
||||
"cleveragents.cli.commands.system.render_version_rich",
|
||||
),
|
||||
patch(
|
||||
"cleveragents.cli.commands.system.build_info_data",
|
||||
return_value={
|
||||
"version": "3.7.0",
|
||||
"config_path": "/tmp/config.toml",
|
||||
"db_url": "sqlite:///test.db",
|
||||
},
|
||||
),
|
||||
patch(
|
||||
"cleveragents.cli.commands.system.render_info_rich",
|
||||
),
|
||||
patch(
|
||||
"cleveragents.cli.commands.system.build_diagnostics_data",
|
||||
return_value={
|
||||
"checks": [],
|
||||
"has_errors": False,
|
||||
},
|
||||
),
|
||||
patch(
|
||||
"cleveragents.cli.commands.system.render_diagnostics_rich",
|
||||
),
|
||||
):
|
||||
return runner.invoke(app, args)
|
||||
|
||||
|
||||
@when('I invoke the CLI with global args "{args_str}"')
|
||||
def step_invoke_cli_global_args(context: Any, args_str: str) -> None:
|
||||
"""Invoke the CLI with the given argument string."""
|
||||
args = args_str.split()
|
||||
result = _invoke_with_mocked_system(context.runner, args)
|
||||
context.cli_result = result
|
||||
context.cli_output = result.output
|
||||
|
||||
|
||||
@then("the global format command should succeed")
|
||||
def step_global_format_command_succeeds(context: Any) -> None:
|
||||
"""Verify the command exited with code 0."""
|
||||
result = context.cli_result
|
||||
assert result.exit_code == 0, (
|
||||
f"Expected exit code 0, got {result.exit_code}.\n"
|
||||
f"Output: {result.output}\n"
|
||||
f"Exception: {result.exception}"
|
||||
)
|
||||
|
||||
|
||||
@then("the global format output should be valid JSON")
|
||||
def step_global_format_output_is_valid_json(context: Any) -> None:
|
||||
"""Verify the output is parseable JSON."""
|
||||
output = context.cli_output.strip()
|
||||
try:
|
||||
parsed = json.loads(output)
|
||||
context.parsed_json = parsed
|
||||
except json.JSONDecodeError as exc:
|
||||
raise AssertionError(
|
||||
f"Output is not valid JSON: {exc}\nOutput was: {output!r}"
|
||||
) from exc
|
||||
|
||||
|
||||
@then("the global format output should be valid YAML")
|
||||
def step_global_format_output_is_valid_yaml(context: Any) -> None:
|
||||
"""Verify the output is parseable YAML."""
|
||||
output = context.cli_output.strip()
|
||||
try:
|
||||
parsed = yaml.safe_load(output)
|
||||
context.parsed_yaml = parsed
|
||||
except yaml.YAMLError as exc:
|
||||
raise AssertionError(
|
||||
f"Output is not valid YAML: {exc}\nOutput was: {output!r}"
|
||||
) from exc
|
||||
|
||||
|
||||
@then("the global format output should contain plain text")
|
||||
def step_global_format_output_contains_plain_text(context: Any) -> None:
|
||||
"""Verify the output contains plain key: value text."""
|
||||
output = context.cli_output.strip()
|
||||
assert output, f"Expected non-empty plain text output, got: {output!r}"
|
||||
|
||||
|
||||
@then('the global format JSON output should contain key "{key}"')
|
||||
def step_global_format_json_output_contains_key(context: Any, key: str) -> None:
|
||||
"""Verify the parsed JSON output contains the given key."""
|
||||
parsed = getattr(context, "parsed_json", None)
|
||||
if parsed is None:
|
||||
output = context.cli_output.strip()
|
||||
parsed = json.loads(output)
|
||||
assert key in parsed, (
|
||||
f"Expected key '{key}' in JSON output. Keys found: {list(parsed.keys())}"
|
||||
)
|
||||
|
||||
|
||||
@when('I call main_callback directly with format "{fmt}"')
|
||||
def step_call_main_callback_directly(context: Any, fmt: str) -> None:
|
||||
"""Call main_callback via the CLI runner to test ctx.obj storage."""
|
||||
result = _invoke_with_mocked_system(context.runner, ["--format", fmt, "version"])
|
||||
context.cli_result = result
|
||||
context.captured_fmt = fmt
|
||||
|
||||
|
||||
@then('the context object should have format "{expected_fmt}"')
|
||||
def step_ctx_obj_has_format(context: Any, expected_fmt: str) -> None:
|
||||
"""Verify the format was stored correctly.
|
||||
|
||||
Since we can't easily inspect ctx.obj after the fact without modifying
|
||||
the command, we verify indirectly: the command succeeded and the format
|
||||
was accepted (no 'Invalid value' error for the format option).
|
||||
"""
|
||||
result = context.cli_result
|
||||
# The command should succeed (format was accepted)
|
||||
assert result.exit_code == 0, (
|
||||
f"Expected exit code 0 for format '{expected_fmt}', "
|
||||
f"got {result.exit_code}.\nOutput: {result.output}"
|
||||
)
|
||||
# Verify no "Invalid value" error for the format option
|
||||
assert "Invalid value" not in result.output, (
|
||||
f"Got 'Invalid value' error for format '{expected_fmt}': {result.output}"
|
||||
)
|
||||
context.verified_fmt = expected_fmt
|
||||
@@ -61,7 +61,8 @@ def step_version_non_rich(context: Any, fmt: str) -> None:
|
||||
return_value="formatted-version",
|
||||
) as mock_fmt,
|
||||
):
|
||||
result = runner.invoke(app, ["version", "--format", fmt])
|
||||
# --format is now a global flag on the root command, not per-command
|
||||
result = runner.invoke(app, ["--format", fmt, "version"])
|
||||
context.cli_result = result
|
||||
context.mock_format_output = mock_fmt
|
||||
context.mock_render_rich = mock_render
|
||||
@@ -99,7 +100,8 @@ def step_info_non_rich(context: Any, fmt: str) -> None:
|
||||
return_value="formatted-info",
|
||||
) as mock_fmt,
|
||||
):
|
||||
result = runner.invoke(app, ["info", "--format", fmt])
|
||||
# --format is now a global flag on the root command, not per-command
|
||||
result = runner.invoke(app, ["--format", fmt, "info"])
|
||||
context.cli_result = result
|
||||
context.mock_format_output = mock_fmt
|
||||
context.mock_render_rich = mock_render
|
||||
@@ -136,7 +138,8 @@ def step_diagnostics_non_rich(context: Any, fmt: str) -> None:
|
||||
return_value="formatted-diag",
|
||||
) as mock_fmt,
|
||||
):
|
||||
result = runner.invoke(app, ["diagnostics", "--format", fmt])
|
||||
# --format is now a global flag on the root command, not per-command
|
||||
result = runner.invoke(app, ["--format", fmt, "diagnostics"])
|
||||
context.cli_result = result
|
||||
context.mock_format_output = mock_fmt
|
||||
context.mock_render_rich = mock_render
|
||||
|
||||
@@ -78,23 +78,23 @@ Completion Rejects Unsupported Shell
|
||||
Should Be Equal As Integers ${result.rc} 2
|
||||
|
||||
Version JSON Format Output
|
||||
[Documentation] version --format json should produce valid JSON
|
||||
${result}= Run Process ${PYTHON} -m cleveragents version --format json
|
||||
[Documentation] --format json version should produce valid JSON (global flag)
|
||||
${result}= Run Process ${PYTHON} -m cleveragents --format json version
|
||||
... timeout=120s on_timeout=kill
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
${valid}= Is Valid Json ${result.stdout}
|
||||
Should Be True ${valid} Output is not valid JSON: ${result.stdout}
|
||||
|
||||
Version YAML Format Output
|
||||
[Documentation] version --format yaml should produce YAML with version key
|
||||
${result}= Run Process ${PYTHON} -m cleveragents version --format yaml
|
||||
[Documentation] --format yaml version should produce YAML with version key (global flag)
|
||||
${result}= Run Process ${PYTHON} -m cleveragents --format yaml version
|
||||
... timeout=120s on_timeout=kill
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} version:
|
||||
|
||||
Version Plain Format Output
|
||||
[Documentation] version --format plain should produce plain key-value output
|
||||
${result}= Run Process ${PYTHON} -m cleveragents version --format plain
|
||||
[Documentation] --format plain version should produce plain key-value output (global flag)
|
||||
${result}= Run Process ${PYTHON} -m cleveragents --format plain version
|
||||
... timeout=120s on_timeout=kill
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} version:
|
||||
@@ -114,8 +114,8 @@ CLI Not Found Format Is Standardized
|
||||
Should Contain ${result}[stdout] Hint:
|
||||
|
||||
Info Command JSON Format
|
||||
[Documentation] info --format json should produce valid JSON
|
||||
${result}= Run Process ${PYTHON} -m cleveragents info --format json
|
||||
[Documentation] --format json info should produce valid JSON (global flag)
|
||||
${result}= Run Process ${PYTHON} -m cleveragents --format json info
|
||||
... timeout=120s on_timeout=kill
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
${valid}= Is Valid Json ${result.stdout}
|
||||
|
||||
+16
-16
@@ -28,8 +28,8 @@ Version Command Default Rich Format
|
||||
Should Contain ${result.stdout} CleverAgents
|
||||
|
||||
Version Command JSON Format
|
||||
[Documentation] Version command with --format json returns valid JSON
|
||||
${result}= Run Process ${PYTHON} -m cleveragents version --format json timeout=120s on_timeout=kill
|
||||
[Documentation] Version command with global --format json returns valid JSON
|
||||
${result}= Run Process ${PYTHON} -m cleveragents --format json version timeout=120s on_timeout=kill
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} "version": "1.0.0"
|
||||
Should Contain ${result.stdout} "schema": "v3"
|
||||
@@ -40,15 +40,15 @@ Version Command JSON Format
|
||||
Should Contain ${result.stdout} "commit"
|
||||
|
||||
Version Command Plain Format
|
||||
[Documentation] Version command with --format plain returns key-value pairs
|
||||
${result}= Run Process ${PYTHON} -m cleveragents version --format plain timeout=120s on_timeout=kill
|
||||
[Documentation] Version command with global --format plain returns key-value pairs
|
||||
${result}= Run Process ${PYTHON} -m cleveragents --format plain version timeout=120s on_timeout=kill
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} version: 1.0.0
|
||||
Should Contain ${result.stdout} schema: v3
|
||||
|
||||
Version Command YAML Format
|
||||
[Documentation] Version command with --format yaml returns YAML
|
||||
${result}= Run Process ${PYTHON} -m cleveragents version --format yaml timeout=120s on_timeout=kill
|
||||
[Documentation] Version command with global --format yaml returns YAML
|
||||
${result}= Run Process ${PYTHON} -m cleveragents --format yaml version timeout=120s on_timeout=kill
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} version: 1.0.0
|
||||
Should Contain ${result.stdout} schema: v3
|
||||
@@ -61,8 +61,8 @@ Info Command Default Rich Format
|
||||
Should Contain ${result.stdout} Runtime
|
||||
|
||||
Info Command JSON Format
|
||||
[Documentation] Info command with --format json returns structured data
|
||||
${result}= Run CLI With Clean Home ${PYTHON} -m cleveragents info --format json
|
||||
[Documentation] Info command with global --format json returns structured data
|
||||
${result}= Run CLI With Clean Home ${PYTHON} -m cleveragents --format json info
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} "version": "1.0.0"
|
||||
Should Contain ${result.stdout} "data_dir"
|
||||
@@ -70,8 +70,8 @@ Info Command JSON Format
|
||||
Should Contain ${result.stdout} "server_mode"
|
||||
|
||||
Info Command Plain Format
|
||||
[Documentation] Info command with --format plain returns key-value pairs
|
||||
${result}= Run CLI With Clean Home ${PYTHON} -m cleveragents info --format plain
|
||||
[Documentation] Info command with global --format plain returns key-value pairs
|
||||
${result}= Run CLI With Clean Home ${PYTHON} -m cleveragents --format plain info
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} version: 1.0.0
|
||||
Should Contain ${result.stdout} server_mode:
|
||||
@@ -84,8 +84,8 @@ Diagnostics Command Default Rich Format
|
||||
Should Contain ${result.stdout} Summary
|
||||
|
||||
Diagnostics Command JSON Format
|
||||
[Documentation] Diagnostics command with --format json returns structured data
|
||||
${result}= Run Process ${PYTHON} -m cleveragents diagnostics --format json timeout=120s on_timeout=kill
|
||||
[Documentation] Diagnostics command with global --format json returns structured data
|
||||
${result}= Run Process ${PYTHON} -m cleveragents --format json diagnostics timeout=120s on_timeout=kill
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} "checks"
|
||||
Should Contain ${result.stdout} "summary"
|
||||
@@ -95,8 +95,8 @@ Diagnostics Command JSON Format
|
||||
Should Contain ${result.stdout} "errors"
|
||||
|
||||
Diagnostics Command Plain Format
|
||||
[Documentation] Diagnostics command with --format plain returns key-value pairs
|
||||
${result}= Run Process ${PYTHON} -m cleveragents diagnostics --format plain timeout=120s on_timeout=kill
|
||||
[Documentation] Diagnostics command with global --format plain returns key-value pairs
|
||||
${result}= Run Process ${PYTHON} -m cleveragents --format plain diagnostics timeout=120s on_timeout=kill
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} checks:
|
||||
Should Contain ${result.stdout} summary:
|
||||
@@ -104,7 +104,7 @@ Diagnostics Command Plain Format
|
||||
Diagnostics Command Check Flag Returns Valid Exit Code
|
||||
[Documentation] Diagnostics --check exits 0 when no critical errors are found.
|
||||
... Disk-space errors are tolerated since CI runners may have low disk.
|
||||
${result}= Run Process ${PYTHON} -m cleveragents diagnostics --check --format json timeout=120s on_timeout=kill
|
||||
${result}= Run Process ${PYTHON} -m cleveragents --format json diagnostics --check timeout=120s on_timeout=kill
|
||||
Should Contain ${result.stdout} "checks"
|
||||
Should Contain ${result.stdout} "has_errors"
|
||||
# Accept exit code 0 (clean) or 1 if the only error is disk space (CI environment)
|
||||
@@ -115,7 +115,7 @@ Diagnostics Command Check Flag Returns Valid Exit Code
|
||||
Diagnostics Command Performance
|
||||
[Documentation] Diagnostics command completes within acceptable time
|
||||
${start}= Get Time epoch
|
||||
${result}= Run Process ${PYTHON} -m cleveragents diagnostics --format json timeout=120s on_timeout=kill
|
||||
${result}= Run Process ${PYTHON} -m cleveragents --format json diagnostics timeout=120s on_timeout=kill
|
||||
${end}= Get Time epoch
|
||||
${duration}= Evaluate ${end} - ${start}
|
||||
Should Be True ${duration} < 90 Diagnostics took too long: ${duration}s
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
*** Settings ***
|
||||
Documentation Smoke tests for CLI --format flag parity (json/yaml/plain/table/rich)
|
||||
... and global --format flag propagation to subcommands.
|
||||
Resource ${CURDIR}/common.resource
|
||||
Suite Setup Setup Test Environment
|
||||
Suite Teardown Cleanup Test Environment
|
||||
@@ -33,3 +34,51 @@ Plan Status Format Plain Outputs Key-Value Pairs
|
||||
${result}= Run Process ${PYTHON} ${HELPER} plan-status-plain cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} cli-formats-plan-status-plain-ok
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Global --format flag propagation tests (Issue #2908)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Global Format Flag JSON Propagates To Version Command
|
||||
[Documentation] Verify that global ``--format json`` propagates to the version command
|
||||
${result}= Run Process ${PYTHON} ${HELPER} global-format-json-version cwd=${WORKSPACE}
|
||||
Log ${result.stdout}
|
||||
Log ${result.stderr}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} cli-global-format-json-version-ok
|
||||
|
||||
Global Format Flag YAML Propagates To Version Command
|
||||
[Documentation] Verify that global ``--format yaml`` propagates to the version command
|
||||
${result}= Run Process ${PYTHON} ${HELPER} global-format-yaml-version cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} cli-global-format-yaml-version-ok
|
||||
|
||||
Global Format Flag Plain Propagates To Version Command
|
||||
[Documentation] Verify that global ``--format plain`` propagates to the version command
|
||||
${result}= Run Process ${PYTHON} ${HELPER} global-format-plain-version cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} cli-global-format-plain-version-ok
|
||||
|
||||
Global Format Flag JSON Propagates To Info Command
|
||||
[Documentation] Verify that global ``--format json`` propagates to the info command
|
||||
${result}= Run Process ${PYTHON} ${HELPER} global-format-json-info cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} cli-global-format-json-info-ok
|
||||
|
||||
Global Format Flag JSON Propagates To Diagnostics Command
|
||||
[Documentation] Verify that global ``--format json`` propagates to the diagnostics command
|
||||
${result}= Run Process ${PYTHON} ${HELPER} global-format-json-diagnostics cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} cli-global-format-json-diagnostics-ok
|
||||
|
||||
Global Format Shorthand Flag Works
|
||||
[Documentation] Verify that global ``-f json`` shorthand propagates to the version command
|
||||
${result}= Run Process ${PYTHON} ${HELPER} global-format-shorthand cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} cli-global-format-shorthand-ok
|
||||
|
||||
All Six Formats Work With Version Command
|
||||
[Documentation] Verify all six output formats work via the global --format flag
|
||||
${result}= Run Process ${PYTHON} ${HELPER} global-format-all-six cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} cli-global-format-all-six-ok
|
||||
|
||||
@@ -264,7 +264,7 @@ WF07 E2E JSON Output Verification
|
||||
... output from the version command.
|
||||
[Tags] E2E
|
||||
[Teardown] Log JSON Output Verification teardown complete
|
||||
${result}= Run CleverAgents Command version --format json
|
||||
${result}= Run CleverAgents Command --format json version
|
||||
Should Not Be Empty ${result.stdout}
|
||||
# Parse as JSON and verify structure
|
||||
${stdout}= Set Variable ${result.stdout}
|
||||
|
||||
@@ -59,7 +59,7 @@ WF14 E2E Diagnostics
|
||||
... without a live server. Server-mode diagnostics (Server
|
||||
... connectivity, Namespace membership) shown in the spec are
|
||||
... not yet implemented in the diagnostics command (spec gap).
|
||||
${result}= Run CleverAgents Command diagnostics --format plain expected_rc=0
|
||||
${result}= Run CleverAgents Command --format plain diagnostics expected_rc=0
|
||||
# Basic diagnostic categories should appear in the output
|
||||
${combined}= Set Variable ${result.stdout}\n${result.stderr}
|
||||
Should Contain Any ${combined} config Config configuration
|
||||
|
||||
@@ -133,11 +133,99 @@ def plan_status_plain() -> None:
|
||||
print("cli-formats-plan-status-plain-ok")
|
||||
|
||||
|
||||
def global_format_json_version() -> None:
|
||||
"""Test global --format json propagates to version command."""
|
||||
from cleveragents.cli.main import app as main_app
|
||||
|
||||
result = runner.invoke(main_app, ["--format", "json", "version"])
|
||||
assert result.exit_code == 0, f"exit={result.exit_code}: {result.output}"
|
||||
parsed = json.loads(result.output.strip())
|
||||
assert isinstance(parsed, dict)
|
||||
assert "version" in parsed
|
||||
print("cli-global-format-json-version-ok")
|
||||
|
||||
|
||||
def global_format_yaml_version() -> None:
|
||||
"""Test global --format yaml propagates to version command."""
|
||||
from cleveragents.cli.main import app as main_app
|
||||
|
||||
result = runner.invoke(main_app, ["--format", "yaml", "version"])
|
||||
assert result.exit_code == 0, f"exit={result.exit_code}: {result.output}"
|
||||
parsed = yaml.safe_load(result.output.strip())
|
||||
assert isinstance(parsed, dict)
|
||||
assert "version" in parsed
|
||||
print("cli-global-format-yaml-version-ok")
|
||||
|
||||
|
||||
def global_format_plain_version() -> None:
|
||||
"""Test global --format plain propagates to version command."""
|
||||
from cleveragents.cli.main import app as main_app
|
||||
|
||||
result = runner.invoke(main_app, ["--format", "plain", "version"])
|
||||
assert result.exit_code == 0, f"exit={result.exit_code}: {result.output}"
|
||||
assert "version:" in result.output
|
||||
print("cli-global-format-plain-version-ok")
|
||||
|
||||
|
||||
def global_format_json_info() -> None:
|
||||
"""Test global --format json propagates to info command."""
|
||||
from cleveragents.cli.main import app as main_app
|
||||
|
||||
result = runner.invoke(main_app, ["--format", "json", "info"])
|
||||
assert result.exit_code == 0, f"exit={result.exit_code}: {result.output}"
|
||||
parsed = json.loads(result.output.strip())
|
||||
assert isinstance(parsed, dict)
|
||||
print("cli-global-format-json-info-ok")
|
||||
|
||||
|
||||
def global_format_json_diagnostics() -> None:
|
||||
"""Test global --format json propagates to diagnostics command."""
|
||||
from cleveragents.cli.main import app as main_app
|
||||
|
||||
result = runner.invoke(main_app, ["--format", "json", "diagnostics"])
|
||||
assert result.exit_code == 0, f"exit={result.exit_code}: {result.output}"
|
||||
parsed = json.loads(result.output.strip())
|
||||
assert isinstance(parsed, dict)
|
||||
print("cli-global-format-json-diagnostics-ok")
|
||||
|
||||
|
||||
def global_format_shorthand() -> None:
|
||||
"""Test global -f json shorthand propagates to version command."""
|
||||
from cleveragents.cli.main import app as main_app
|
||||
|
||||
result = runner.invoke(main_app, ["-f", "json", "version"])
|
||||
assert result.exit_code == 0, f"exit={result.exit_code}: {result.output}"
|
||||
parsed = json.loads(result.output.strip())
|
||||
assert isinstance(parsed, dict)
|
||||
assert "version" in parsed
|
||||
print("cli-global-format-shorthand-ok")
|
||||
|
||||
|
||||
def global_format_all_six() -> None:
|
||||
"""Test all six output formats work via the global --format flag."""
|
||||
from cleveragents.cli.main import app as main_app
|
||||
|
||||
formats = ["json", "yaml", "plain", "rich", "table", "color"]
|
||||
for fmt in formats:
|
||||
result = runner.invoke(main_app, ["--format", fmt, "version"])
|
||||
assert result.exit_code == 0, (
|
||||
f"Format '{fmt}' failed: exit={result.exit_code}: {result.output}"
|
||||
)
|
||||
print("cli-global-format-all-six-ok")
|
||||
|
||||
|
||||
_COMMANDS = {
|
||||
"action-list-json": action_list_json,
|
||||
"action-show-yaml": action_show_yaml,
|
||||
"plan-list-json": plan_list_json,
|
||||
"plan-status-plain": plan_status_plain,
|
||||
"global-format-json-version": global_format_json_version,
|
||||
"global-format-yaml-version": global_format_yaml_version,
|
||||
"global-format-plain-version": global_format_plain_version,
|
||||
"global-format-json-info": global_format_json_info,
|
||||
"global-format-json-diagnostics": global_format_json_diagnostics,
|
||||
"global-format-shorthand": global_format_shorthand,
|
||||
"global-format-all-six": global_format_all_six,
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, Annotated, Any
|
||||
import typer
|
||||
|
||||
from cleveragents import __version__
|
||||
from cleveragents.cli.formatting import OutputFormat
|
||||
from cleveragents.core.exceptions import CleverAgentsError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -291,6 +292,7 @@ def show_secrets_callback(value: bool) -> None:
|
||||
|
||||
@app.callback()
|
||||
def main_callback(
|
||||
ctx: typer.Context,
|
||||
version: bool = typer.Option(
|
||||
None,
|
||||
"--version",
|
||||
@@ -305,6 +307,17 @@ def main_callback(
|
||||
is_eager=True,
|
||||
help="Reveal secrets in CLI output (default: masked)",
|
||||
),
|
||||
fmt: Annotated[
|
||||
OutputFormat,
|
||||
typer.Option(
|
||||
"--format",
|
||||
"-f",
|
||||
help=(
|
||||
"Output format for all subcommands: "
|
||||
"rich, color, table, plain, json, or yaml (default: rich)"
|
||||
),
|
||||
),
|
||||
] = OutputFormat.RICH,
|
||||
) -> None:
|
||||
"""CleverAgents - AI-powered development assistant."""
|
||||
# Suppress debug-level logs on stdout for ALL commands so machine-readable
|
||||
@@ -315,15 +328,15 @@ def main_callback(
|
||||
configure_structlog(log_level="WARNING")
|
||||
_register_subcommands()
|
||||
|
||||
# Store the selected output format in the Typer context so all subcommands
|
||||
# can read it via ctx.obj["format"] without needing their own --format flag.
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj["format"] = fmt.value
|
||||
|
||||
|
||||
@app.command()
|
||||
def version(
|
||||
fmt: str = typer.Option(
|
||||
"rich",
|
||||
"--format",
|
||||
"-f",
|
||||
help="Output format: rich, plain, json, yaml",
|
||||
),
|
||||
ctx: typer.Context,
|
||||
) -> None:
|
||||
"""Display version information."""
|
||||
from cleveragents.cli.commands.system import (
|
||||
@@ -332,8 +345,11 @@ def version(
|
||||
)
|
||||
from cleveragents.cli.formatting import format_output
|
||||
|
||||
ctx.ensure_object(dict)
|
||||
fmt: str = ctx.obj.get("format", OutputFormat.RICH.value)
|
||||
|
||||
data = build_version_data()
|
||||
if fmt.lower() == "rich":
|
||||
if fmt.lower() == OutputFormat.RICH.value:
|
||||
render_version_rich(data)
|
||||
else:
|
||||
typer.echo(format_output(data, fmt))
|
||||
@@ -341,18 +357,16 @@ def version(
|
||||
|
||||
@app.command()
|
||||
def info(
|
||||
fmt: str = typer.Option(
|
||||
"rich",
|
||||
"--format",
|
||||
"-f",
|
||||
help="Output format: rich, plain, json, yaml",
|
||||
),
|
||||
ctx: typer.Context,
|
||||
) -> None:
|
||||
"""Display system information and configuration."""
|
||||
from cleveragents.cli.commands.system import build_info_data, render_info_rich
|
||||
from cleveragents.cli.formatting import format_output
|
||||
|
||||
if fmt.lower() != "rich":
|
||||
ctx.ensure_object(dict)
|
||||
fmt: str = ctx.obj.get("format", OutputFormat.RICH.value)
|
||||
|
||||
if fmt.lower() != OutputFormat.RICH.value:
|
||||
# Suppress debug-level log output so machine-readable formats
|
||||
# (json, yaml, plain) receive clean stdout without log lines mixed in.
|
||||
from cleveragents.config.logging import configure_structlog
|
||||
@@ -360,7 +374,7 @@ def info(
|
||||
configure_structlog(log_level="WARNING")
|
||||
|
||||
data = build_info_data()
|
||||
if fmt.lower() == "rich":
|
||||
if fmt.lower() == OutputFormat.RICH.value:
|
||||
render_info_rich(data)
|
||||
else:
|
||||
typer.echo(format_output(data, fmt))
|
||||
@@ -368,17 +382,12 @@ def info(
|
||||
|
||||
@app.command()
|
||||
def diagnostics(
|
||||
ctx: typer.Context,
|
||||
check: bool = typer.Option(
|
||||
False,
|
||||
"--check",
|
||||
help="Exit non-zero when any check fails",
|
||||
),
|
||||
fmt: str = typer.Option(
|
||||
"rich",
|
||||
"--format",
|
||||
"-f",
|
||||
help="Output format: rich, plain, json, yaml",
|
||||
),
|
||||
) -> None:
|
||||
"""Run system diagnostics and health checks."""
|
||||
from cleveragents.cli.commands.system import (
|
||||
@@ -387,13 +396,16 @@ def diagnostics(
|
||||
)
|
||||
from cleveragents.cli.formatting import format_output
|
||||
|
||||
if fmt.lower() != "rich":
|
||||
ctx.ensure_object(dict)
|
||||
fmt: str = ctx.obj.get("format", OutputFormat.RICH.value)
|
||||
|
||||
if fmt.lower() != OutputFormat.RICH.value:
|
||||
from cleveragents.config.logging import configure_structlog
|
||||
|
||||
configure_structlog(log_level="WARNING")
|
||||
|
||||
data = build_diagnostics_data()
|
||||
if fmt.lower() == "rich":
|
||||
if fmt.lower() == OutputFormat.RICH.value:
|
||||
render_diagnostics_rich(data)
|
||||
else:
|
||||
typer.echo(format_output(data, fmt))
|
||||
|
||||
Reference in New Issue
Block a user