@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.
- package/README.md +3 -3
- package/crow_skills/__init__.py +1 -1
- package/crow_skills/broadcast.py +987 -0
- package/crow_skills/cli.py +176 -0
- package/crow_skills/events.py +64 -3
- package/crow_skills/hook.py +313 -14
- package/crow_skills/integrations.py +90 -22
- package/crow_skills/progress.py +55 -5
- package/crow_skills/resources.py +713 -0
- package/docs/cli.md +66 -6
- package/package.json +1 -1
- package/release.json +50 -30
- package/skills/crow-accessibility/SKILL.md +2 -0
- package/skills/crow-api-test/SKILL.md +2 -0
- package/skills/crow-backend/SKILL.md +2 -0
- package/skills/crow-behaviour/SKILL.md +2 -0
- package/skills/crow-behaviour/references/broadcast.md +45 -0
- package/skills/crow-behaviour/references/orchestration.md +6 -0
- package/skills/crow-behaviour/references/progress-ledger.md +3 -0
- package/skills/crow-behaviour/references/resources.md +44 -0
- package/skills/crow-behaviour/scripts/crow_ledger.py +75 -2
- package/skills/crow-browser/SKILL.md +2 -0
- package/skills/crow-debug/SKILL.md +2 -0
- package/skills/crow-frontend/SKILL.md +2 -0
- package/skills/crow-infra/SKILL.md +2 -0
- package/skills/crow-interface/SKILL.md +2 -0
- package/skills/crow-media/SKILL.md +2 -0
- package/skills/crow-native-mobile/SKILL.md +2 -0
- package/skills/crow-performance/SKILL.md +2 -0
- package/skills/crow-qa/SKILL.md +2 -0
- package/skills/crow-release/SKILL.md +2 -0
- package/skills/crow-reverse/SKILL.md +2 -0
- package/skills/crow-security-scan/SKILL.md +2 -0
package/crow_skills/progress.py
CHANGED
|
@@ -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
|
-
|
|
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
|
|
767
|
+
return head.replace("#", "##")
|
|
718
768
|
text = compact(run, tokens(run, home), unicode=True, width=8)
|
|
719
|
-
return ((
|
|
769
|
+
return ((head + " · " if head else "") + text).replace("#", "##")
|
|
720
770
|
|
|
721
771
|
|
|
722
772
|
def main(argv=None):
|