@andespindola/crow-skills 0.19.1 → 0.20.1

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.1"
@@ -319,6 +319,71 @@ 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
+ # NÃO entra em "updated": verificado com o binário publicado, a linha do CLI dizia as duas coisas
381
+ # da mesma skill — "a atualizar: crow-be-human · instalaria novas: crow-be-human". Instalar agora
382
+ # e atualizar o que já existia são fatos diferentes e cada um tem a sua coluna.
383
+ entry["installedNew"] = sorted(done)
384
+ if data.get("transaction"):
385
+ entry["newTransaction"] = data["transaction"]
386
+
322
387
  def reinstall_behaviour(home, entry, dry_run, trust):
323
388
  """Codex without a working crow-behaviour (its projection is broken, no native copy): install it natively,
324
389
  as setup does, so the guide and hooks keep a coordinator to point to."""
@@ -402,6 +467,9 @@ def skills_update(home, trust=True, dry_run=False):
402
467
  from . import machine
403
468
  entry = {"agent": agent, "target": target["path"], "updated": [], "unchanged": 0, "kept": list(broken), "error": None,
404
469
  "via_uniflow": projected}
470
+ gap = catalog_gap(agent, home, current, rows, projected, broken)
471
+ if gap["missing"]:
472
+ entry["newInCatalog"] = gap["missing"]
405
473
  if dry_run:
406
474
  entry["dry_run"] = True
407
475
  try:
@@ -413,6 +481,8 @@ def skills_update(home, trust=True, dry_run=False):
413
481
  entry["error"] = {"code": exc.code, "message": str(exc)}
414
482
  except (OSError, ValueError, KeyError, TypeError):
415
483
  entry["error"] = {"code": "io_error", "message": "Falha de leitura/gravação; nada concluído para este agente."}
484
+ if gap["install"] and not entry.get("error"):
485
+ install_new(home, entry, gap["install"], agent, dry_run, trust)
416
486
  if agent == "codex" and not entry.get("error"):
417
487
  # Ramo mais provável de quem vinha da 0.14.0: no diretório do Codex só há links do
418
488
  # Uniflow, e TODAS as cópias do codex estão na pasta compartilhada. Sem migrar aqui,
@@ -431,6 +501,9 @@ def skills_update(home, trust=True, dry_run=False):
431
501
  names.append(r["name"])
432
502
  entry = {"agent": agent, "target": target["path"], "updated": [], "unchanged": 0, "kept": kept + list(broken),
433
503
  "error": None, "via_uniflow": projected}
504
+ gap = catalog_gap(agent, home, current, rows, projected, broken)
505
+ if gap["missing"]:
506
+ entry["newInCatalog"] = gap["missing"]
434
507
  if dry_run:
435
508
  entry["dry_run"] = True
436
509
  if agent == "codex":
@@ -462,6 +535,8 @@ def skills_update(home, trust=True, dry_run=False):
462
535
  entry["error"] = {"code": exc.code, "message": str(exc)}
463
536
  except (OSError, ValueError, KeyError, TypeError):
464
537
  entry["error"] = {"code": "io_error", "message": "Falha de leitura/gravação; nada concluído para este agente."}
538
+ if gap["install"] and not entry.get("error"):
539
+ install_new(home, entry, gap["install"], agent, dry_run, trust)
465
540
  if agent == "codex" and not entry.get("error"):
466
541
  # Mesma migração do setup, e pelo mesmo motivo: sem isto, uma máquina que só roda
467
542
  # 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.1",
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": "fe7f42f4b197f8dbf5e96979a3d2d2ef6ff79c7d2b099d45415d8b2a9e96364c"
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": "c192495c895a95b957cad04c2d7fc058aa86cd67e76077200fa202d7c96fb039"
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": "1d95441781de5db5184c2962d48feaecb9a8c31b15f1b9afab21c7bbb08e34ca"
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": "d8b071036d6d9bda4f894d0764dfca45556ce9ff",
725
725
  "schema_version": 1,
726
- "version": "0.19.1"
726
+ "version": "0.20.1"
727
727
  }