@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.
@@ -1,3 +1,3 @@
1
1
  """Portable, local-first installer for the crow-skills catalogue."""
2
2
 
3
- __version__ = "0.19.1"
3
+ __version__ = "0.20.0"
@@ -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
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/crow-skills",
3
- "version": "0.19.1",
3
+ "version": "0.20.0",
4
4
  "description": "Versioned, complementary agent skills with a protected multi-agent installer",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
package/release.json CHANGED
@@ -18,7 +18,7 @@
18
18
  "crow_skills/__init__.py": {
19
19
  "executable": false,
20
20
  "kind": "file",
21
- "sha256": "0fac3e997aef5911a1a83e61ec8abc3b92abc0e02cdff187a430b3277e40b80f"
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": "8de4a6f3f7ce773dafb4c4f9b217d399becbd98b0b0638a80e27e13450e3a0f6"
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": "8b7187afae9ad85369fdc8a918e19b32bab9d95776246cda6670a963dff19df3"
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": "25d9d44e380c13b64264f33b18d0b722a1b465c693fd92d819581b78df7f6fb8"
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": "2a8fb8a28c5b1073fd5faea2da0e2b4fe631fab7",
724
+ "revision": "4c059d58b0bff2c5c3cfcf019667bb1ee93cd08d",
725
725
  "schema_version": 1,
726
- "version": "0.19.1"
726
+ "version": "0.20.0"
727
727
  }