diff --git a/codebase_rag/cli.py b/codebase_rag/cli.py index 036c00cb9..38aedaf3c 100644 --- a/codebase_rag/cli.py +++ b/codebase_rag/cli.py @@ -1,4 +1,5 @@ import asyncio +from importlib.metadata import version as get_version from pathlib import Path import typer @@ -27,13 +28,22 @@ from .tools.language import cli as language_cli app = typer.Typer( - name="code-graph-rag", + name=cs.PACKAGE_NAME, help=ch.APP_DESCRIPTION, no_args_is_help=True, add_completion=False, ) +def _version_callback(value: bool) -> None: + if value: + app_context.console.print( + cs.CLI_MSG_VERSION.format(package=cs.PACKAGE_NAME, version=get_version(cs.PACKAGE_NAME)), + highlight=False, + ) + raise typer.Exit() + + def validate_models_early() -> None: try: orchestrator_config = settings.active_orchestrator_config @@ -58,6 +68,14 @@ def _update_and_validate_models(orchestrator: str | None, cypher: str | None) -> @app.callback() def _global_options( + version: bool | None = typer.Option( + None, + "--version", + "-v", + help=ch.HELP_VERSION, + callback=_version_callback, + is_eager=True, + ), quiet: bool = typer.Option( False, "--quiet", diff --git a/codebase_rag/cli_help.py b/codebase_rag/cli_help.py index 96e816d9a..25a9810b7 100644 --- a/codebase_rag/cli_help.py +++ b/codebase_rag/cli_help.py @@ -50,6 +50,7 @@ class CLICommandName(StrEnum): HELP_REPO_PATH_INDEX = "Path to the target repository to index." HELP_REPO_PATH_OPTIMIZE = "Path to the repository to optimize" HELP_REPO_PATH_WATCH = "Path to the repository to watch." +HELP_VERSION = "Show the version and exit." HELP_UPDATE_GRAPH = "Update the knowledge graph by parsing the repository" HELP_CLEAN_DB = "Clean the database before updating (use when adding first repo)" diff --git a/codebase_rag/constants.py b/codebase_rag/constants.py index fa6d160ff..aa18ce7b4 100644 --- a/codebase_rag/constants.py +++ b/codebase_rag/constants.py @@ -236,6 +236,8 @@ class GoogleProviderType(StrEnum): CLI_MSG_EXPORTING_DATA = "Exporting graph data..." CLI_MSG_OPTIMIZATION_TERMINATED = "\nOptimization session terminated by user." CLI_MSG_MCP_TERMINATED = "\nMCP server terminated by user." +PACKAGE_NAME = "code-graph-rag" +CLI_MSG_VERSION = "{package} version {version}" CLI_MSG_HINT_TARGET_REPO = ( "\nHint: Make sure TARGET_REPO_PATH environment variable is set." ) diff --git a/codebase_rag/tests/test_cli_smoke.py b/codebase_rag/tests/test_cli_smoke.py index 89173ff87..aeef03dd6 100644 --- a/codebase_rag/tests/test_cli_smoke.py +++ b/codebase_rag/tests/test_cli_smoke.py @@ -1,10 +1,13 @@ import re import subprocess import sys +from importlib.metadata import version as get_version from pathlib import Path import pytest +from codebase_rag import constants as cs + _ANSI_RE = re.compile(r"\x1b\[[0-9;]*m") @@ -35,3 +38,26 @@ def test_import_cli_module() -> None: assert hasattr(cli, "app"), "CLI module missing app attribute" except ImportError as e: pytest.fail(f"Failed to import cli module: {e}") + + +def test_version_flag() -> None: + repo_root = Path(__file__).parent.parent.parent + + for flag in ["--version", "-v"]: + result = subprocess.run( + [sys.executable, "-m", "codebase_rag.cli", flag], + check=False, + cwd=repo_root, + capture_output=True, + text=True, + timeout=30, + ) + + assert result.returncode == 0, ( + f"{flag} exited with code {result.returncode}: {result.stderr}" + ) + expected = cs.CLI_MSG_VERSION.format(package=cs.PACKAGE_NAME, version=get_version(cs.PACKAGE_NAME)) + assert result.stdout.strip() == expected, ( + f"{flag} output did not match expected format: {repr(result.stdout)}" + ) + assert result.stderr == "", f"Unexpected stderr for {flag}: {result.stderr}"