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

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:
2026-04-05 08:39:14 +00:00
parent 1411adfed3
commit 4d31f0ed02
11 changed files with 524 additions and 58 deletions
+6 -6
View File
@@ -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:"
+156
View File
@@ -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
+8 -8
View File
@@ -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
View File
@@ -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
+49
View File
@@ -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
+1 -1
View File
@@ -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}
+1 -1
View File
@@ -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
+88
View File
@@ -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__":
+35 -23
View File
@@ -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))