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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,17 @@ In your agent's bootstrap (CLAUDE.md / AGENTS.md):
agent-handover check # exit 1 → finish the interrupted handover first
```

For automation and dashboards, `status --json` emits one JSON object without
extra log output:

```bash
agent-handover status --json
# {"status":"in_progress","steps":{"publish":"pending"},"pending":["publish"],...}
```

The object always includes `status`, `steps`, `pending`, `started_at`, and
`finished_at`. Human-readable output remains the default.

## Declarative handover (`agent-handover run`)

Don't want to write Python? Describe the handover in a TOML file and run it from
Expand Down
15 changes: 14 additions & 1 deletion src/agent_handover/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

Expand All @@ -26,6 +27,9 @@ def _build_parser() -> argparse.ArgumentParser:

p_status = sub.add_parser("status", help="print checkpoint state")
p_status.add_argument("--checkpoint", type=Path, default=DEFAULT_CHECKPOINT)
p_status.add_argument(
"--json", dest="json_output", action="store_true", help="print machine-readable JSON"
)

p_run = sub.add_parser("run", help="run a declarative handover from a TOML config")
p_run.add_argument("--config", type=Path, required=True)
Expand All @@ -47,11 +51,20 @@ def main(argv: list[str] | None = None) -> int:
if args.command == "status":
cp = Checkpoint(args.checkpoint)
data = cp.load()
pending = cp.pending_steps()
if args.json_output:
print(json.dumps({
"status": data["status"],
"steps": data.get("steps", {}),
"pending": pending,
"started_at": data.get("started_at"),
"finished_at": data.get("finished_at"),
}, ensure_ascii=False))
return 0
print(f"status: {data['status']}")
for name, state in data.get("steps", {}).items():
mark = "x" if state == "done" else " "
print(f" [{mark}] {name}")
pending = cp.pending_steps()
if data["status"] == "in_progress" and pending:
print(f"pending: {', '.join(pending)}")
return 0
Expand Down
23 changes: 23 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import json

from agent_handover.checkpoint import Checkpoint
from agent_handover.cli import main

Expand Down Expand Up @@ -25,6 +27,27 @@ def test_status_runs(tmp_path, capsys):
assert "[ ] a" in out


def test_status_json_prints_stable_state_object(tmp_path, capsys):
cp_path = tmp_path / "cp.json"
cp = Checkpoint(cp_path)
cp.start(["done-step", "pending-step"])
cp.mark_done("done-step")

assert main(["status", "--json", "--checkpoint", str(cp_path)]) == 0

out = capsys.readouterr().out
data = json.loads(out)
assert data == {
"status": "in_progress",
"steps": {"done-step": "done", "pending-step": "pending"},
"pending": ["pending-step"],
"started_at": data["started_at"],
"finished_at": None,
}
assert data["started_at"] is not None
assert out.count("\n") == 1


def _write_config(tmp_path, mem, cp):
cfg = tmp_path / "handover.toml"
cfg.write_text(
Expand Down