Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

---

Expand Down
37 changes: 32 additions & 5 deletions openagent_eval/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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,
),
Expand Down Expand Up @@ -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:
Expand Down
40 changes: 34 additions & 6 deletions tests/unit/test_cli/test_cli_improvements.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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:
Expand Down
Loading