@andespindola/crow-skills 0.11.0 → 0.11.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.
- package/README.md +2 -2
- package/crow_skills/__init__.py +1 -1
- package/crow_skills/autoupdate.py +77 -4
- package/crow_skills/cli.py +65 -2
- package/crow_skills/guide.py +29 -7
- package/crow_skills/machine.py +79 -4
- package/crow_skills/projection.py +128 -0
- package/docs/cli.md +14 -2
- package/package.json +1 -1
- package/release.json +15 -10
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Skills com escopo explícito, conhecimento especializado preservado e fluxos comuns reutilizáveis.
|
|
4
4
|
|
|
5
|
-
**Versão 0.11.
|
|
5
|
+
**Versão 0.11.1** ([mudanças](CHANGELOG.md)): o Codex deixa de mostrar em dobro as skills que o Uniflow já projeta em `~/.codex/skills`. A 0.11.0 trouxe `crow-skills setup --yes`, que configura de uma vez todos os CLIs de agente da máquina, e `crow-skills upgrade --yes`, que atualiza tudo numa só passada; os hooks que o crow-skills grava já saem habilitados (no Codex, aprovados pelo próprio `codex app-server`, só os do crow-skills; `--no-trust` desliga) e `status`/`doctor` mostram se cada CLI vai rodá-los; a ajuda ficou curta e organizada por tarefa. A 0.10.1 passou a tratar swap e CPU como eixos de pressão no controle de recursos (swap quase cheio e carga alta reduzem agentes, jobs e workers; nenhuma recomendação cresce com a pressão), mostrou a CPU% dos CLIs de agente, trouxe `crow-skills resources --exec-prefix` para rodar jobs pesados em prioridade baixa e as regras da crow-behaviour sob pressão (reduzir já, um job pesado por vez, não repetir verificação, validação ampla no CI); os hooks do Codex vão para o `config.toml` quando o `hooks.json` é symlink. A 0.10.0 trouxe o crow-broadcast, que compartilha, de forma passiva, o que cada sessão e cada CLI (Claude Code, Codex, Gemini CLI, Antigravity, Devin, inclusive dentro do Uniflow) está fazendo nesta máquina: repositório, clone, branch, execução crow em andamento e arquivos em edição; o prompt recebe um resumo só quando outra sessão viva está no mesmo repositório, editar um arquivo que outra sessão tocou ou trocar de branch num clone em uso gera aviso (ou bloqueio, se configurado), e `crow-skills broadcast claim` reserva áreas; a crow-behaviour consulta isso antes de planejar e reserva as áreas que delega ([crow-broadcast](docs/broadcast.md)). A 0.9.0 trouxe o controle de memória da máquina: `crow-skills resources` mede memória, pressão e os CLIs de agente em execução e recomenda quantos agentes, jobs pesados e workers rodar em paralelo; a crow-behaviour consulta essa recomendação antes de cada fan-out (sob `warn` reduz, sob `critical` segue sequencial), limita workers de builds e testes, reutiliza um dev server por projeto e encerra o que abriu, e 15 especialistas que sobem processos pesados ganharam a regra correspondente; o hook de prompt e as status lines só avisam quando a máquina está sob pressão. A 0.8.0 trouxe `crow-skills mode on|off`, que liga e desliga a crow-behaviour em todas as sessões e CLIs (Claude Code, Codex, Gemini CLI, Antigravity, Devin) a partir do próximo prompt, com `/crow on|off` dentro da sessão e o indicador `▸▸ crow on` na status line; a crow-behaviour mostra o progresso como a lista de agentes do host (`● main`, `○ dono atividade`); `crow-skills events` publica um barramento de eventos dos hooks para terminais; `crow-skills autoupdate on` atualiza o CLI e as skills gerenciadas todo dia e no login. A 0.7.1 trouxe as correções da revalidação no Codex às skills especialistas; a 0.7.0 tornou o Uniflow agente de instalação (`--agents uniflow`); a 0.6.1 passou a mostrar o progresso da crow-behaviour na própria conversa; a 0.6.0 trouxe as regras de entrega das 32 skills especialistas, a partir da [validação no Codex](docs/evaluations/2026-10-26-codex.md).
|
|
6
6
|
|
|
7
7
|
**Reconstrução em andamento.** A primeira versão com 32 contratos genéricos não atende à preservação de capacidades solicitada. Não use esse número nem o mapeamento por categoria como prova de substituição das originais. A revisão passa a abranger cada definição e seus recursos, modos e dependências, sem número fixo de skills ou limite artificial de linhas. Veja [critérios e progresso](docs/reconstruction.md).
|
|
8
8
|
|
|
@@ -42,7 +42,7 @@ Exemplos:
|
|
|
42
42
|
O pacote de cada skill é somente sua pasta em `skills/`. O [CLI crow-skills](docs/cli.md) instala cópias completas e versionadas em agentes/escopos explícitos, com preview, saída JSON, detecção de conflitos e recuperação. Requer Python 3.10+; Git para fontes em clone, Node 18+ para o launcher npm; mutações em macOS/Linux. A distribuição npm privada `@andespindola/crow-skills` inclui um snapshot completo das skills, sem clone ou hooks que as instalem automaticamente. No [Uniflow](docs/cli.md#uniflow), `--agents uniflow --global` grava as skills no store dele (`~/.config/uniflow/skills`), sem tocar no resto da configuração. Em `--global`, instalar a crow-behaviour também grava o guia aditivo do agente (claude, codex, gemini, devin) para usar as skills; `--no-guide` desliga. As integrações de interface (status lines do Claude Code, do Codex e do Antigravity, hooks do modo crow em cada CLI, comando `/crow`, segmento tmux) seguem o mesmo fluxo, preservando a configuração existente; `--no-integrations` desliga. O [barramento de eventos](docs/events.md) e a atualização automática (`crow-skills autoupdate on`) estão no [CLI](docs/cli.md).
|
|
43
43
|
|
|
44
44
|
```sh
|
|
45
|
-
npm install -g @andespindola/crow-skills@0.11.
|
|
45
|
+
npm install -g @andespindola/crow-skills@0.11.1
|
|
46
46
|
crow-skills mode
|
|
47
47
|
crow-skills progress
|
|
48
48
|
./bin/crow-skills list
|
package/crow_skills/__init__.py
CHANGED
|
@@ -305,6 +305,60 @@ def preview_self(install, git_pull):
|
|
|
305
305
|
return {"kind": "unknown", "result": "skipped", "reason": "instalação não reconhecida (brew, npm ou checkout git)"}
|
|
306
306
|
|
|
307
307
|
|
|
308
|
+
def broken_projections(agent, home, store, rows):
|
|
309
|
+
"""Uniflow links of this CLI whose store entry was edited or lost files: the CLI may not load them, so
|
|
310
|
+
upgrade reports them as needing the user (same as setup), with the command that restores the store."""
|
|
311
|
+
from .guide import broken_fix, store_broken
|
|
312
|
+
names = set()
|
|
313
|
+
if any(r["status"] == "uniflow_link" for r in rows):
|
|
314
|
+
bad = store_broken(home, store)
|
|
315
|
+
names |= {r["name"] for r in rows if r["status"] == "uniflow_link" and r["name"] in bad}
|
|
316
|
+
if agent == "codex":
|
|
317
|
+
from . import projection
|
|
318
|
+
names |= {n for n, kind in projection.links(home, store).items() if kind == "modified"}
|
|
319
|
+
return [{"name": n, "status": "uniflow_link_modified", "fix": broken_fix(n)} for n in sorted(names)]
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def reinstall_behaviour(home, entry, dry_run, trust):
|
|
323
|
+
"""Codex without a working crow-behaviour (its projection is broken, no native copy): install it natively,
|
|
324
|
+
as setup does, so the guide and hooks keep a coordinator to point to."""
|
|
325
|
+
from .cli import parser, run as cli_run
|
|
326
|
+
argv = ["install", "crow-behaviour", "--agents", "codex", "--global", "--json", "--home", str(home),
|
|
327
|
+
"--dry-run" if dry_run else "--yes"]
|
|
328
|
+
if not trust:
|
|
329
|
+
argv.append("--no-trust")
|
|
330
|
+
data = cli_run(parser().parse_args(argv))
|
|
331
|
+
if any(c["name"] == "crow-behaviour" and c["action"] != "unchanged" for c in data["changes"]):
|
|
332
|
+
entry["updated"].append("crow-behaviour")
|
|
333
|
+
entry["transaction"] = data.get("transaction")
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def codex_duplicates(home, store, entry, names, dry_run):
|
|
337
|
+
"""Codex sees Uniflow's links in ~/.codex/skills: those names are not updated natively, and intact managed
|
|
338
|
+
native copies of them are removed through uninstall (backup, transaction). Returns the names to update."""
|
|
339
|
+
from . import projection
|
|
340
|
+
from .cli import parser, run as cli_run
|
|
341
|
+
seen = projection.projected("codex", home, store)
|
|
342
|
+
if not seen:
|
|
343
|
+
return names
|
|
344
|
+
dupes = projection.duplicates(home, store)
|
|
345
|
+
entry["via_uniflow"] = sorted(set(entry["via_uniflow"]) | seen)
|
|
346
|
+
entry["kept"] = [k for k in entry["kept"] if k["name"] not in seen]
|
|
347
|
+
entry["kept"] += [{"name": n, "status": "duplicate_modified"} for n in dupes["modified"]]
|
|
348
|
+
entry["kept"] += [{"name": n, "status": "duplicate_shared"} for n in dupes["shared"]]
|
|
349
|
+
if dupes["remove"]:
|
|
350
|
+
argv = ["uninstall", *dupes["remove"], "--agents", "codex", "--global", "--json", "--home", str(home),
|
|
351
|
+
"--dry-run" if dry_run else "--yes", "--no-integrations"]
|
|
352
|
+
try:
|
|
353
|
+
data = cli_run(parser().parse_args(argv))
|
|
354
|
+
entry["removed"] = [{"name": c["name"], "reason": "removida: duplicava a projeção do Uniflow"}
|
|
355
|
+
for c in data["changes"] if c["action"] != "unchanged"]
|
|
356
|
+
entry["removal_transaction"] = data.get("transaction")
|
|
357
|
+
except CrowError as exc:
|
|
358
|
+
entry["error"] = {"code": exc.code, "message": str(exc)}
|
|
359
|
+
return [n for n in names if n not in seen]
|
|
360
|
+
|
|
361
|
+
|
|
308
362
|
def skills_update(home, trust=True, dry_run=False):
|
|
309
363
|
"""update --global for every agent with managed installs; modified skills are kept and reported.
|
|
310
364
|
|
|
@@ -334,17 +388,23 @@ def skills_update(home, trust=True, dry_run=False):
|
|
|
334
388
|
from .guide import store_managed
|
|
335
389
|
store_ok = store_managed(home, store) if any(r["status"] == "uniflow_link" for r in rows) else set()
|
|
336
390
|
projected = sorted(r["name"] for r in rows if r["status"] == "uniflow_link" and r["name"] in store_ok)
|
|
391
|
+
if agent == "codex":
|
|
392
|
+
from . import projection
|
|
393
|
+
projected = sorted(set(projected) | projection.projected("codex", home, store)) # ~/.codex/skills links
|
|
394
|
+
broken = broken_projections(agent, home, store, rows)
|
|
337
395
|
rows = [r for r in rows if r["name"] and r["revision"]]
|
|
338
396
|
if not rows:
|
|
339
|
-
if projected:
|
|
397
|
+
if projected or broken:
|
|
340
398
|
# Every crow skill here comes from Uniflow's store (updated as agent uniflow): only the guide,
|
|
341
399
|
# integrations and hooks of this CLI are refreshed.
|
|
342
400
|
from . import machine
|
|
343
|
-
entry = {"agent": agent, "target": target["path"], "updated": [], "unchanged": 0, "kept":
|
|
401
|
+
entry = {"agent": agent, "target": target["path"], "updated": [], "unchanged": 0, "kept": list(broken), "error": None,
|
|
344
402
|
"via_uniflow": projected}
|
|
345
403
|
if dry_run:
|
|
346
404
|
entry["dry_run"] = True
|
|
347
405
|
try:
|
|
406
|
+
if agent == "codex" and "crow-behaviour" in [k["name"] for k in broken]:
|
|
407
|
+
reinstall_behaviour(home, entry, dry_run, trust)
|
|
348
408
|
extras = machine.refresh_extras(agent, home, dry_run, trust)
|
|
349
409
|
entry["guides"] = (extras.get("guides") or {}).get("transaction")
|
|
350
410
|
except CrowError as exc:
|
|
@@ -362,10 +422,23 @@ def skills_update(home, trust=True, dry_run=False):
|
|
|
362
422
|
kept.append({"name": r["name"], "status": "other_origin"}) # update --source or install switches it
|
|
363
423
|
else:
|
|
364
424
|
names.append(r["name"])
|
|
365
|
-
entry = {"agent": agent, "target": target["path"], "updated": [], "unchanged": 0, "kept": kept
|
|
366
|
-
"via_uniflow": projected}
|
|
425
|
+
entry = {"agent": agent, "target": target["path"], "updated": [], "unchanged": 0, "kept": kept + list(broken),
|
|
426
|
+
"error": None, "via_uniflow": projected}
|
|
367
427
|
if dry_run:
|
|
368
428
|
entry["dry_run"] = True
|
|
429
|
+
if agent == "codex":
|
|
430
|
+
names = codex_duplicates(home, store, entry, names, dry_run)
|
|
431
|
+
if "crow-behaviour" in [k["name"] for k in broken] and "crow-behaviour" not in [r["name"] for r in rows]:
|
|
432
|
+
try:
|
|
433
|
+
reinstall_behaviour(home, entry, dry_run, trust)
|
|
434
|
+
except CrowError as exc:
|
|
435
|
+
entry["error"] = {"code": exc.code, "message": str(exc)}
|
|
436
|
+
if not names and not entry.get("error"):
|
|
437
|
+
from . import machine
|
|
438
|
+
try:
|
|
439
|
+
machine.refresh_extras(agent, home, dry_run, trust)
|
|
440
|
+
except CrowError as exc:
|
|
441
|
+
entry["error"] = {"code": exc.code, "message": str(exc)}
|
|
369
442
|
if names:
|
|
370
443
|
try:
|
|
371
444
|
argv = ["update", *names, "--agents", agent, "--global", "--dry-run" if dry_run else "--yes", "--json",
|
package/crow_skills/cli.py
CHANGED
|
@@ -14,6 +14,7 @@ from . import guide
|
|
|
14
14
|
from . import hook
|
|
15
15
|
from . import integrations
|
|
16
16
|
from . import progress
|
|
17
|
+
from . import projection
|
|
17
18
|
from . import resources
|
|
18
19
|
from . import machine
|
|
19
20
|
from . import ui
|
|
@@ -332,6 +333,7 @@ def guide_sync(args, home, store, command, targets, plan, source):
|
|
|
332
333
|
if agent not in selected and not (record and path in touched):
|
|
333
334
|
continue
|
|
334
335
|
added, removed = touched.get(path, (set(), set()))
|
|
336
|
+
removed = removed - projection.projected(agent, home, store) # still provided by Uniflow's projection
|
|
335
337
|
after = sorted((set(guide.managed_skills(agent, home, store)) | added) - removed)
|
|
336
338
|
if mech["kind"] == "toml" and guide.tomllib is None:
|
|
337
339
|
report["skipped"].append({"agent": agent, "reason": "Python 3.11+ (tomllib) é necessário para editar config.toml; guia não alterado."})
|
|
@@ -387,6 +389,7 @@ def behaviour_agents(home, store, plan=None):
|
|
|
387
389
|
path = os.path.abspath(destinations(agent, home)[0]["path"])
|
|
388
390
|
added, removed = touched.get(path, (set(), set()))
|
|
389
391
|
try:
|
|
392
|
+
removed = removed - projection.projected(agent, home, store)
|
|
390
393
|
managed = set(guide.managed_skills(agent, home, store))
|
|
391
394
|
except CrowError:
|
|
392
395
|
# An unreadable skills root (e.g. a dotfiles symlink) holds nothing this CLI manages;
|
|
@@ -744,14 +747,43 @@ def pick(source):
|
|
|
744
747
|
return names, ",".join(chosen_groups) or None
|
|
745
748
|
|
|
746
749
|
|
|
750
|
+
def codex_projection_split(home, store, targets, names):
|
|
751
|
+
"""The user-scope codex target and which of the names Codex already sees via Uniflow (install skips them)."""
|
|
752
|
+
target = next((t for t in targets if projection.native_target("codex", t, home)), None)
|
|
753
|
+
if target is None or set(target["agents"]) - {"codex"}:
|
|
754
|
+
return None # --agents shared: other hosts read that dir without Uniflow's codex projection
|
|
755
|
+
projected = projection.projected("codex", home, store)
|
|
756
|
+
if not projected:
|
|
757
|
+
return None
|
|
758
|
+
dupes = projection.duplicates(home, store)
|
|
759
|
+
return {"target": target, "skip": set(names) & projected,
|
|
760
|
+
"duplicates": sorted(set(dupes["remove"]) | set(dupes["modified"]))}
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
def merge_plans(plans):
|
|
764
|
+
merged = {"before": {}, "after": {}, "items": [], "packages": {}, "changes": []}
|
|
765
|
+
for plan in plans:
|
|
766
|
+
merged["before"].update(plan["before"])
|
|
767
|
+
merged["after"].update(plan["after"])
|
|
768
|
+
merged["items"] += plan["items"]
|
|
769
|
+
merged["packages"].update(plan["packages"])
|
|
770
|
+
merged["changes"] += plan["changes"]
|
|
771
|
+
return merged
|
|
772
|
+
|
|
773
|
+
|
|
747
774
|
def unmanaged_projections(store, home, rows):
|
|
748
775
|
"""A Uniflow link to a store entry crow-skills does not manage is not an install (same rule as setup)."""
|
|
749
776
|
if not any(r["status"] == "uniflow_link" for r in rows):
|
|
750
777
|
return rows
|
|
751
778
|
managed = guide.store_managed(home, store)
|
|
752
779
|
disabled = machine.store_disabled(home, store)
|
|
780
|
+
broken = guide.store_broken(home, store)
|
|
753
781
|
for row in rows:
|
|
754
|
-
if row["status"] == "uniflow_link" and row["name"] in
|
|
782
|
+
if row["status"] == "uniflow_link" and row["name"] in broken:
|
|
783
|
+
# The store entry this link resolves to was edited or lost files: not a working install.
|
|
784
|
+
row.update(status="modified", projection="uniflow_link_modified",
|
|
785
|
+
note=guide.BROKEN_REASON[0].upper() + guide.BROKEN_REASON[1:] + ". Para corrigir: " + guide.broken_fix(row["name"]))
|
|
786
|
+
elif row["status"] == "uniflow_link" and row["name"] in disabled:
|
|
755
787
|
# Managed by crow-skills but disabled by Uniflow: reported as such, never as unmanaged (no --replace).
|
|
756
788
|
row.update(status="disabled", projection="uniflow_link_disabled",
|
|
757
789
|
note="Desabilitada no Uniflow (SKILL.md.disabled no store); reabilite no Uniflow para usar.")
|
|
@@ -859,6 +891,17 @@ def run(args):
|
|
|
859
891
|
result = {"installations": rows, "guides": guides, "integrations": ui, "pending_transactions": pending,
|
|
860
892
|
"mode": hook.read_mode(home), "activation_verified": False,
|
|
861
893
|
"reload_hint": "Peça ao host para recarregar/listar skills; reinicie a sessão se necessário."}
|
|
894
|
+
if any(projection.native_target("codex", t, home) for t in targets):
|
|
895
|
+
rows += projection.status_rows(home, store, rows)
|
|
896
|
+
dupes = projection.duplicates(home, store)
|
|
897
|
+
names_ = dupes["remove"] + dupes["modified"] + dupes["shared"]
|
|
898
|
+
if names_:
|
|
899
|
+
# A warning, not unhealthy: both copies are intact and work; Codex just lists them twice.
|
|
900
|
+
result["warnings"] = [{"code": "codex_duplicates", "skills": names_,
|
|
901
|
+
"message": "O Codex vê " + str(len(names_)) + " skill(s) duas vezes (cópia em ~/.agents/skills "
|
|
902
|
+
"e link do Uniflow em " + str(projection.codex_skills_dir(home)) + ").",
|
|
903
|
+
"fix": "crow-skills setup --agents codex --yes"
|
|
904
|
+
+ (" --overwrite-modified" if dupes["modified"] else "")}]
|
|
862
905
|
result["groups"] = group_coverage(rows)
|
|
863
906
|
try:
|
|
864
907
|
result["autoupdate"] = autoupdate.status(store, home) if global_view else None
|
|
@@ -942,7 +985,17 @@ def run(args):
|
|
|
942
985
|
for name in list(names):
|
|
943
986
|
source.require(name)
|
|
944
987
|
names.extend(n for n in source.catalog[name]["next"] if n not in names)
|
|
945
|
-
|
|
988
|
+
codex_view = None
|
|
989
|
+
if args.command == "install" and not (getattr(args, "project", None) or getattr(args, "dest", None)):
|
|
990
|
+
codex_view = codex_projection_split(home, store, targets, names)
|
|
991
|
+
if codex_view and codex_view["skip"]:
|
|
992
|
+
# Codex already sees these through Uniflow's links in ~/.codex/skills: no native copy for codex.
|
|
993
|
+
plans = [store.plan(args.command, [n for n in names if n not in codex_view["skip"]] if t is codex_view["target"] else names,
|
|
994
|
+
[t], source, getattr(args, "replace", False), args.overwrite_modified)
|
|
995
|
+
for t in targets if t is not codex_view["target"] or set(names) - codex_view["skip"]]
|
|
996
|
+
plan = merge_plans(plans)
|
|
997
|
+
else:
|
|
998
|
+
plan = store.plan(args.command, names, targets, source, getattr(args, "replace", False), args.overwrite_modified)
|
|
946
999
|
guides, gplan = guide_sync(args, home, store, args.command, targets, plan, source)
|
|
947
1000
|
ui = integrations_preview(args, home, store, plan)
|
|
948
1001
|
summary = {"changes": plan["changes"], "revision": source.revision if source else None,
|
|
@@ -953,6 +1006,14 @@ def run(args):
|
|
|
953
1006
|
notice = uniflow_notice(home, store, targets, plan) if args.command != "uninstall" else None
|
|
954
1007
|
if notice is not None:
|
|
955
1008
|
summary["uniflow"] = notice
|
|
1009
|
+
if codex_view and (codex_view["skip"] or codex_view["duplicates"]):
|
|
1010
|
+
summary["codex_projection"] = {
|
|
1011
|
+
"via_uniflow": sorted(codex_view["skip"]), "duplicates": codex_view["duplicates"],
|
|
1012
|
+
"note": "O Codex já vê estas skills pelo link do Uniflow em " + str(projection.codex_skills_dir(home)) +
|
|
1013
|
+
"; nenhuma cópia nativa é criada para o codex. Cópias nativas que duplicam a projeção não são removidas "
|
|
1014
|
+
"por install; remova-as com o comando sugerido ou rode crow-skills setup --yes.",
|
|
1015
|
+
"suggested_command": ("crow-skills uninstall " + " ".join(codex_view["duplicates"]) + " --agents codex --global")
|
|
1016
|
+
if codex_view["duplicates"] else None}
|
|
956
1017
|
if plan["items"] or (gplan and gplan["items"]) or ui.get("changes"):
|
|
957
1018
|
authorize(args, interactive, summary)
|
|
958
1019
|
txid = None
|
|
@@ -1023,6 +1084,8 @@ def render_status(data, doctor=False, stream=None):
|
|
|
1023
1084
|
("fail" if h["state"] in machine.BAD_HOOKS else "warn"), stream),
|
|
1024
1085
|
h["integration"], ui.word(h["state"]),
|
|
1025
1086
|
ui.clean(h.get("fix") or "")] for h in hooks], stream=stream)
|
|
1087
|
+
for warning in data.get("warnings") or []:
|
|
1088
|
+
lines += ["", ui.mark("warn", stream) + " " + ui.clean(warning["message"]) + "\n Para corrigir: " + warning["fix"]]
|
|
1026
1089
|
if data.get("pending_transactions"):
|
|
1027
1090
|
lines += ["", ui.mark("fail", stream) + " Transação pendente: crow-skills recover --yes"]
|
|
1028
1091
|
if doctor:
|
package/crow_skills/guide.py
CHANGED
|
@@ -187,17 +187,39 @@ def managed_skills(agent, home, store):
|
|
|
187
187
|
target = destinations(agent, home)[0]
|
|
188
188
|
rows = store.status([target], ())
|
|
189
189
|
store_ok = store_managed(home, store) if any(r["status"] == "uniflow_link" for r in rows) else set()
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
names = {r["name"] for r in rows if r["status"] in ("installed", "modified")
|
|
191
|
+
or (r["status"] == "uniflow_link" and r["name"] in store_ok)}
|
|
192
|
+
from .projection import projected
|
|
193
|
+
return sorted(names | projected(agent, home, store)) # codex: Uniflow's links in ~/.codex/skills
|
|
192
194
|
|
|
193
195
|
|
|
194
|
-
def
|
|
195
|
-
"""
|
|
196
|
-
A link to a store entry the user wrote by hand is not a crow-skills install for the CLI."""
|
|
196
|
+
def store_statuses(home, store):
|
|
197
|
+
"""{name: status} of the Uniflow store (crow-skills view); {} when it cannot be read."""
|
|
197
198
|
try:
|
|
198
|
-
return {r["name"] for r in store.status(destinations("uniflow", home), ()) if r["
|
|
199
|
+
return {r["name"]: r["status"] for r in store.status(destinations("uniflow", home), ()) if r["name"]}
|
|
199
200
|
except (CrowError, OSError):
|
|
200
|
-
return
|
|
201
|
+
return {}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def store_managed(home, store):
|
|
205
|
+
"""Names whose Uniflow store copy is an intact crow-skills install. A link to a store entry the user wrote
|
|
206
|
+
by hand, or to a managed entry that was edited or lost files (a CLI may fail to load it), does not count
|
|
207
|
+
as an install for the CLI."""
|
|
208
|
+
return {n for n, s in store_statuses(home, store).items() if s == "installed"}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def store_broken(home, store):
|
|
212
|
+
"""Managed store entries that were edited or lost files (status modified/missing)."""
|
|
213
|
+
return {n for n, s in store_statuses(home, store).items() if s in ("modified", "missing")}
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
BROKEN_REASON = ("a entrada do store do Uniflow foi editada ou perdeu arquivos; o CLI pode não carregá-la, então não conta "
|
|
217
|
+
"como instalada")
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def broken_fix(name):
|
|
221
|
+
return ("revise o store e, para restaurá-lo com backup: crow-skills update " + name +
|
|
222
|
+
" --agents uniflow --global --overwrite-modified --yes")
|
|
201
223
|
|
|
202
224
|
|
|
203
225
|
def present_skills(agent, home, store, catalog):
|
package/crow_skills/machine.py
CHANGED
|
@@ -173,6 +173,7 @@ def classify(store, target, names, agent, replace, overwrite, store_names=()):
|
|
|
173
173
|
pending = set(store_names) if projected else set()
|
|
174
174
|
store_ok = guide.store_managed(store.home, store) if projected else set()
|
|
175
175
|
disabled = store_disabled(store.home, store) if projected else set()
|
|
176
|
+
broken = guide.store_broken(store.home, store) if projected else set()
|
|
176
177
|
for name in names:
|
|
177
178
|
row = rows.get(name) or {"status": "absent"}
|
|
178
179
|
status = row["status"]
|
|
@@ -181,6 +182,9 @@ def classify(store, target, names, agent, replace, overwrite, store_names=()):
|
|
|
181
182
|
elif status == "uniflow_link" and name in disabled:
|
|
182
183
|
kept.append({"name": name, "status": "uniflow_link_disabled", "reason": DISABLED_REASON,
|
|
183
184
|
"fix": "reabilite a skill no Uniflow; o crow-skills mantém a desabilitação"})
|
|
185
|
+
elif status == "uniflow_link" and name in broken:
|
|
186
|
+
kept.append({"name": name, "status": "uniflow_link_modified", "reason": guide.BROKEN_REASON,
|
|
187
|
+
"fix": guide.broken_fix(name)})
|
|
184
188
|
elif status == "uniflow_link":
|
|
185
189
|
kept.append({"name": name, "status": "uniflow_link_unmanaged",
|
|
186
190
|
"reason": "link do Uniflow para um pacote do store que o crow-skills não gerencia; não conta como instalada",
|
|
@@ -240,6 +244,8 @@ def configure_agent(args, home, store, agent, names, dry_run, store_names=()):
|
|
|
240
244
|
try:
|
|
241
245
|
install, entry["kept"], entry["via_uniflow"], waiting = classify(store, target, names, agent, replace, overwrite,
|
|
242
246
|
store_names if agent != "uniflow" else ())
|
|
247
|
+
if agent == "codex":
|
|
248
|
+
install = codex_projection_step(args, home, store, entry, names, install, waiting, store_names, dry_run)
|
|
243
249
|
if waiting:
|
|
244
250
|
entry["via_uniflow_pending"] = waiting
|
|
245
251
|
if not install:
|
|
@@ -272,6 +278,60 @@ def configure_agent(args, home, store, agent, names, dry_run, store_names=()):
|
|
|
272
278
|
return entry
|
|
273
279
|
|
|
274
280
|
|
|
281
|
+
DUPLICATE = "removida: duplicava a projeção do Uniflow"
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def codex_projection_step(args, home, store, entry, names, install, waiting, store_names, dry_run):
|
|
285
|
+
"""Codex sees ~/.codex/skills (Uniflow's links to its store) and ~/.agents/skills (our copies) and lists a
|
|
286
|
+
same-name skill twice. A name Codex sees via a managed projection gets no native copy, and an intact
|
|
287
|
+
managed native copy of it is removed through uninstall (backup, transaction, rollback). Returns the
|
|
288
|
+
names still to install natively."""
|
|
289
|
+
from . import projection
|
|
290
|
+
for name, kind in sorted(projection.links(home, store).items()):
|
|
291
|
+
if kind == "modified" and name in names:
|
|
292
|
+
entry["kept"].append({"name": name, "status": "uniflow_link_modified",
|
|
293
|
+
"reason": guide.BROKEN_REASON + "; a cópia nativa em ~/.agents/skills continua valendo",
|
|
294
|
+
"fix": guide.broken_fix(name)})
|
|
295
|
+
projected = projection.projected("codex", home, store)
|
|
296
|
+
if not projected:
|
|
297
|
+
return install
|
|
298
|
+
selected = set(names)
|
|
299
|
+
# Names on their way into the store reach Codex by the same projection on its next start.
|
|
300
|
+
pending = (set(store_names) - projected - {"crow-behaviour"}) & set(install)
|
|
301
|
+
pending = {n for n in pending if n not in entry["unchanged"]}
|
|
302
|
+
native = {r["name"]: r["status"] for r in store.status([destinations("codex", home)[0]], sorted(pending)) if r["name"]}
|
|
303
|
+
pending = {n for n in pending if native.get(n, "absent") == "absent"}
|
|
304
|
+
entry["via_uniflow"] = sorted(set(entry["via_uniflow"]) | (projected & selected) | pending)
|
|
305
|
+
waiting.extend(sorted(pending))
|
|
306
|
+
dupes = projection.duplicates(home, store)
|
|
307
|
+
overwrite = bool(getattr(args, "overwrite_modified", False))
|
|
308
|
+
remove = [n for n in dupes["remove"] + (dupes["modified"] if overwrite else []) if n in selected]
|
|
309
|
+
entry["kept"] = [k for k in entry["kept"] if k["name"] not in projected]
|
|
310
|
+
for name in dupes["modified"]:
|
|
311
|
+
if name in selected and not overwrite:
|
|
312
|
+
entry["kept"].append({"name": name, "status": "duplicate_modified",
|
|
313
|
+
"reason": "cópia nativa editada que duplica a projeção do Uniflow no Codex",
|
|
314
|
+
"fix": "revise e, para remover com backup: crow-skills setup --agents codex --overwrite-modified --yes"})
|
|
315
|
+
for name in dupes["shared"]:
|
|
316
|
+
if name in selected:
|
|
317
|
+
entry["kept"].append({"name": name, "status": "duplicate_shared",
|
|
318
|
+
"reason": "cópia nativa também registrada para shared (diretório lido por outros hosts); "
|
|
319
|
+
"mantida, e o Codex a vê duas vezes",
|
|
320
|
+
"fix": "se nenhum outro host precisa dela: crow-skills uninstall " + name +
|
|
321
|
+
" --agents codex,shared --global --yes"})
|
|
322
|
+
if remove:
|
|
323
|
+
cli = _cli()
|
|
324
|
+
argv = ["uninstall", *remove, "--agents", "codex", "--global", "--json", "--home", str(home),
|
|
325
|
+
"--dry-run" if dry_run else "--yes"]
|
|
326
|
+
if overwrite:
|
|
327
|
+
argv.append("--overwrite-modified")
|
|
328
|
+
argv.append("--no-integrations") # hooks/trust follow in the install/refresh step, with the run's --no-trust
|
|
329
|
+
data = cli.run(cli.parser().parse_args(argv))
|
|
330
|
+
entry["removed"] = [{"name": c["name"], "reason": DUPLICATE} for c in data["changes"] if c["action"] != "unchanged"]
|
|
331
|
+
entry["removal_transaction"] = data.get("transaction")
|
|
332
|
+
return [n for n in install if n not in projected and n not in pending]
|
|
333
|
+
|
|
334
|
+
|
|
275
335
|
def machine_steps(args, home, store, dry_run):
|
|
276
336
|
steps = {}
|
|
277
337
|
# Crow mode: written as on only when the user never chose (no mode file).
|
|
@@ -409,6 +469,8 @@ def summarize(detected, entries, store, home, probe, dry_run, trust=True):
|
|
|
409
469
|
parts.append(str(unchanged) + " já em dia")
|
|
410
470
|
if entry.get("via_uniflow"):
|
|
411
471
|
parts.append(str(len(entry["via_uniflow"])) + " via uniflow")
|
|
472
|
+
if entry.get("removed"):
|
|
473
|
+
parts.append(str(len(entry["removed"])) + (" a remover" if dry_run else " removidas") + " (duplicavam o Uniflow)")
|
|
412
474
|
if kept:
|
|
413
475
|
parts.append(str(len(kept)) + " mantidas")
|
|
414
476
|
item["skills"] = ", ".join(parts) or "—"
|
|
@@ -466,7 +528,7 @@ def summarize(detected, entries, store, home, probe, dry_run, trust=True):
|
|
|
466
528
|
elif kept or state in ("warn", "absent"):
|
|
467
529
|
item["state"] = "warn"
|
|
468
530
|
else:
|
|
469
|
-
item["state"] = "plan" if dry_run and (entry["changed"] or real_changes(entry.get("integrations"))) else "ok"
|
|
531
|
+
item["state"] = "plan" if dry_run and (entry["changed"] or entry.get("removed") or real_changes(entry.get("integrations"))) else "ok"
|
|
470
532
|
summary.append(item)
|
|
471
533
|
return summary
|
|
472
534
|
|
|
@@ -497,7 +559,7 @@ def setup(args, home, store, interactive):
|
|
|
497
559
|
entries, steps = build(True)
|
|
498
560
|
result = {"version": __version__, "revision": source.revision, "skills": names, "detected": detected, "agents": agents,
|
|
499
561
|
"absent": [r["id"] for r in detected if not r["detected"]], "trust": not getattr(args, "no_trust", False)}
|
|
500
|
-
pending = any(e["changed"] or real_changes(e.get("integrations")) or real_changes(e.get("guides"))
|
|
562
|
+
pending = any(e["changed"] or e.get("removed") or real_changes(e.get("integrations")) or real_changes(e.get("guides"))
|
|
501
563
|
for e in entries) or any(s["state"] == "plan" for s in steps.values())
|
|
502
564
|
if dry_run or not agents:
|
|
503
565
|
summary = summarize(detected, entries, store, home, False, True)
|
|
@@ -564,11 +626,21 @@ def upgrade_summary(skills, dry_run):
|
|
|
564
626
|
agent = s["agent"]
|
|
565
627
|
item = {"agent": agent, "label": AGENT_LABELS.get(agent, agent), "changed": s.get("updated") or [],
|
|
566
628
|
"unchanged": s.get("unchanged", 0), "kept": s.get("kept") or [], "via_uniflow": len(s.get("via_uniflow") or []),
|
|
629
|
+
"removed": [r["name"] for r in s.get("removed") or []],
|
|
567
630
|
"needs_user": []}
|
|
568
631
|
for k in item["kept"]:
|
|
569
632
|
if k["status"] in ("modified", "missing"):
|
|
570
633
|
item["needs_user"].append(k["name"] + " editada localmente; para substituir com backup: crow-skills update " +
|
|
571
634
|
k["name"] + " --agents " + agent + " --global --overwrite-modified --yes")
|
|
635
|
+
elif k["status"] == "duplicate_modified":
|
|
636
|
+
item["needs_user"].append(k["name"] + " (cópia nativa editada) duplica a projeção do Uniflow no Codex; para remover "
|
|
637
|
+
"com backup: crow-skills setup --agents codex --overwrite-modified --yes")
|
|
638
|
+
elif k["status"] == "uniflow_link_modified":
|
|
639
|
+
item["needs_user"].append(k["name"] + ": a entrada do store do Uniflow foi editada ou perdeu arquivos; para "
|
|
640
|
+
"restaurá-la com backup: " + k.get("fix", "crow-skills update " + k["name"] +
|
|
641
|
+
" --agents uniflow --global --overwrite-modified --yes").split(": ", 1)[-1])
|
|
642
|
+
elif k["status"] == "duplicate_shared":
|
|
643
|
+
item["needs_user"].append(k["name"] + " duplica a projeção do Uniflow no Codex, mas também é de shared; mantida")
|
|
572
644
|
elif k["status"] == "other_origin":
|
|
573
645
|
item["needs_user"].append(k["name"] + " instalada de outra origem; para trocar: crow-skills install " +
|
|
574
646
|
k["name"] + " --agents " + agent + " --global --yes")
|
|
@@ -576,7 +648,7 @@ def upgrade_summary(skills, dry_run):
|
|
|
576
648
|
item["needs_user"].append(k["name"] + ": " + k["status"])
|
|
577
649
|
if s.get("error"):
|
|
578
650
|
item["needs_user"].insert(0, s["error"]["message"])
|
|
579
|
-
item["state"] = "fail" if s.get("error") else ("warn" if item["needs_user"] else ("plan" if dry_run and item["changed"] else "ok"))
|
|
651
|
+
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"))
|
|
580
652
|
rows.append(item)
|
|
581
653
|
return rows
|
|
582
654
|
|
|
@@ -698,8 +770,11 @@ def render_upgrade(data, stream=None):
|
|
|
698
770
|
rows = []
|
|
699
771
|
for item in data.get("summary", []):
|
|
700
772
|
changed = ", ".join(item["changed"]) if len(item["changed"]) <= 3 else str(len(item["changed"])) + " skills"
|
|
773
|
+
removed = ((" · " if changed else "") + str(len(item["removed"])) + (" a remover" if dry else " removidas") +
|
|
774
|
+
" (duplicavam o Uniflow)") if item.get("removed") else ""
|
|
701
775
|
rows.append([ui.mark(STATE_MARK[item["state"]], stream), item["label"],
|
|
702
|
-
(("a atualizar: " if dry else "atualizadas: ") + changed) if changed else "sem mudanças"
|
|
776
|
+
((("a atualizar: " if dry else "atualizadas: ") + changed) if changed else ("" if removed else "sem mudanças"))
|
|
777
|
+
+ removed,
|
|
703
778
|
str(item["unchanged"]), str(item.get("via_uniflow", 0)), str(len(item["kept"]))])
|
|
704
779
|
if rows:
|
|
705
780
|
lines.append("")
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Uniflow's projection of its store into Codex's own skills dir ($CODEX_HOME/skills, default ~/.codex/skills).
|
|
2
|
+
|
|
3
|
+
Codex scans both $CODEX_HOME/skills and ~/.agents/skills (where crow-skills installs for codex), follows
|
|
4
|
+
symlinks and lists a same-name skill twice when the two entries resolve to different directories. Uniflow
|
|
5
|
+
links ~/.codex/skills/crow-x -> <store>/crow-x, so a native copy in ~/.agents/skills duplicates it.
|
|
6
|
+
|
|
7
|
+
A link counts as the codex install of a skill ("via uniflow") only when it resolves to <store>/<name> and
|
|
8
|
+
that store entry is crow-skills-managed (guide.store_managed); a store entry Uniflow disabled is reported as
|
|
9
|
+
disabled; a link to anything else is ignored. A link ~/.codex/skills/crow-x -> ~/.agents/skills/crow-x is an
|
|
10
|
+
alias Codex deduplicates by canonical path: harmless, never touched.
|
|
11
|
+
"""
|
|
12
|
+
import os
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from .agents import destinations, real_home, uniflow_store
|
|
16
|
+
from .source import CrowError
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def codex_home(home):
|
|
20
|
+
"""$CODEX_HOME for the real profile (as Codex resolves it); an isolated --home uses <home>/.codex."""
|
|
21
|
+
home = Path(home)
|
|
22
|
+
if real_home(home) and os.environ.get("CODEX_HOME"):
|
|
23
|
+
return Path(os.path.abspath(Path(os.environ["CODEX_HOME"]).expanduser()))
|
|
24
|
+
return Path(os.path.abspath(home / ".codex"))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def codex_skills_dir(home):
|
|
28
|
+
return codex_home(home) / "skills"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def links(home, store):
|
|
32
|
+
"""{name: "managed" | "disabled" | "modified" | "unmanaged" | "alias" | "other"} for crow-* symlinks in Codex's own
|
|
33
|
+
skills dir: to an intact managed / Uniflow-disabled / edited-or-incomplete managed / hand-written store entry, to our
|
|
34
|
+
own copy, or elsewhere. Only "managed" counts as the codex install and allows removing a native duplicate."""
|
|
35
|
+
directory = codex_skills_dir(home)
|
|
36
|
+
try:
|
|
37
|
+
if directory.is_symlink() or not directory.is_dir():
|
|
38
|
+
return {}
|
|
39
|
+
entries = [p for p in directory.iterdir() if p.name.startswith("crow-") and p.is_symlink()]
|
|
40
|
+
except OSError:
|
|
41
|
+
return {}
|
|
42
|
+
if not entries:
|
|
43
|
+
return {}
|
|
44
|
+
store_dir = uniflow_store(home)
|
|
45
|
+
native = Path(destinations("codex", home)[0]["path"])
|
|
46
|
+
from .guide import store_statuses
|
|
47
|
+
statuses = store_statuses(home, store) # only an intact ("installed") store entry is a working codex install
|
|
48
|
+
result = {}
|
|
49
|
+
for link in entries:
|
|
50
|
+
name = link.name
|
|
51
|
+
resolved = os.path.realpath(link)
|
|
52
|
+
if resolved == os.path.realpath(native / name):
|
|
53
|
+
result[name] = "alias"
|
|
54
|
+
elif (resolved == os.path.realpath(store_dir / name) and (store_dir / name).is_dir()
|
|
55
|
+
and not (store_dir / name).is_symlink()):
|
|
56
|
+
state = statuses.get(name)
|
|
57
|
+
result[name] = {"installed": "managed", "disabled": "disabled", "modified": "modified",
|
|
58
|
+
"missing": "modified"}.get(state, "unmanaged")
|
|
59
|
+
else:
|
|
60
|
+
result[name] = "other"
|
|
61
|
+
return result
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def status_rows(home, store, rows):
|
|
65
|
+
"""Rows for `status`/`doctor`: Codex's view through Uniflow's links, as a destination of its own."""
|
|
66
|
+
directory = str(codex_skills_dir(home))
|
|
67
|
+
extra = []
|
|
68
|
+
for name, kind in sorted(links(home, store).items()):
|
|
69
|
+
if kind == "managed":
|
|
70
|
+
extra.append({"name": name, "target": directory, "agents": ["codex"], "status": "uniflow_link", "revision": None,
|
|
71
|
+
"activation_verified": False, "projection": "codex_home",
|
|
72
|
+
"note": "Link do Uniflow em " + directory + " para o store; é a instalação do codex (via uniflow)."})
|
|
73
|
+
elif kind == "disabled":
|
|
74
|
+
extra.append({"name": name, "target": directory, "agents": ["codex"], "status": "disabled", "revision": None,
|
|
75
|
+
"activation_verified": False, "projection": "uniflow_link_disabled",
|
|
76
|
+
"note": "Desabilitada no Uniflow (SKILL.md.disabled no store); reabilite no Uniflow para usar."})
|
|
77
|
+
elif kind == "modified":
|
|
78
|
+
from .guide import BROKEN_REASON, broken_fix
|
|
79
|
+
extra.append({"name": name, "target": directory, "agents": ["codex"], "status": "modified", "revision": None,
|
|
80
|
+
"activation_verified": False, "projection": "uniflow_link_modified",
|
|
81
|
+
"note": BROKEN_REASON[0].upper() + BROKEN_REASON[1:] + ". Para corrigir: " + broken_fix(name)})
|
|
82
|
+
elif kind == "unmanaged":
|
|
83
|
+
extra.append({"name": name, "target": directory, "agents": ["codex"], "status": "unmanaged", "revision": None,
|
|
84
|
+
"activation_verified": False, "projection": "uniflow_link_unmanaged",
|
|
85
|
+
"note": "Link do Uniflow para um pacote do store que o crow-skills não gerencia; não conta como instalada. "
|
|
86
|
+
"Revise o store e, para gerenciá-lo com backup: crow-skills setup --agents uniflow --replace --yes"})
|
|
87
|
+
return extra
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def projected(agent, home, store):
|
|
91
|
+
"""Names Codex already sees through a managed Uniflow projection (empty for other agents)."""
|
|
92
|
+
if agent != "codex":
|
|
93
|
+
return set()
|
|
94
|
+
return {n for n, kind in links(home, store).items() if kind == "managed"}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def native_target(agent, target, home):
|
|
98
|
+
"""target is the user-scope codex install dir (not --project/--dest)."""
|
|
99
|
+
return agent == "codex" and os.path.abspath(target["path"]) == os.path.abspath(destinations("codex", home)[0]["path"])
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def duplicates(home, store):
|
|
103
|
+
"""Native copies in ~/.agents/skills that duplicate a managed projection, by what can be done with them.
|
|
104
|
+
|
|
105
|
+
Returns {"remove": [...], "modified": [...], "shared": [...]}: "remove" are crow-skills-managed and intact;
|
|
106
|
+
"modified" were edited locally (kept unless --overwrite-modified); "shared" are also recorded for another
|
|
107
|
+
agent of that dir (shared), which may rely on them."""
|
|
108
|
+
names = projected("codex", home, store)
|
|
109
|
+
result = {"remove": [], "modified": [], "shared": []}
|
|
110
|
+
if not names:
|
|
111
|
+
return result
|
|
112
|
+
target = destinations("codex", home)[0]
|
|
113
|
+
try:
|
|
114
|
+
rows = {r["name"]: r for r in store.status([target], sorted(names)) if r["name"]}
|
|
115
|
+
records = store.state(target["path"])["installed"]
|
|
116
|
+
except (CrowError, OSError):
|
|
117
|
+
return result
|
|
118
|
+
for name in sorted(names):
|
|
119
|
+
row = rows.get(name)
|
|
120
|
+
if not row or row["status"] not in ("installed", "modified", "missing"):
|
|
121
|
+
continue # absent, unmanaged or unreadable: nothing of ours to remove
|
|
122
|
+
if set(records.get(name, {}).get("agents") or ["codex"]) - {"codex"}:
|
|
123
|
+
result["shared"].append(name)
|
|
124
|
+
elif row["status"] == "installed":
|
|
125
|
+
result["remove"].append(name)
|
|
126
|
+
else:
|
|
127
|
+
result["modified"].append(name)
|
|
128
|
+
return result
|
package/docs/cli.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# crow-skills CLI 0.11.
|
|
1
|
+
# crow-skills CLI 0.11.1
|
|
2
2
|
|
|
3
3
|
CLI local-first para humanos e agentes. Python 3.10+, sem bibliotecas Python externas; Git é necessário para fontes em clone, não para o snapshot npm. O launcher npm requer Node 18+. Mutações suportadas em macOS/Linux (locks POSIX). Não instala o agente, não executa scripts das skills e não muda modelos, prompts base ou permissões. Instruções de uso das skills (o guia do agente, abaixo) são gravadas pelo comando `guide` e, por padrão, por `install`/`update`/`uninstall` em `--global` quando a crow-behaviour fica instalada para um agente com mecanismo verificado; `--no-guide` desliga isso. Os mesmos comandos também aplicam as [integrações de interface](#integrações-de-interface-integrations) (status lines do Claude Code, do Codex e do Antigravity, hooks do modo crow em cada CLI, comando `/crow`, segmento tmux); `--no-integrations` desliga. `crow-skills mode on|off` liga e desliga a crow-behaviour em [todas as sessões e CLIs](#modo-crow-mode); `crow-skills progress` mostra o [painel de progresso](#progresso-progress) da execução ativa; `crow-skills events` expõe o [barramento de eventos](#eventos-events) dos hooks; `crow-skills autoupdate on` liga a [atualização automática](#atualização-automática-autoupdate).
|
|
4
4
|
|
|
@@ -81,7 +81,7 @@ Para testar/distribuir o arquivo local sem registry:
|
|
|
81
81
|
|
|
82
82
|
```sh
|
|
83
83
|
npm pack
|
|
84
|
-
npm install -g ./andespindola-crow-skills-0.11.
|
|
84
|
+
npm install -g ./andespindola-crow-skills-0.11.1.tgz --ignore-scripts
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
O pacote contém o CLI Python e todas as skills do commit, com `release.json` de hashes. Não requer acesso ao GitHub ou clone para list/install. O launcher Node encaminha argumentos sem shell para Python em modo isolado, evitando imports pelo diretório do chamador/PYTHONPATH. `CROW_SKILLS_PYTHON` pode apontar para outro executável Python 3.10+. Não há dependências npm nem hooks de instalação que instalem skills automaticamente.
|
|
@@ -217,6 +217,18 @@ O Uniflow guarda as skills num store próprio e cria, ao subir cada CLI, links d
|
|
|
217
217
|
- Cópias nativas duplicadas: se claude, gemini ou devin já têm um pacote crow de mesmo nome, o Uniflow não cria o link para ele e a cópia nativa continua valendo ali, possivelmente em outra revisão (`effect: skipped`). No codex é pior: o crow-skills instala em `~/.agents/skills`, o Uniflow liga o store em `~/.codex/skills` e o Codex lê os dois, vendo duas skills de mesmo nome (`effect: both`). `install`/`update` com `uniflow` listam essas cópias em `uniflow.native_duplicates` (diretórios que não puderam ser lidos, como um `~/.claude` symlink de dotfiles, vão para `uniflow.uninspected` sem derrubar o comando) e sugerem em `uniflow.suggested_command` o `uninstall` explícito; nada é removido sem esse comando. Remover a cópia nativa da crow-behaviour remove também o guia daquele agente, que deixa de valer fora do Uniflow.
|
|
218
218
|
- Links projetados: em `status`/`doctor`, um link do Uniflow para o store num diretório de agente aparece como `uniflow_link`, e não deixa o doctor `healthy:false`.
|
|
219
219
|
|
|
220
|
+
#### Codex e a projeção do Uniflow
|
|
221
|
+
|
|
222
|
+
O Codex lê duas raízes de skills do usuário: `$CODEX_HOME/skills` (padrão `~/.codex/skills`, onde o Uniflow liga o store) e `~/.agents/skills` (onde o crow-skills instala para o codex). Ele segue symlinks e lista duas vezes uma skill de mesmo nome quando as duas entradas apontam para diretórios diferentes. Por isso:
|
|
223
|
+
|
|
224
|
+
- Um link `~/.codex/skills/crow-x` → `<store>/crow-x`, com a entrada do store gerenciada pelo crow-skills, conta como a instalação do codex (`via uniflow`). Entrada desabilitada no Uniflow aparece como `disabled`; entrada escrita à mão, como não gerenciada (com o mesmo motivo e correção do resto do Uniflow). `CODEX_HOME` vale para o perfil real; `--home` isolado usa `<home>/.codex`.
|
|
225
|
+
- `setup` e `upgrade` não criam cópia nativa em `~/.agents/skills` para esses nomes e removem, pelo `uninstall` normal (backup, transação, `rollback`), as cópias nativas gerenciadas e intactas que duplicam a projeção (`removida: duplicava a projeção do Uniflow`). Uma cópia editada fica, com aviso e a dica `--overwrite-modified`. Uma cópia também registrada para `shared` (`--agents codex,shared`: o diretório é lido por outros hosts) fica, porque outro host pode depender dela; o motivo e o comando aparecem no resumo.
|
|
226
|
+
- `install --agents codex` não cria cópia nativa de um nome projetado (`codex_projection.via_uniflow` no resumo) e nunca remove nada: as duplicatas existentes aparecem em `codex_projection.duplicates`, com `suggested_command`. Com `--agents codex,shared` instala como antes.
|
|
227
|
+
- A crow-behaviour continua nativa onde o Uniflow não a projeta, e o guia, as integrações e os hooks do Codex seguem funcionando; quando ela é projetada, o guia conta a projeção.
|
|
228
|
+
- Um link `~/.codex/skills/crow-x` → `~/.agents/skills/crow-x` é só um apelido (o Codex o deduplica pelo caminho canônico): nunca é alterado nem removido.
|
|
229
|
+
- `status`/`doctor` mostram `~/.codex/skills` como um destino do codex (`N skills (N via uniflow)`). Duplicatas geram o aviso `codex_duplicates` (`warnings`) com o comando que resolve; o doctor continua `healthy:true`, porque as duas cópias estão íntegras e funcionam (o Codex só as lista duas vezes).
|
|
230
|
+
|
|
231
|
+
|
|
220
232
|
Fontes primárias consultadas em 2026-10-20: [Codex](https://learn.chatgpt.com/docs/build-skills#where-codex-loads-local-skills), [Claude](https://code.claude.com/docs/en/skills), [Gemini](https://geminicli.com/docs/cli/using-agent-skills/), [Devin](https://docs.devin.ai/cli/extensibility/skills/overview).
|
|
221
233
|
|
|
222
234
|
## Instruções para o agente (guide)
|
package/package.json
CHANGED
package/release.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"README.md": {
|
|
4
4
|
"executable": false,
|
|
5
5
|
"kind": "file",
|
|
6
|
-
"sha256": "
|
|
6
|
+
"sha256": "2f357dd4b258621fb3801702907a33b371ec0ff3350f6c9107b60d5c98a736b1"
|
|
7
7
|
},
|
|
8
8
|
"bin/npm-crow-skills.cjs": {
|
|
9
9
|
"executable": true,
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"crow_skills/__init__.py": {
|
|
19
19
|
"executable": false,
|
|
20
20
|
"kind": "file",
|
|
21
|
-
"sha256": "
|
|
21
|
+
"sha256": "e2a03185ed4cda7560a68844180c6b1dd684ff3314befe8f0ba978f3e45daf49"
|
|
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": "bbe3c38f93d8f6e70c0ec428e3d383d7248c61c07c774420c4c47e27c7e9def5"
|
|
37
37
|
},
|
|
38
38
|
"crow_skills/broadcast.py": {
|
|
39
39
|
"executable": false,
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"crow_skills/cli.py": {
|
|
44
44
|
"executable": false,
|
|
45
45
|
"kind": "file",
|
|
46
|
-
"sha256": "
|
|
46
|
+
"sha256": "1d0730dcfc0bfd2d11747aef884f4bbca9eb7768ce0dd321f0cb7e5506419ab5"
|
|
47
47
|
},
|
|
48
48
|
"crow_skills/events.py": {
|
|
49
49
|
"executable": false,
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"crow_skills/guide.py": {
|
|
59
59
|
"executable": false,
|
|
60
60
|
"kind": "file",
|
|
61
|
-
"sha256": "
|
|
61
|
+
"sha256": "5b2767b58439c55d1f1d439bc04a9769c54aba2e59a1680aa00a97ba4f5efd0d"
|
|
62
62
|
},
|
|
63
63
|
"crow_skills/hook.py": {
|
|
64
64
|
"executable": false,
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"crow_skills/machine.py": {
|
|
74
74
|
"executable": false,
|
|
75
75
|
"kind": "file",
|
|
76
|
-
"sha256": "
|
|
76
|
+
"sha256": "f48bb54b7816ce0fc62e68694dd970f699d355e60db2fb2eab651291294c7012"
|
|
77
77
|
},
|
|
78
78
|
"crow_skills/npm_entry.py": {
|
|
79
79
|
"executable": false,
|
|
@@ -85,6 +85,11 @@
|
|
|
85
85
|
"kind": "file",
|
|
86
86
|
"sha256": "b4c34e8abf29656d38771b0d0ea87664babaeab63507df8c50ac5f248cbe0dc3"
|
|
87
87
|
},
|
|
88
|
+
"crow_skills/projection.py": {
|
|
89
|
+
"executable": false,
|
|
90
|
+
"kind": "file",
|
|
91
|
+
"sha256": "0f6a7c788e58cc54752bbf293f107a2a8e731cacefa2dcd95d3f4998f2866d4a"
|
|
92
|
+
},
|
|
88
93
|
"crow_skills/resources.py": {
|
|
89
94
|
"executable": false,
|
|
90
95
|
"kind": "file",
|
|
@@ -118,12 +123,12 @@
|
|
|
118
123
|
"docs/cli.md": {
|
|
119
124
|
"executable": false,
|
|
120
125
|
"kind": "file",
|
|
121
|
-
"sha256": "
|
|
126
|
+
"sha256": "4b39fe944290967c083cd79af886a96653ab334a7dbde6d4ad0a5c7059d38503"
|
|
122
127
|
},
|
|
123
128
|
"package.json": {
|
|
124
129
|
"executable": false,
|
|
125
130
|
"kind": "file",
|
|
126
|
-
"sha256": "
|
|
131
|
+
"sha256": "2a4fda62a127bf8ddbd35323f698e2886dda6360d41100126cdb8ba05b2a4c32"
|
|
127
132
|
},
|
|
128
133
|
"skills/crow-accessibility/SKILL.md": {
|
|
129
134
|
"executable": false,
|
|
@@ -686,7 +691,7 @@
|
|
|
686
691
|
"sha256": "e37132ef8b060548191fbf14a5a18a9dd3acd84ea9a35e802174455b9e374d3d"
|
|
687
692
|
}
|
|
688
693
|
},
|
|
689
|
-
"revision": "
|
|
694
|
+
"revision": "06d4a9e1e39dad6c977807ee67f00fa5ed311dd1",
|
|
690
695
|
"schema_version": 1,
|
|
691
|
-
"version": "0.11.
|
|
696
|
+
"version": "0.11.1"
|
|
692
697
|
}
|