@andespindola/crow-skills 0.19.1 → 0.20.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/crow_skills/__init__.py +1 -1
- package/crow_skills/autoupdate.py +73 -0
- package/crow_skills/machine.py +18 -2
- package/package.json +1 -1
- package/release.json +6 -6
package/crow_skills/__init__.py
CHANGED
|
@@ -319,6 +319,69 @@ def broken_projections(agent, home, store, rows):
|
|
|
319
319
|
return [{"name": n, "status": "uniflow_link_modified", "fix": broken_fix(n)} for n in sorted(names)]
|
|
320
320
|
|
|
321
321
|
|
|
322
|
+
|
|
323
|
+
def catalog_gap(agent, home, source, rows, projected, broken):
|
|
324
|
+
"""What this agent is missing from the catalog, and whether upgrade may install it.
|
|
325
|
+
|
|
326
|
+
WHY THIS EXISTS. `upgrade` only ever touched skills already RECORDED for the agent, so a skill
|
|
327
|
+
that is new in the catalog was published and never arrived: 0.17.0 shipped crow-be-human, every
|
|
328
|
+
CLI reported "atualizadas" and `doctor` stayed healthy while the skill existed nowhere on disk.
|
|
329
|
+
A release that the command the user runs cannot deliver is not delivered.
|
|
330
|
+
|
|
331
|
+
Returns {"missing": [names], "install": [names]}. The decision is per skill and read from the
|
|
332
|
+
GROUPS, because "has everything else" is vacuous — the set of missing names is by definition
|
|
333
|
+
everything it does not have. A missing skill is installed when some group of it is otherwise
|
|
334
|
+
COMPLETE here: the user took that group wholesale, and a new member belongs to it. A skill whose
|
|
335
|
+
every group is partial was curated by hand, so it is only reported, with the command.
|
|
336
|
+
"""
|
|
337
|
+
if source is None:
|
|
338
|
+
return {"missing": [], "install": []}
|
|
339
|
+
present = {r.get("name") for r in rows if r.get("name")}
|
|
340
|
+
present |= set(projected or [])
|
|
341
|
+
present |= {k.get("name") for k in (broken or []) if isinstance(k, dict) and k.get("name")}
|
|
342
|
+
missing = sorted(set(source.catalog) - present)
|
|
343
|
+
if not missing:
|
|
344
|
+
return {"missing": [], "install": []}
|
|
345
|
+
try:
|
|
346
|
+
from . import groups as groups_mod
|
|
347
|
+
available = [g for g in groups_mod.load(source) if g["name"] != groups_mod.ALL]
|
|
348
|
+
except (CrowError, OSError, ValueError, KeyError):
|
|
349
|
+
available = []
|
|
350
|
+
install = []
|
|
351
|
+
for name in missing:
|
|
352
|
+
for group in available:
|
|
353
|
+
members = [s for s in group["skills"] if s != name]
|
|
354
|
+
if name in group["skills"] and members and all(m in present for m in members):
|
|
355
|
+
install.append(name)
|
|
356
|
+
break
|
|
357
|
+
return {"missing": missing, "install": install}
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def install_new(home, entry, names, agent, dry_run, trust):
|
|
361
|
+
"""Install catalog skills the agent is missing — only when it already has every other one.
|
|
362
|
+
|
|
363
|
+
The decision is read from the machine, not assumed: an agent holding the whole catalog minus the
|
|
364
|
+
new names has said "all of them", so a new skill belongs there. An agent with a SUBSET chose that
|
|
365
|
+
subset, and adding to it would be deciding for the user, so those names are only reported.
|
|
366
|
+
"""
|
|
367
|
+
if not names:
|
|
368
|
+
return
|
|
369
|
+
argv = ["install", *names, "--agents", agent, "--global", "--json", "--home", str(home),
|
|
370
|
+
"--dry-run" if dry_run else "--yes"]
|
|
371
|
+
if not trust:
|
|
372
|
+
argv.append("--no-trust")
|
|
373
|
+
from .cli import parser, run as cli_run
|
|
374
|
+
try:
|
|
375
|
+
data = cli_run(parser().parse_args(argv))
|
|
376
|
+
except CrowError as exc:
|
|
377
|
+
entry["newInCatalogError"] = {"code": exc.code, "message": str(exc)}
|
|
378
|
+
return
|
|
379
|
+
done = [c["name"] for c in data.get("changes") or [] if c.get("action") != "unchanged"]
|
|
380
|
+
entry["installedNew"] = sorted(done)
|
|
381
|
+
entry["updated"] = sorted({*entry.get("updated", []), *done})
|
|
382
|
+
if data.get("transaction"):
|
|
383
|
+
entry["newTransaction"] = data["transaction"]
|
|
384
|
+
|
|
322
385
|
def reinstall_behaviour(home, entry, dry_run, trust):
|
|
323
386
|
"""Codex without a working crow-behaviour (its projection is broken, no native copy): install it natively,
|
|
324
387
|
as setup does, so the guide and hooks keep a coordinator to point to."""
|
|
@@ -402,6 +465,9 @@ def skills_update(home, trust=True, dry_run=False):
|
|
|
402
465
|
from . import machine
|
|
403
466
|
entry = {"agent": agent, "target": target["path"], "updated": [], "unchanged": 0, "kept": list(broken), "error": None,
|
|
404
467
|
"via_uniflow": projected}
|
|
468
|
+
gap = catalog_gap(agent, home, current, rows, projected, broken)
|
|
469
|
+
if gap["missing"]:
|
|
470
|
+
entry["newInCatalog"] = gap["missing"]
|
|
405
471
|
if dry_run:
|
|
406
472
|
entry["dry_run"] = True
|
|
407
473
|
try:
|
|
@@ -413,6 +479,8 @@ def skills_update(home, trust=True, dry_run=False):
|
|
|
413
479
|
entry["error"] = {"code": exc.code, "message": str(exc)}
|
|
414
480
|
except (OSError, ValueError, KeyError, TypeError):
|
|
415
481
|
entry["error"] = {"code": "io_error", "message": "Falha de leitura/gravação; nada concluído para este agente."}
|
|
482
|
+
if gap["install"] and not entry.get("error"):
|
|
483
|
+
install_new(home, entry, gap["install"], agent, dry_run, trust)
|
|
416
484
|
if agent == "codex" and not entry.get("error"):
|
|
417
485
|
# Ramo mais provável de quem vinha da 0.14.0: no diretório do Codex só há links do
|
|
418
486
|
# Uniflow, e TODAS as cópias do codex estão na pasta compartilhada. Sem migrar aqui,
|
|
@@ -431,6 +499,9 @@ def skills_update(home, trust=True, dry_run=False):
|
|
|
431
499
|
names.append(r["name"])
|
|
432
500
|
entry = {"agent": agent, "target": target["path"], "updated": [], "unchanged": 0, "kept": kept + list(broken),
|
|
433
501
|
"error": None, "via_uniflow": projected}
|
|
502
|
+
gap = catalog_gap(agent, home, current, rows, projected, broken)
|
|
503
|
+
if gap["missing"]:
|
|
504
|
+
entry["newInCatalog"] = gap["missing"]
|
|
434
505
|
if dry_run:
|
|
435
506
|
entry["dry_run"] = True
|
|
436
507
|
if agent == "codex":
|
|
@@ -462,6 +533,8 @@ def skills_update(home, trust=True, dry_run=False):
|
|
|
462
533
|
entry["error"] = {"code": exc.code, "message": str(exc)}
|
|
463
534
|
except (OSError, ValueError, KeyError, TypeError):
|
|
464
535
|
entry["error"] = {"code": "io_error", "message": "Falha de leitura/gravação; nada concluído para este agente."}
|
|
536
|
+
if gap["install"] and not entry.get("error"):
|
|
537
|
+
install_new(home, entry, gap["install"], agent, dry_run, trust)
|
|
465
538
|
if agent == "codex" and not entry.get("error"):
|
|
466
539
|
# Mesma migração do setup, e pelo mesmo motivo: sem isto, uma máquina que só roda
|
|
467
540
|
# autoupdate/upgrade mantém a pasta compartilhada antiga para sempre. Depois da
|
package/crow_skills/machine.py
CHANGED
|
@@ -778,6 +778,17 @@ def upgrade_summary(skills, dry_run):
|
|
|
778
778
|
k["name"] + " --agents " + agent + " --global --yes")
|
|
779
779
|
else:
|
|
780
780
|
item["needs_user"].append(k["name"] + ": " + k["status"])
|
|
781
|
+
# Skill nova no catálogo: instalada quando o agente já tinha todas as outras, e NOMEADA com o
|
|
782
|
+
# comando quando ele tem um subconjunto escolhido. Antes disto o upgrade dizia "atualizadas" e
|
|
783
|
+
# a skill não existia em disco em lugar nenhum.
|
|
784
|
+
item["installed_new"] = s.get("installedNew") or []
|
|
785
|
+
pending = [n for n in (s.get("newInCatalog") or []) if n not in item["installed_new"]]
|
|
786
|
+
if pending:
|
|
787
|
+
item["needs_user"].append(str(len(pending)) + " skill(s) nova(s) no catálogo não instalada(s) aqui (" +
|
|
788
|
+
", ".join(pending[:3]) + "); este agente tem um subconjunto escolhido. Para instalar: "
|
|
789
|
+
"crow-skills install " + " ".join(pending[:3]) + " --agents " + agent + " --global --yes")
|
|
790
|
+
if s.get("newInCatalogError"):
|
|
791
|
+
item["needs_user"].insert(0, "skill nova do catálogo não instalada: " + s["newInCatalogError"]["message"])
|
|
781
792
|
if s.get("error"):
|
|
782
793
|
item["needs_user"].insert(0, s["error"]["message"])
|
|
783
794
|
item["state"] = "fail" if s.get("error") else ("warn" if item["needs_user"] else ("plan" if dry_run and (item["changed"] or item["removed"]) else "ok"))
|
|
@@ -904,9 +915,14 @@ def render_upgrade(data, stream=None):
|
|
|
904
915
|
changed = ", ".join(item["changed"]) if len(item["changed"]) <= 3 else str(len(item["changed"])) + " skills"
|
|
905
916
|
removed = ((" · " if changed else "") + str(len(item["removed"])) + (" a remover" if dry else " removidas") +
|
|
906
917
|
" (duplicavam o Uniflow)") if item.get("removed") else ""
|
|
918
|
+
fresh = item.get("installed_new") or []
|
|
919
|
+
# Dito na própria linha do CLI: uma skill nova do catálogo não é "atualizada", é instalada
|
|
920
|
+
# agora — e foi exatamente isso que faltou quando a 0.17.0 publicou a crow-be-human.
|
|
921
|
+
new_text = ((" · " if changed or removed else "") + (("instalaria " if dry else "instaladas ") +
|
|
922
|
+
("novas: " + ", ".join(fresh[:3]) if len(fresh) <= 3 else str(len(fresh)) + " novas"))) if fresh else ""
|
|
907
923
|
rows.append([ui.mark(STATE_MARK[item["state"]], stream), item["label"],
|
|
908
|
-
((("a atualizar: " if dry else "atualizadas: ") + changed) if changed else ("" if removed else "sem mudanças"))
|
|
909
|
-
+ removed,
|
|
924
|
+
((("a atualizar: " if dry else "atualizadas: ") + changed) if changed else ("" if removed or new_text else "sem mudanças"))
|
|
925
|
+
+ removed + new_text,
|
|
910
926
|
str(item["unchanged"]), str(item.get("via_uniflow", 0)), str(len(item["kept"]))])
|
|
911
927
|
if rows:
|
|
912
928
|
lines.append("")
|
package/package.json
CHANGED
package/release.json
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"crow_skills/__init__.py": {
|
|
19
19
|
"executable": false,
|
|
20
20
|
"kind": "file",
|
|
21
|
-
"sha256": "
|
|
21
|
+
"sha256": "430c470bc3c5bc90aecb240c2756f9d1d7465a1e8828c36e01451eb15b211dbd"
|
|
22
22
|
},
|
|
23
23
|
"crow_skills/__main__.py": {
|
|
24
24
|
"executable": false,
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"crow_skills/autoupdate.py": {
|
|
34
34
|
"executable": false,
|
|
35
35
|
"kind": "file",
|
|
36
|
-
"sha256": "
|
|
36
|
+
"sha256": "f5a994f8eb47ed77861cf2e512d7660654ba3e8cf9381229add678180ae14e0d"
|
|
37
37
|
},
|
|
38
38
|
"crow_skills/broadcast.py": {
|
|
39
39
|
"executable": false,
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"crow_skills/machine.py": {
|
|
74
74
|
"executable": false,
|
|
75
75
|
"kind": "file",
|
|
76
|
-
"sha256": "
|
|
76
|
+
"sha256": "83d3f6fa0e296b4de87d90ff90ff50784241cf346bc1947ab78dd415409ebc0d"
|
|
77
77
|
},
|
|
78
78
|
"crow_skills/npm_entry.py": {
|
|
79
79
|
"executable": false,
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
"package.json": {
|
|
129
129
|
"executable": false,
|
|
130
130
|
"kind": "file",
|
|
131
|
-
"sha256": "
|
|
131
|
+
"sha256": "84ebc44ccadb19d2e6b1640994476d016c6ebef62bb0e2d79fdd14e6643f5141"
|
|
132
132
|
},
|
|
133
133
|
"skills/crow-accessibility/SKILL.md": {
|
|
134
134
|
"executable": false,
|
|
@@ -721,7 +721,7 @@
|
|
|
721
721
|
"sha256": "e37132ef8b060548191fbf14a5a18a9dd3acd84ea9a35e802174455b9e374d3d"
|
|
722
722
|
}
|
|
723
723
|
},
|
|
724
|
-
"revision": "
|
|
724
|
+
"revision": "4c059d58b0bff2c5c3cfcf019667bb1ee93cd08d",
|
|
725
725
|
"schema_version": 1,
|
|
726
|
-
"version": "0.
|
|
726
|
+
"version": "0.20.0"
|
|
727
727
|
}
|