From d07972efab793e116cdaf39c3eb8b5b22513622c Mon Sep 17 00:00:00 2001 From: Nitjsefnie Date: Fri, 17 Jul 2026 22:06:49 +0000 Subject: [PATCH] feat(cli): add --json to steer-report for machine-readable output paths (#9) When --json is passed, print the artifact outputs mapping (markdown, html, dose_png, layer_png) as a single JSON object with string path values to stdout, and suppress the human "[steerbench] wrote:" lines. Default text output is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/steerbench/cli.py | 15 ++++++++++++--- tests/test_cli.py | 27 +++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/steerbench/cli.py b/src/steerbench/cli.py index cc8814a..c0f1e29 100644 --- a/src/steerbench/cli.py +++ b/src/steerbench/cli.py @@ -15,6 +15,7 @@ from __future__ import annotations import argparse +import json import subprocess import sys from pathlib import Path @@ -101,6 +102,11 @@ def _build_parser() -> argparse.ArgumentParser: metavar="FUNCTION", help="run a Modal harness function (needs steerbench[gpu]) instead of rendering", ) + parser.add_argument( + "--json", + action="store_true", + help="print the written artifact paths as a single JSON object (suppresses human text)", + ) return parser @@ -126,9 +132,12 @@ def main(argv: list[str] | None = None) -> int: out_dir=args.out, stem=args.stem, ) - print("[steerbench] wrote:") - for kind, path in outputs.items(): - print(f" {kind:9s} {path}") + if args.json: + print(json.dumps({kind: str(path) for kind, path in outputs.items()})) + else: + print("[steerbench] wrote:") + for kind, path in outputs.items(): + print(f" {kind:9s} {path}") return 0 diff --git a/tests/test_cli.py b/tests/test_cli.py index 6a42734..48de874 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -2,6 +2,7 @@ from __future__ import annotations +import json from pathlib import Path import pytest @@ -37,6 +38,32 @@ def test_cli_renders_from_real_artifacts(tmp_path: Path) -> None: assert (out / "side_effects.csv").read_text().startswith("benchmark,") +def test_cli_json_emits_artifact_paths(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: + pytest.importorskip("matplotlib") + out = tmp_path / "card" + rc = cli.main( + [ + "--dose-csv", + str(_DOSE), + "--layer-csv", + str(_LAYER), + "--out", + str(out), + "--stem", + "report", + "--json", + ] + ) + assert rc == 0 + stdout = capsys.readouterr().out + # human "wrote:" lines are suppressed under --json. + assert "[steerbench] wrote:" not in stdout + payload = json.loads(stdout) + for key in ("markdown", "html", "dose_png", "layer_png"): + assert key in payload, f"missing artifact key: {key}" + assert Path(payload[key]).exists(), f"artifact path does not exist: {payload[key]}" + + def test_cli_errors_on_missing_csv(tmp_path: Path) -> None: with pytest.raises(SystemExit): cli.main(["--dose-csv", str(tmp_path / "nope.csv"), "--out", str(tmp_path / "o")])