diff --git a/CLAUDE.md b/CLAUDE.md index 824708f..b658057 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -79,6 +79,8 @@ soup train --config soup.yaml **Confirmation prompts:** `commands/train.py` and `commands/sweep.py` ask for confirmation before starting. Skip with `--yes` / `-y`. +**Version:** `cli.py` `version()` command supports `--full` flag that shows version, Python version, GPU backend, and installed optional extras in one line. + ## Code Conventions - **Line length:** 100 chars (ruff enforced) @@ -107,14 +109,14 @@ soup train --config soup.yaml ## Tests -Test suite (~321 tests) lives in `tests/`: +Test suite lives in `tests/`: | File | Covers | |---|---| | `test_config.py` | Config loading, validation, defaults | | `test_data.py` | Format detection, conversion, validation | | `test_gpu.py` | GPU detection, batch size estimation | -| `test_cli.py` | CLI commands basic validation | +| `test_cli.py` | CLI commands, version --full | | `test_tracker.py` | SQLite experiment tracker | | `test_runs.py` | `soup runs` CLI commands | | `test_data_tools.py` | Data convert/merge/dedup/stats commands | diff --git a/README.md b/README.md index f36ad4e..309005e 100644 --- a/README.md +++ b/README.md @@ -349,6 +349,17 @@ soup doctor Shows: Python version, GPU availability, all dependency versions, and fix suggestions. +## Version Info + +```bash +# Basic version +soup version + +# Full system info (useful for bug reports) +soup version --full +# → soup v0.3.2 | Python 3.11.5 | CUDA 12.1 | extras: serve, data +``` + ## Error Handling Soup shows friendly error messages by default (2-3 lines with a fix suggestion). For full tracebacks: @@ -500,7 +511,7 @@ soup runs show Run details + loss graph soup runs compare Compare two runs soup doctor Check environment soup quickstart [--dry-run] Full demo -soup version Show version +soup version [--full] Show version (--full: system info) soup --verbose Full traceback on errors ``` diff --git a/pyproject.toml b/pyproject.toml index 23f8dde..5544208 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "soup-cli" -version = "0.3.1" +version = "0.3.2" description = "Fine-tune LLMs in one command. No SSH, no config hell." readme = "README.md" license = "MIT" diff --git a/soup_cli/__init__.py b/soup_cli/__init__.py index d4540e5..caa0eae 100644 --- a/soup_cli/__init__.py +++ b/soup_cli/__init__.py @@ -1,3 +1,3 @@ """Soup CLI — Fine-tune LLMs in one command.""" -__version__ = "0.3.1" +__version__ = "0.3.2" diff --git a/soup_cli/cli.py b/soup_cli/cli.py index c3708ee..216554f 100644 --- a/soup_cli/cli.py +++ b/soup_cli/cli.py @@ -60,9 +60,50 @@ data.app.command(name="generate")(generate.generate) @app.command() -def version(): +def version( + full: bool = typer.Option(False, "--full", "-f", help="Show system info and extras"), +): """Show Soup CLI version.""" - console.print(f"[bold green]soup[/] v{__version__}") + if not full: + console.print(f"[bold green]soup[/] v{__version__}") + return + + import platform + + parts = [f"[bold green]soup[/] v{__version__}"] + parts.append(f"Python {platform.python_version()}") + + # GPU info + try: + import torch + if torch.cuda.is_available(): + parts.append(f"CUDA {torch.version.cuda}") + elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available(): + parts.append("MPS") + else: + parts.append("CPU only") + except ImportError: + parts.append("no torch") + + # Installed extras + extras = [] + for name, label in [ + ("fastapi", "serve"), + ("datasketch", "data"), + ("lm_eval", "eval"), + ("deepspeed", "deepspeed"), + ("wandb", "wandb"), + ]: + try: + __import__(name) + extras.append(label) + except ImportError: + pass + + if extras: + parts.append(f"extras: {', '.join(extras)}") + + console.print(" | ".join(parts)) @app.callback(invoke_without_command=True) diff --git a/tests/test_cli.py b/tests/test_cli.py index 1a1dd7b..a1357e6 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -14,6 +14,19 @@ def test_version(): assert __version__ in result.output +def test_version_full(): + result = runner.invoke(app, ["version", "--full"]) + assert result.exit_code == 0 + assert __version__ in result.output + assert "Python" in result.output + + +def test_version_full_short_flag(): + result = runner.invoke(app, ["version", "-f"]) + assert result.exit_code == 0 + assert "Python" in result.output + + def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0