fix(cli): allow global format flag after subcommands
CI / push-validation (pull_request) Successful in 17s
CI / typecheck (pull_request) Successful in 54s
CI / build (pull_request) Successful in 31s
CI / helm (pull_request) Successful in 39s
CI / e2e_tests (pull_request) Failing after 1m56s
CI / lint (pull_request) Successful in 3m22s
CI / quality (pull_request) Successful in 3m42s
CI / security (pull_request) Successful in 4m6s
CI / integration_tests (pull_request) Failing after 4m7s
CI / unit_tests (pull_request) Successful in 8m49s
CI / docker (pull_request) Successful in 1m30s
CI / coverage (pull_request) Successful in 10m28s
CI / status-check (pull_request) Failing after 1s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Successful in 57m13s

Closes #6879\n\nISSUES CLOSED: #6879
This commit is contained in:
2026-04-10 18:54:27 +00:00
parent 8b7bdb5edf
commit 214d5afee2
3 changed files with 105 additions and 0 deletions
+5
View File
@@ -147,6 +147,11 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
`sandbox_root=.cleveragents/sandbox/`, so LLM file output (`FILE:` blocks)
is written to disk during the execute phase. (#4222)
- **CLI global format flag parsing** (#6879): Promotes global CLI options before
Typer parsing so `agents <command> --format json` and `-f` shorthand work from
the documented position. Adds regression coverage for version, info, and
diagnostics commands.
- **Robot Framework TDD Listener Guards** (#5436): Added three guard conditions to the
`tdd_expected_fail_listener` `end_test()` function to prevent blindly inverting ALL test
failures to passes, which was masking infrastructure errors and causing flaky CI behavior.
+19
View File
@@ -44,6 +44,25 @@ Feature: Global --format flag propagation to subcommands
Then the global format command should succeed
And the global format output should be valid JSON
# -----------------------------------------------------------------------
# Regression: global --format accepted after the subcommand name
# -----------------------------------------------------------------------
Scenario: Global --format flag works after the info subcommand name
When I invoke the CLI with global args "info --format json"
Then the global format command should succeed
And the global format output should be valid JSON
Scenario: Global --format flag works after the version subcommand name
When I invoke the CLI with global args "version --format json"
Then the global format command should succeed
And the global format output should be valid JSON
Scenario: Global --format flag works after the diagnostics subcommand name
When I invoke the CLI with global args "diagnostics --format json"
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
# -----------------------------------------------------------------------
+81
View File
@@ -7,7 +7,9 @@ import sys
from pathlib import Path
from typing import TYPE_CHECKING, Annotated, Any
import click
import typer
from typer.core import TyperGroup
from cleveragents import __version__
from cleveragents.cli.formatting import OutputFormat
@@ -20,6 +22,81 @@ _console: "Console | None" = None
_err_console: "Console | None" = None
_subcommands_registered = False
_GLOBAL_VALUE_OPTIONS = frozenset({"--format"})
_GLOBAL_VALUE_SHORT_OPTIONS = frozenset({"-f"})
_GLOBAL_VALUE_EQ_PREFIXES = ("--format=",)
_GLOBAL_FLAG_OPTIONS = frozenset({"--show-secrets", "--version", "-V"})
def _split_short_value_option(option: str) -> tuple[str, str] | None:
for short in _GLOBAL_VALUE_SHORT_OPTIONS:
if option.startswith(short) and len(option) > len(short):
return short, option[len(short) :]
return None
def _promote_global_options(args: list[str]) -> list[str]:
"""Move global root options before subcommands for Typer parsing."""
if not args:
return args
promoted: list[str] = []
remaining: list[str] = []
i = 0
length = len(args)
while i < length:
arg = args[i]
if arg == "--":
remaining.extend(args[i:])
break
if arg in _GLOBAL_FLAG_OPTIONS:
promoted.append(arg)
i += 1
continue
if arg in _GLOBAL_VALUE_OPTIONS:
if i + 1 < length:
promoted.extend([arg, args[i + 1]])
i += 2
else:
remaining.append(arg)
i += 1
continue
eq_prefix = next(
(prefix for prefix in _GLOBAL_VALUE_EQ_PREFIXES if arg.startswith(prefix)),
None,
)
if eq_prefix is not None and len(arg) > len(eq_prefix):
promoted.extend([eq_prefix.rstrip("="), arg[len(eq_prefix) :]])
i += 1
continue
split_short = _split_short_value_option(arg)
if split_short is not None:
short_opt, value = split_short
promoted.extend([short_opt, value])
i += 1
continue
remaining.append(arg)
i += 1
return promoted + remaining
class CleverAgentsTyperGroup(TyperGroup):
"""Typer group that promotes global options before subcommand parsing."""
def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]:
promoted_args = _promote_global_options(list(args))
return super().parse_args(ctx, promoted_args)
__all__ = [
"_print_basic_help",
"app",
@@ -68,6 +145,7 @@ app: Any = typer.Typer(
pretty_exceptions_enable=True,
pretty_exceptions_show_locals=False,
rich_markup_mode="rich",
cls=CleverAgentsTyperGroup,
)
@@ -700,6 +778,9 @@ def main(args: list[str] | None = None) -> int:
if not args:
args = ["--help"]
# Global CLI options are promoted by CleverAgentsTyperGroup.parse_args,
# so we avoid reprocessing here to prevent double handling.
# Fast paths for lightweight flags to avoid heavy imports when using the
# real Typer app. If the app is monkeypatched (e.g., tests), fall back to
# normal execution so patched call paths are exercised.