@andespindola/crow-skills 0.8.0 → 0.10.0

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.
@@ -83,7 +83,7 @@ def validate(data):
83
83
  "status": data.get("status") if data.get("status") in ("running",) + FINISHED else "running",
84
84
  "startedAt": data.get("startedAt"), "updatedAt": data.get("updatedAt"), "endedAt": data.get("endedAt"),
85
85
  "session": data.get("session") if isinstance(data.get("session"), dict) else {},
86
- "phases": [], "units": []}
86
+ "phases": [], "units": [], "resources": resources_decision(data.get("resources"))}
87
87
  for phase in phases:
88
88
  if isinstance(phase, dict) and isinstance(phase.get("id"), str):
89
89
  run["phases"].append({"id": phase["id"], "title": safe(phase.get("title") or phase["id"]),
@@ -107,6 +107,42 @@ def validate(data):
107
107
  return run
108
108
 
109
109
 
110
+ RESOURCE_MODES = ("parallel", "reduced", "sequential")
111
+ RESOURCE_LEVELS = ("normal", "warn", "critical", "unknown")
112
+ RESOURCE_SOURCES = ("probe", "os", "assumed")
113
+
114
+
115
+ def resources_decision(value):
116
+ """The optional run-level ``resources`` field (0.9.0): the parallelism decision recorded
117
+ before a fan-out. Ledgers without it, or with an invalid one, read as None."""
118
+ if not isinstance(value, dict) or value.get("mode") not in RESOURCE_MODES:
119
+ return None
120
+
121
+ def count(key):
122
+ number = value.get(key)
123
+ return number if isinstance(number, int) and not isinstance(number, bool) and 0 <= number <= 256 else None
124
+
125
+ return {"mode": value["mode"], "pressure": value.get("pressure") if value.get("pressure") in RESOURCE_LEVELS else None,
126
+ "source": value.get("source") if value.get("source") in RESOURCE_SOURCES else None,
127
+ "maxParallelAgents": count("maxParallelAgents"), "maxParallelHeavyJobs": count("maxParallelHeavyJobs"),
128
+ "maxWorkersPerJob": count("maxWorkersPerJob"),
129
+ "checkedAt": value.get("checkedAt") if isinstance(value.get("checkedAt"), str) else None,
130
+ "note": safe(value.get("note") or "", 160)}
131
+
132
+
133
+ def resources_line(decision, unicode=True):
134
+ if not decision:
135
+ return ""
136
+ parts = [decision["mode"]]
137
+ if decision["maxParallelAgents"] is not None:
138
+ parts.append(("≤" if unicode else "<=") + str(decision["maxParallelAgents"]) + " agente(s)")
139
+ if decision["maxParallelHeavyJobs"] is not None:
140
+ parts.append(("≤" if unicode else "<=") + str(decision["maxParallelHeavyJobs"]) + " job(s) pesado(s)")
141
+ if decision["pressure"]:
142
+ parts.append("pressão " + decision["pressure"])
143
+ return "recursos: " + (" · " if unicode else " - ").join(parts) + (" (" + decision["note"] + ")" if decision["note"] else "")
144
+
145
+
110
146
  def load(path):
111
147
  path = Path(path)
112
148
  if path.is_symlink() or not path.is_file() or path.stat().st_size > MAX_LEDGER:
@@ -515,6 +551,8 @@ def render(run, usage=None, now=None, color=False, unicode=True, width=20):
515
551
  bar(info["progress"], width, unicode) + " " + str(int(info["progress"] * 100)).rjust(3) + "% " +
516
552
  str(info["done"]) + "/" + str(info["total"]) + " unidades" +
517
553
  (" - " + str(info["failed"]) + " falha(s)" if info["failed"] else "") + " " + token_text(usage, unicode)]
554
+ if run.get("resources"):
555
+ lines.append(paint("dim", resources_line(run["resources"], unicode)))
518
556
  lines += ["", *agent_rows(run, unicode, counts=False), ""]
519
557
  phase_ids = [p["id"] for p in run["phases"]]
520
558
  groups = [(p, [u for u in run["units"] if u["phase"] == p["id"]]) for p in run["phases"]]
@@ -649,6 +687,18 @@ def mode_text(home, unicode=True):
649
687
  return ""
650
688
 
651
689
 
690
+ def memory_text(home, unicode=True):
691
+ """'mem ▲ 85% ×2' when the machine is under pressure or parallelism is reduced; else ''."""
692
+ hook = hook_module()
693
+ module = hook.resources_module() if hook is not None and hasattr(hook, "resources_module") else None
694
+ if module is None:
695
+ return ""
696
+ try:
697
+ return module.segment(module.probe(home, processes=False, use_cache=True), unicode)
698
+ except Exception: # noqa: BLE001 - the probe never breaks a status line
699
+ return ""
700
+
701
+
652
702
  def statusline(home, stdin_text, chain=None, host="claude"):
653
703
  """Status line for Claude Code and Antigravity (agy).
654
704
 
@@ -684,7 +734,7 @@ def statusline(home, stdin_text, chain=None, host="claude"):
684
734
  except (OSError, subprocess.SubprocessError):
685
735
  previous = ""
686
736
  run = select(discover(cwd, home), cwd, strict=True, session=session)
687
- parts = [p for p in (mode_text(home),) if p]
737
+ parts = [p for p in (mode_text(home), memory_text(home)) if p]
688
738
  rows = []
689
739
  if active(run):
690
740
  if run["host"] == "claude" and host == "claude" and not run["session"].get("id") and session:
@@ -711,12 +761,12 @@ def statusline(home, stdin_text, chain=None, host="claude"):
711
761
 
712
762
 
713
763
  def tmux_segment(home, cwd):
714
- mode = mode_text(home)
764
+ head = " · ".join(p for p in (mode_text(home), memory_text(home)) if p)
715
765
  run = select(discover(cwd, home), cwd, strict=True)
716
766
  if not active(run):
717
- return mode
767
+ return head.replace("#", "##")
718
768
  text = compact(run, tokens(run, home), unicode=True, width=8)
719
- return ((mode + " · " if mode else "") + text).replace("#", "##")
769
+ return ((head + " · " if head else "") + text).replace("#", "##")
720
770
 
721
771
 
722
772
  def main(argv=None):