Skip to content
Open
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: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ From a clone, for development:

```bash
pip install -e ".[dev]"
pytest -q # 17 tests
pytest -q # 22 tests
```

No session of your own to look at yet? A synthetic one ships with the repo:
Expand Down Expand Up @@ -132,4 +132,4 @@ as such rather than vanish from the report.

## Status

Working, 17 tests, validated against a real 34MB session with 152 subagent runs.
Working, 22 tests, validated against a real 34MB session with 152 subagent runs.
18 changes: 15 additions & 3 deletions agentrace/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from pathlib import Path

from rich.console import Console
from rich.markup import escape
from rich.table import Table

from .checks import analyse
Expand All @@ -31,7 +32,11 @@ def _load(args) -> list[AgentRun]:
sessions = parse_all(Path(args.dir) if args.dir else None)
runs = [r for s in sessions for r in s.runs]
if not runs:
console.print("[yellow]No subagent runs found.[/] Looked in ~/.claude/projects unless --dir was given.")
source = args.file or args.dir or "~/.claude/projects"
console.print(
f"[yellow]No subagent runs found.[/] Looked in {escape(source)}.",
soft_wrap=True,
)
return runs


Expand Down Expand Up @@ -155,10 +160,17 @@ def cmd_stats(args) -> int:
return 0


def _existing_file(value: str) -> str:
if not Path(value).is_file():
raise argparse.ArgumentTypeError(f"not a file: {value}")
return value


def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(prog="agentrace", description=__doc__.split("\n")[0])
p.add_argument("--dir", help="transcript root (default ~/.claude/projects)")
p.add_argument("--file", help="a single .jsonl transcript")
source = p.add_mutually_exclusive_group()
source.add_argument("--dir", help="transcript root (default ~/.claude/projects)")
source.add_argument("--file", type=_existing_file, help="a single .jsonl transcript")
sub = p.add_subparsers(dest="cmd", required=True)

sub.add_parser("list", help="list subagent runs").set_defaults(func=cmd_list)
Expand Down
54 changes: 54 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from __future__ import annotations

import subprocess
import sys


def run_cli(*args: str) -> subprocess.CompletedProcess[str]:
return subprocess.run(
[sys.executable, "-m", "agentrace.cli", *args],
capture_output=True,
check=False,
text=True,
)


def test_file_and_dir_are_mutually_exclusive(tmp_path):
transcript = tmp_path / "session.jsonl"
transcript.write_text("")

result = run_cli("--dir", str(tmp_path), "--file", str(transcript), "list")

assert result.returncode == 2
assert "argument --file: not allowed with argument --dir" in result.stderr
assert result.stdout == ""


def test_missing_file_is_a_clean_parser_error(tmp_path):
missing = tmp_path / "missing.jsonl"

result = run_cli("--file", str(missing), "list")

assert result.returncode == 2
assert f"not a file: {missing}" in result.stderr
assert "Traceback" not in result.stderr
assert result.stdout == ""


def test_empty_result_names_selected_file(tmp_path):
transcript = tmp_path / "empty.jsonl"
transcript.write_text("")

result = run_cli("--file", str(transcript), "list")

assert result.returncode == 0
assert "No subagent runs found." in result.stdout
assert f"Looked in {transcript}." in result.stdout


def test_empty_result_names_selected_directory(tmp_path):
result = run_cli("--dir", str(tmp_path), "list")

assert result.returncode == 0
assert "No subagent runs found." in result.stdout
assert f"Looked in {tmp_path}." in result.stdout
Loading