@caddis/cli 0.4.74 → 0.4.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -259,6 +259,45 @@ def _relays(directory: Path) -> list[tuple[str, dict, str]]:
259
259
  return out
260
260
 
261
261
 
262
+ JSON_SCHEMA = 1 # the `schema` key of every --json output (hub rule R13)
263
+
264
+
265
+ def _relay_dir(root: Path) -> Path:
266
+ artifact = _artifact(root)
267
+ directory = artifact / "relay"
268
+ if directory.exists() and not directory.resolve().is_relative_to(artifact.resolve()):
269
+ raise ValueError("relay directory escapes artifact root")
270
+ return directory
271
+
272
+
273
+ def relays_json(root: Path, now: datetime | None = None) -> dict:
274
+ """Return every valid relay as JSON data. A bad status line gives status INVALID."""
275
+ now = now or datetime.now(timezone.utc)
276
+ rows = []
277
+ for name, meta, content in _relays(_relay_dir(root)):
278
+ when = datetime.fromisoformat(meta["generated"]["at"].replace("Z", "+00:00"))
279
+ if when.tzinfo is None:
280
+ when = when.replace(tzinfo=timezone.utc)
281
+ when = when.astimezone(timezone.utc)
282
+ try:
283
+ status, status_arg = parse_status(content)
284
+ except ValueError as exc:
285
+ status, status_arg = "INVALID", str(exc)
286
+ step = _next_step(content)
287
+ rows.append({
288
+ "name": name,
289
+ "branch": meta.get("branch"),
290
+ "plan": meta.get("plan"),
291
+ "session_id": meta.get("session_id"),
292
+ "updated_at": when.strftime("%Y-%m-%dT%H:%M:%SZ"),
293
+ "age_min": max(0, int((now - when).total_seconds()) // 60),
294
+ "status": status,
295
+ "status_arg": status_arg,
296
+ "next_step": None if step == "next step unrecorded" else step,
297
+ })
298
+ return {"schema": JSON_SCHEMA, "relays": sorted(rows, key=lambda r: r["name"])}
299
+
300
+
262
301
  def _item_matches(item: str, next_text: str) -> bool:
263
302
  needle = item.strip()
264
303
  if not needle:
@@ -690,6 +729,7 @@ def main(argv: list[str] | None = None) -> int:
690
729
 
691
730
  list_p = sub.add_parser("list", help="list each relay's name, branch and next step")
692
731
  list_p.add_argument("--root", type=Path, default=Path("."))
732
+ list_p.add_argument("--json", action="store_true")
693
733
 
694
734
  mentions_p = sub.add_parser("mentions", help="list other relays whose Next step mentions an item")
695
735
  mentions_p.add_argument("--item", required=True)
@@ -735,7 +775,7 @@ def main(argv: list[str] | None = None) -> int:
735
775
  sys.stderr.write(f"invalid relay status: {exc}\n")
736
776
  return 1
737
777
  if args.json:
738
- print(json.dumps({"status": kind, "arg": arg}))
778
+ print(json.dumps({"schema": JSON_SCHEMA, "status": kind, "arg": arg}))
739
779
  else:
740
780
  if arg:
741
781
  print(f"STATUS: {kind} {arg}")
@@ -788,10 +828,18 @@ def main(argv: list[str] | None = None) -> int:
788
828
  print(f" conflict: {conflict}")
789
829
  return 0 if report.status in ("clean", "migrated") else 1
790
830
  elif args.command == "list":
791
- artifact = _artifact(args.root)
792
- directory = artifact / "relay"
793
- if directory.exists() and not directory.resolve().is_relative_to(artifact.resolve()):
794
- raise ValueError("relay directory escapes artifact root")
831
+ try:
832
+ data = relays_json(args.root) if args.json else None
833
+ directory = _relay_dir(args.root)
834
+ except ValueError as exc: # the relay folder escapes .caddis: one error, never a traceback
835
+ if args.json:
836
+ print(json.dumps({"schema": JSON_SCHEMA, "error": str(exc)}))
837
+ else:
838
+ sys.stderr.write(f"cannot list relays: {exc}\n")
839
+ return 1
840
+ if args.json:
841
+ print(json.dumps(data, indent=2))
842
+ return 0
795
843
  for name, meta, content in _relays(directory):
796
844
  branch_label = _single_line(str(meta.get("branch", "?")), 50)
797
845
  print(f"{name}\t{branch_label}\t{_next_step(content)}")