diff --git a/CHANGELOG.md b/CHANGELOG.md index d4d8d55..fc0b4b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] -No changes yet. +### Fixed + +- Add consistent `--version` flag to all CLI commands (#125). --- diff --git a/openagent_eval/cli/main.py b/openagent_eval/cli/main.py index 4cd0adc..adf096b 100644 --- a/openagent_eval/cli/main.py +++ b/openagent_eval/cli/main.py @@ -3,11 +3,14 @@ from __future__ import annotations import sys +from importlib.metadata import PackageNotFoundError, version import typer from rich.console import Console +from typer.core import TyperGroup from openagent_eval.cli.banner import create_mini_banner +from openagent_eval.cli.commands.audit import audit_command from openagent_eval.cli.commands.compare import compare_command from openagent_eval.cli.commands.delete import delete_command from openagent_eval.cli.commands.diagnose import diagnose_command @@ -19,9 +22,7 @@ from openagent_eval.cli.commands.synth import synth_command from openagent_eval.cli.commands.test import test_command from openagent_eval.cli.commands.validate import validate_command -from openagent_eval.cli.commands.audit import audit_command from openagent_eval.cli.context import CLIContext, set_context -from openagent_eval.cli.utils.callbacks import version_callback from openagent_eval.exceptions import OpenAgentEvalError # Error code mapping for different exception types @@ -33,7 +34,31 @@ "CorpusError": 6, } + +def version_callback(value: bool) -> None: + if value: + try: + v = version("openagent-eval") + except PackageNotFoundError: + v = "unknown" + typer.echo(f"openagent-eval {v}") + raise typer.Exit() + + +class VersionAwareGroup(TyperGroup): + def parse_args(self, ctx, args): + if args: + version_flags = {"--version", "-V"} + for flag in version_flags: + if flag in args: + # Reconstruct args with the version flag at the beginning + args = [flag] + [arg for arg in args if arg != flag] + break + return super().parse_args(ctx, args) + + app = typer.Typer( + cls=VersionAwareGroup, name="oaeval", help="Open-source CLI framework for evaluating RAG systems and AI Agents.", no_args_is_help=True, @@ -45,10 +70,10 @@ @app.callback(invoke_without_command=True) def main( version: bool = typer.Option( - False, + None, "--version", "-V", - help="Show version and exit.", + help="Show the application's version and exit.", callback=version_callback, is_eager=True, ), @@ -454,7 +479,9 @@ def _generate_fish_completion() -> str: # Override the default exception handler -def _cli_exception_handler(exc_type: type, exc_value: BaseException, exc_tb: object) -> None: +def _cli_exception_handler( + exc_type: type, exc_value: BaseException, exc_tb: object +) -> None: """Custom exception handler for CLI. Args: diff --git a/tests/unit/test_cli/test_cli_improvements.py b/tests/unit/test_cli/test_cli_improvements.py index 91c2e94..13247ae 100644 --- a/tests/unit/test_cli/test_cli_improvements.py +++ b/tests/unit/test_cli/test_cli_improvements.py @@ -23,8 +23,8 @@ def strip_ansi(text: str) -> str: """Strip ANSI escape codes from text.""" - ansi_escape = re.compile(r'\x1b\[[0-9;]*m') - return ansi_escape.sub('', text) + ansi_escape = re.compile(r"\x1b\[[0-9;]*m") + return ansi_escape.sub("", text) class TestGlobalFlags: @@ -120,7 +120,11 @@ def test_delete_nonexistent_report(self): """Test delete with nonexistent report ID.""" result = runner.invoke(app, ["delete", "nonexistent_report"]) # Should fail or show error - assert result.exit_code != 0 or "Error" in result.output or "not found" in result.output + assert ( + result.exit_code != 0 + or "Error" in result.output + or "not found" in result.output + ) class TestCompletionCommand: @@ -252,7 +256,9 @@ def test_find_config_file_found(self, tmp_path): from openagent_eval.cli.utils.discovery import find_config_file config_file = tmp_path / "config.yaml" - config_file.write_text("dataset: test.yaml\nllm:\n provider: openai\n model: gpt-4") + config_file.write_text( + "dataset: test.yaml\nllm:\n provider: openai\n model: gpt-4" + ) result = find_config_file(tmp_path) assert result == config_file @@ -261,7 +267,9 @@ def test_find_config_file_oaeval_name(self, tmp_path): from openagent_eval.cli.utils.discovery import find_config_file config_file = tmp_path / "oaeval.yaml" - config_file.write_text("dataset: test.yaml\nllm:\n provider: openai\n model: gpt-4") + config_file.write_text( + "dataset: test.yaml\nllm:\n provider: openai\n model: gpt-4" + ) result = find_config_file(tmp_path) assert result == config_file @@ -271,13 +279,33 @@ class TestVersionCommand: def test_version_short_flag(self): """Test --version flag.""" + from importlib.metadata import version + result = runner.invoke(app, ["--version"]) assert result.exit_code == 0 + assert result.output.strip() == f"openagent-eval {version('openagent-eval')}" - def test_version_V_flag(self): + def test_version_V_flag(self): # noqa: N802 """Test -V flag.""" + from importlib.metadata import version + result = runner.invoke(app, ["-V"]) assert result.exit_code == 0 + assert result.output.strip() == f"openagent-eval {version('openagent-eval')}" + + def test_version_on_subcommand(self): + """Test --version flag on subcommands.""" + from importlib.metadata import version + + expected = f"openagent-eval {version('openagent-eval')}" + + result_run = runner.invoke(app, ["run", "--version"]) + assert result_run.exit_code == 0 + assert result_run.output.strip() == expected + + result_init = runner.invoke(app, ["init", "-V"]) + assert result_init.exit_code == 0 + assert result_init.output.strip() == expected class TestHelpOutput: