diff --git a/projects/openshell-agent-runner/docs/reference.md b/projects/openshell-agent-runner/docs/reference.md index 8cd7a268..30e87c4d 100644 --- a/projects/openshell-agent-runner/docs/reference.md +++ b/projects/openshell-agent-runner/docs/reference.md @@ -10,6 +10,7 @@ agent_markdown: true | Command | Purpose | | --- | --- | +| `oar --version` | Display the installed OAR version. | | `oar init PROFILE_ROOT --model MODEL_ID` | Create editable copies of the packaged profiles. | | `oar validate PROFILE_DIRECTORY` | Check the profile and its local resources. | | `oar doctor` | Display the OpenShell version, gateway status, and inference configuration. | diff --git a/projects/openshell-agent-runner/src/openshell_agent_runner/cli.py b/projects/openshell-agent-runner/src/openshell_agent_runner/cli.py index ec908528..eae876c9 100644 --- a/projects/openshell-agent-runner/src/openshell_agent_runner/cli.py +++ b/projects/openshell-agent-runner/src/openshell_agent_runner/cli.py @@ -6,6 +6,8 @@ from __future__ import annotations import shlex +from importlib.metadata import PackageNotFoundError +from importlib.metadata import version as distribution_version from pathlib import Path from typing import Annotated, NoReturn @@ -20,6 +22,14 @@ from openshell_agent_runner.profile_init import ThinkingLevel, initialize_profiles from openshell_agent_runner.runner import RunRequest, render_dry_run, run_agent + +def version_callback(value: bool) -> None: + """Print the installed OAR version when requested.""" + if value: + typer.echo(f"oar {_installed_version()}") + raise typer.Exit() + + app = typer.Typer( help="Launch ephemeral agents for single tasks in OpenShell sandboxes.", no_args_is_help=True, @@ -28,6 +38,21 @@ ) +@app.callback() +def main( + version: Annotated[ + bool | None, + typer.Option( + "--version", + callback=version_callback, + is_eager=True, + help="Show the installed OAR version and exit.", + ), + ] = None, +) -> None: + """Launch ephemeral agents for single tasks in OpenShell sandboxes.""" + + class ProfileTaskHelpCommand(TyperCommand): """Show focused help when a profile task is selected.""" @@ -215,6 +240,13 @@ def _fail(error: OarError) -> NoReturn: raise typer.Exit(1) +def _installed_version() -> str: + try: + return distribution_version("openshell-agent-runner") + except PackageNotFoundError: + return "unknown" + + def _profile_task_selection(args: list[str]) -> tuple[Path, str] | None: if not args or args[0].startswith("-"): return None diff --git a/projects/openshell-agent-runner/tests/test_cli.py b/projects/openshell-agent-runner/tests/test_cli.py index 7e717e28..a77d59e0 100644 --- a/projects/openshell-agent-runner/tests/test_cli.py +++ b/projects/openshell-agent-runner/tests/test_cli.py @@ -2,6 +2,8 @@ # SPDX-License-Identifier: Apache-2.0 import shutil +from importlib.metadata import PackageNotFoundError +from importlib.metadata import version as distribution_version from pathlib import Path import pytest @@ -26,8 +28,33 @@ def test_root_help_lists_commands() -> None: result = CliRunner().invoke(app, ["--help"]) assert result.exit_code == 0 + help_text = Text.from_ansi(result.stdout).plain for command in ("init", "validate", "run", "doctor"): - assert command in result.stdout + assert command in help_text + assert "--version" in help_text + + +def test_version_reports_installed_distribution_version() -> None: + result = CliRunner().invoke(app, ["--version"]) + + assert result.exit_code == 0 + assert result.stdout == f"oar {distribution_version('openshell-agent-runner')}\n" + + +def test_version_handles_unavailable_distribution_metadata( + monkeypatch: pytest.MonkeyPatch, +) -> None: + def missing_distribution(_distribution_name: str) -> str: + raise PackageNotFoundError + + monkeypatch.setattr( + "openshell_agent_runner.cli.distribution_version", missing_distribution + ) + + result = CliRunner().invoke(app, ["--version"]) + + assert result.exit_code == 0 + assert result.stdout == "oar unknown\n" @pytest.mark.parametrize(