@andespindola/crow-skills 0.10.0 → 0.11.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 +16 -2
- package/catalog.json +24 -0
- package/crow_skills/__init__.py +1 -1
- package/crow_skills/autoupdate.py +71 -10
- package/crow_skills/cli.py +379 -61
- package/crow_skills/events.py +5 -2
- package/crow_skills/groups.py +76 -0
- package/crow_skills/guide.py +15 -2
- package/crow_skills/integrations.py +404 -13
- package/crow_skills/machine.py +720 -0
- package/crow_skills/resources.py +326 -102
- package/crow_skills/trust.py +437 -0
- package/crow_skills/ui.py +198 -0
- package/docs/cli.md +97 -19
- package/package.json +1 -1
- package/release.json +37 -17
- package/skills/crow-behaviour/SKILL.md +2 -1
- package/skills/crow-behaviour/references/progress-ledger.md +1 -1
- package/skills/crow-behaviour/references/resources.md +25 -5
- package/skills/crow-behaviour/scripts/crow_ledger.py +7 -1
package/crow_skills/cli.py
CHANGED
|
@@ -9,22 +9,99 @@ from . import __version__
|
|
|
9
9
|
from . import autoupdate
|
|
10
10
|
from . import broadcast
|
|
11
11
|
from . import events
|
|
12
|
+
from . import groups as groups_mod
|
|
12
13
|
from . import guide
|
|
13
14
|
from . import hook
|
|
14
15
|
from . import integrations
|
|
15
16
|
from . import progress
|
|
16
17
|
from . import resources
|
|
18
|
+
from . import machine
|
|
19
|
+
from . import ui
|
|
17
20
|
from .agents import AGENTS, destinations, inventory
|
|
18
21
|
from .source import CrowError, Source, valid_name
|
|
19
22
|
from .storage import Store, checked
|
|
20
23
|
|
|
21
24
|
DEFAULT_SOURCE = Path(__file__).resolve().parents[1]
|
|
22
|
-
MUTATIONS = {"install", "update", "uninstall", "rollback", "recover", "guide", "integrations", "mode", "autoupdate"}
|
|
25
|
+
MUTATIONS = {"install", "update", "uninstall", "rollback", "recover", "guide", "integrations", "mode", "autoupdate", "setup", "upgrade"}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
TRUST_HELP = "Não habilitar/aprovar os hooks do crow-skills nos CLIs (padrão: habilita)"
|
|
29
|
+
|
|
30
|
+
# Per-command help: what it is for and real examples (crow-skills help <cmd> or <cmd> --help).
|
|
31
|
+
COMMANDS = {
|
|
32
|
+
"setup": ("Detecta os CLIs de agente desta máquina (Claude Code, Codex, Gemini CLI, Antigravity, Devin, Uniflow, tmux) e "
|
|
33
|
+
"configura cada um no escopo do usuário: todas as skills do catálogo, guia, integrações, hooks do modo crow "
|
|
34
|
+
"habilitados, modo crow e autoupdate. Rodar de novo só corrige o que mudou.",
|
|
35
|
+
["crow-skills setup --dry-run", "crow-skills setup --yes", "crow-skills setup --agents claude,codex --yes",
|
|
36
|
+
"crow-skills setup --skills crow-behaviour,crow-debug --no-autoupdate --yes"]),
|
|
37
|
+
"upgrade": ("Atualiza o próprio crow-skills (Homebrew, npm ou checkout git) e, em seguida, skills, guias, integrações e "
|
|
38
|
+
"hooks de todo agente configurado. Skills editadas localmente nunca são sobrescritas.",
|
|
39
|
+
["crow-skills upgrade --dry-run", "crow-skills upgrade --yes", "crow-skills upgrade --skills-only --yes"]),
|
|
40
|
+
"help": ("Ajuda curta, por tarefa; help <comando> detalha um comando e help flags lista as flags globais.",
|
|
41
|
+
["crow-skills help", "crow-skills help setup", "crow-skills help flags"]),
|
|
42
|
+
"list": ("Lista o catálogo de skills da fonte atual.", ["crow-skills list", "crow-skills list --json"]),
|
|
43
|
+
"groups": ("Grupos de skills pré-definidos no catálogo, para install --group e setup --group (todas = o catálogo inteiro; "
|
|
44
|
+
"a crow-behaviour entra com qualquer grupo).", ["crow-skills groups", "crow-skills groups --json"]),
|
|
45
|
+
"agents": ("Agentes detectados (PATH e diretórios); não prova que as skills carregam.", ["crow-skills agents --json"]),
|
|
46
|
+
"status": ("Instalações, guias, integrações, modo e autoupdate, por agente.", ["crow-skills status", "crow-skills status --agents claude"]),
|
|
47
|
+
"doctor": ("Como status, com healthy true/false: skills editadas/ausentes, guia alterado, hooks não aprovados, transação pendente.",
|
|
48
|
+
["crow-skills doctor", "crow-skills doctor --json"]),
|
|
49
|
+
"info": ("Detalhes de uma skill do catálogo.", ["crow-skills info crow-backend"]),
|
|
50
|
+
"related": ("Handoffs, overlays e disponibilidade de complementos de uma skill.", ["crow-skills related crow-backend --agents claude --global"]),
|
|
51
|
+
"install": ("Instala skills escolhidas em agentes e escopo explícitos, com preview, backup e transação.",
|
|
52
|
+
["crow-skills install crow-behaviour --agents claude,codex --global --dry-run",
|
|
53
|
+
"crow-skills install --group essencial --agents claude --global --yes",
|
|
54
|
+
"crow-skills install --group web,qualidade --agents codex --global --yes",
|
|
55
|
+
"crow-skills install --all --agents claude --global --yes",
|
|
56
|
+
"crow-skills install crow-debug --agents gemini --project . --yes"]),
|
|
57
|
+
"update": ("Atualiza skills gerenciadas para a revisão da fonte atual (sem self-upgrade; para tudo, use upgrade).",
|
|
58
|
+
["crow-skills update --all --agents claude --global --yes", "crow-skills update crow-debug --agents codex --global --dry-run"]),
|
|
59
|
+
"uninstall": ("Remove skills gerenciadas, com backup; edições locais exigem --overwrite-modified.",
|
|
60
|
+
["crow-skills uninstall crow-debug --agents claude --global --yes"]),
|
|
61
|
+
"guide": ("Instruções gerenciadas para o agente usar as skills (só --global).",
|
|
62
|
+
["crow-skills guide status", "crow-skills guide install --agents claude --global --yes"]),
|
|
63
|
+
"integrations": ("Status lines, hooks do modo crow, comando /crow e segmento tmux (só --global).",
|
|
64
|
+
["crow-skills integrations status", "crow-skills integrations install --global --yes"]),
|
|
65
|
+
"progress": ("Painel de progresso da execução crow-behaviour (ledger, barras, tokens).",
|
|
66
|
+
["crow-skills progress", "crow-skills progress --watch"]),
|
|
67
|
+
"mode": ("Liga/desliga a crow-behaviour em todas as sessões e CLIs, a partir do próximo prompt.",
|
|
68
|
+
["crow-skills mode", "crow-skills mode off", "crow-skills mode on"]),
|
|
69
|
+
"events": ("Barramento de eventos dos hooks (crow-event/1) para terminais.",
|
|
70
|
+
["crow-skills events", "crow-skills events tail --limit 20", "crow-skills events tail --follow"]),
|
|
71
|
+
"broadcast": ("Sessões vivas dos CLIs, arquivos em edição, conflitos e reservas.",
|
|
72
|
+
["crow-skills broadcast", "crow-skills broadcast claim src/api --ttl 30m", "crow-skills broadcast release --all"]),
|
|
73
|
+
"resources": ("Memória, pressão, agentes em execução e paralelismo recomendado (somente leitura).",
|
|
74
|
+
["crow-skills resources", "crow-skills resources --exec-prefix"]),
|
|
75
|
+
"autoupdate": ("Agenda a atualização diária e no login (o mesmo fluxo de upgrade) ou roda agora.",
|
|
76
|
+
["crow-skills autoupdate on --yes", "crow-skills autoupdate status", "crow-skills autoupdate run --yes"]),
|
|
77
|
+
"rollback": ("Desfaz uma transação concluída (skills e o guia ligado a ela).", ["crow-skills rollback --transaction <id> --yes"]),
|
|
78
|
+
"recover": ("Conclui ou desfaz uma transação interrompida.", ["crow-skills recover --dry-run", "crow-skills recover --yes"]),
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class Formatter(argparse.RawDescriptionHelpFormatter):
|
|
83
|
+
def add_usage(self, usage, actions, groups, prefix=None):
|
|
84
|
+
return super().add_usage(usage, actions, groups, "uso: " if prefix is None else prefix)
|
|
23
85
|
|
|
24
86
|
|
|
25
87
|
class Parser(argparse.ArgumentParser):
|
|
88
|
+
def __init__(self, *args, **kwargs):
|
|
89
|
+
kwargs.setdefault("formatter_class", Formatter)
|
|
90
|
+
super().__init__(*args, **kwargs)
|
|
91
|
+
self._positionals.title = "argumentos"
|
|
92
|
+
self._optionals.title = "opções"
|
|
93
|
+
for action in self._actions:
|
|
94
|
+
if isinstance(action, argparse._HelpAction):
|
|
95
|
+
action.help = "Mostra esta ajuda e sai"
|
|
96
|
+
|
|
26
97
|
def error(self, message):
|
|
27
|
-
raise CrowError("usage", message)
|
|
98
|
+
raise CrowError("usage", ui.translate_usage(message))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def command_parser(sub, name, parents, aliases=()):
|
|
102
|
+
text, examples = COMMANDS[name]
|
|
103
|
+
return sub.add_parser(name, parents=parents, aliases=list(aliases), help=text, description=text,
|
|
104
|
+
epilog="exemplos:\n" + "\n".join(" $ " + e for e in examples))
|
|
28
105
|
|
|
29
106
|
|
|
30
107
|
def parser():
|
|
@@ -38,24 +115,46 @@ def parser():
|
|
|
38
115
|
("--project", {"help": "Diretório físico do projeto"}),
|
|
39
116
|
("--dest", {"help": "Diretório de skills explícito, para outro host"}),
|
|
40
117
|
("--json", {"action": "store_true", "help": "Envelope JSON; nunca abre prompts"}),
|
|
41
|
-
("--non-interactive", {"action": "store_true"}),
|
|
118
|
+
("--non-interactive", {"action": "store_true", "help": "Nunca perguntar; sem --yes, mutações falham"}),
|
|
42
119
|
("--yes", {"action": "store_true", "help": "Aplicar o escopo explícito sem prompt"}),
|
|
43
120
|
("--dry-run", {"action": "store_true", "help": "Preview sem gravar estado ou instalar"}),
|
|
44
121
|
("--fetch", {"action": "store_true", "help": "Buscar origin explicitamente; não cria clone"}),
|
|
45
122
|
]:
|
|
46
123
|
common.add_argument(flag, default=argparse.SUPPRESS, **kwargs)
|
|
47
|
-
|
|
124
|
+
common._optionals.title = "flags globais (crow-skills help flags)"
|
|
125
|
+
p = Parser(prog="crow-skills", parents=[common], add_help=False,
|
|
126
|
+
description="Instale pacotes crow completos, por agente e revisão, sem executar scripts de skills.")
|
|
127
|
+
p.add_argument("-h", "--help", dest="show_help", action="store_true", default=argparse.SUPPRESS,
|
|
128
|
+
help="Ajuda curta, por tarefa")
|
|
48
129
|
p.add_argument("--version", action="store_true")
|
|
49
|
-
sub = p.add_subparsers(dest="command")
|
|
50
|
-
|
|
51
|
-
|
|
130
|
+
sub = p.add_subparsers(dest="command", title="comandos", metavar="<comando>")
|
|
131
|
+
child = command_parser(sub, "setup", [common], aliases=("init",))
|
|
132
|
+
child.add_argument("--skills", help="Só estas skills (separadas por vírgula); padrão: todo o catálogo")
|
|
133
|
+
child.add_argument("--group", help="Só estes grupos (separados por vírgula; veja crow-skills groups); padrão: todas")
|
|
134
|
+
child.add_argument("--no-autoupdate", action="store_true", help="Não ligar o autoupdate")
|
|
135
|
+
child.add_argument("--replace", action="store_true", help="Autorizar backup e substituição de pacote/guia não gerenciado")
|
|
136
|
+
child.add_argument("--overwrite-modified", action="store_true", help="Autorizar backup e substituição de skills/guias editados")
|
|
137
|
+
child.add_argument("--no-trust", action="store_true", help=TRUST_HELP)
|
|
138
|
+
child.add_argument("--no-probe", action="store_true", help=argparse.SUPPRESS)
|
|
139
|
+
child = command_parser(sub, "upgrade", [common])
|
|
140
|
+
child.add_argument("--skills-only", action="store_true", help="Só skills/guias/hooks, sem atualizar o crow-skills")
|
|
141
|
+
child.add_argument("--no-self", action="store_true", help="Igual a --skills-only")
|
|
142
|
+
child.add_argument("--git-pull", action="store_true", help="Checkout git: git pull --ff-only se estiver limpo em main/master")
|
|
143
|
+
child.add_argument("--no-trust", action="store_true", help=TRUST_HELP)
|
|
144
|
+
child = command_parser(sub, "help", [common])
|
|
145
|
+
child.add_argument("topic", nargs="?", help="Comando, ou flags")
|
|
146
|
+
for name in ("list", "groups", "agents", "status", "doctor"):
|
|
147
|
+
command_parser(sub, name, [common])
|
|
52
148
|
for name in ("info", "related"):
|
|
53
|
-
child = sub
|
|
149
|
+
child = command_parser(sub, name, [common])
|
|
54
150
|
child.add_argument("name")
|
|
55
151
|
for name in ("install", "update", "uninstall"):
|
|
56
|
-
child = sub
|
|
152
|
+
child = command_parser(sub, name, [common])
|
|
57
153
|
child.add_argument("skills", nargs="*")
|
|
58
154
|
child.add_argument("--all", action="store_true", help="Todos do catálogo (install) ou gerenciados (update/uninstall)")
|
|
155
|
+
if name == "install":
|
|
156
|
+
child.add_argument("--group", help="Grupos pré-definidos, separados por vírgula (crow-skills groups); "
|
|
157
|
+
"todas = catálogo inteiro; crow-behaviour sempre entra")
|
|
59
158
|
if name != "uninstall":
|
|
60
159
|
child.add_argument("--replace", action="store_true", help="Autorizar substituição com backup de pacote não gerenciado")
|
|
61
160
|
child.add_argument("--with-handoffs", action="store_true", help="Incluir apenas handoffs diretos opcionais do catálogo")
|
|
@@ -63,24 +162,26 @@ def parser():
|
|
|
63
162
|
child.add_argument("--no-guide", action="store_true", help="Não aplicar/atualizar/remover o guia do agente (--global)")
|
|
64
163
|
child.add_argument("--no-integrations", action="store_true",
|
|
65
164
|
help="Não aplicar/remover as integrações de interface (status line, tmux) em --global")
|
|
66
|
-
|
|
165
|
+
if name != "uninstall":
|
|
166
|
+
child.add_argument("--no-trust", action="store_true", help=TRUST_HELP)
|
|
167
|
+
child = command_parser(sub, "guide", [common])
|
|
67
168
|
child.add_argument("action", choices=("install", "status", "uninstall"))
|
|
68
169
|
child.add_argument("--replace", action="store_true", help="Autorizar backup e substituição de conteúdo não gerenciado no caminho do guia")
|
|
69
170
|
child.add_argument("--overwrite-modified", action="store_true", help="Autorizar backup e substituição/remoção de guia editado")
|
|
70
|
-
child = sub
|
|
171
|
+
child = command_parser(sub, "integrations", [common])
|
|
71
172
|
child.add_argument("action", choices=("install", "status", "uninstall"))
|
|
72
|
-
child
|
|
173
|
+
child.add_argument("--no-trust", action="store_true", help=TRUST_HELP)
|
|
174
|
+
child = command_parser(sub, "progress", [common])
|
|
73
175
|
child.add_argument("--watch", action="store_true", help="Redesenhar até a execução terminar ou Ctrl-C")
|
|
74
176
|
child.add_argument("--run", help="ID da execução; padrão: a ativa mais recente do diretório")
|
|
75
177
|
child.add_argument("--cwd", help="Diretório da execução; padrão: o atual")
|
|
76
|
-
child = sub
|
|
178
|
+
child = command_parser(sub, "mode", [common])
|
|
77
179
|
child.add_argument("action", nargs="?", default="status", choices=("on", "off", "status", "toggle"))
|
|
78
|
-
child = sub
|
|
180
|
+
child = command_parser(sub, "events", [common])
|
|
79
181
|
child.add_argument("action", nargs="?", default="snapshot", choices=("snapshot", "tail"))
|
|
80
182
|
child.add_argument("--limit", type=int, default=50, help="tail: quantos eventos (padrão 50; 0 = todos os retidos)")
|
|
81
183
|
child.add_argument("--follow", action="store_true", help="tail: transmitir eventos novos em JSONL até Ctrl-C")
|
|
82
|
-
child = sub
|
|
83
|
-
help="crow-broadcast: sessões vivas dos CLIs, arquivos em edição, conflitos e reservas")
|
|
184
|
+
child = command_parser(sub, "broadcast", [common])
|
|
84
185
|
child.add_argument("action", nargs="?", default="status", choices=("status", "list", "claim", "release", "watch"))
|
|
85
186
|
child.add_argument("targets", nargs="*", help="claim: caminhos, globs ou área; release: ids das reservas")
|
|
86
187
|
child.add_argument("--ttl", default="30m", help="claim: validade (ex.: 30m, 2h; máximo 24h)")
|
|
@@ -93,17 +194,21 @@ def parser():
|
|
|
93
194
|
child.add_argument("--all", action="store_true", help="release: todas as reservas desta sessão")
|
|
94
195
|
child.add_argument("--interval", type=float, default=2.0, help=argparse.SUPPRESS)
|
|
95
196
|
child.add_argument("--max-frames", type=int, default=None, help=argparse.SUPPRESS)
|
|
96
|
-
child = sub
|
|
97
|
-
help="Memória, pressão, agentes em execução e paralelismo recomendado (somente leitura)")
|
|
197
|
+
child = command_parser(sub, "resources", [common])
|
|
98
198
|
child.add_argument("--no-processes", action="store_true", help="Não varrer os processos dos CLIs de agente (mais rápido)")
|
|
99
|
-
child
|
|
199
|
+
child.add_argument("--exec-prefix", action="store_true",
|
|
200
|
+
help="Só imprimir o prefixo que roda um job pesado em prioridade baixa neste sistema (taskpolicy/nice/ionice/start /low)")
|
|
201
|
+
child = command_parser(sub, "autoupdate", [common])
|
|
100
202
|
child.add_argument("action", choices=("on", "off", "status", "run"))
|
|
101
203
|
child.add_argument("--git-pull", action="store_true", help="on: também atualizar um checkout git limpo em main/master (git pull --ff-only)")
|
|
102
204
|
child.add_argument("--skills-only", action="store_true", help="run: só atualizar as skills, sem atualizar o crow-skills")
|
|
103
205
|
child.add_argument("--no-self", action="store_true", help="run: igual a --skills-only")
|
|
206
|
+
child.add_argument("--no-trust", action="store_true", help=TRUST_HELP)
|
|
104
207
|
for name in ("rollback", "recover"):
|
|
105
|
-
child = sub
|
|
208
|
+
child = command_parser(sub, name, [common])
|
|
106
209
|
child.add_argument("--transaction", required=name == "rollback")
|
|
210
|
+
p.common = common
|
|
211
|
+
p.commands = sub.choices
|
|
107
212
|
return p
|
|
108
213
|
|
|
109
214
|
|
|
@@ -299,7 +404,12 @@ def integrations_preview(args, home, store, plan):
|
|
|
299
404
|
if getattr(args, "project", None) or getattr(args, "dest", None):
|
|
300
405
|
return {"changes": [], "skipped": [{"integration": "*", "reason": "Integrações de interface só existem no escopo do usuário (--global)."}],
|
|
301
406
|
"disabled": True}
|
|
302
|
-
return integrations.sync(store, home, integration_agents(args, home, store, plan), dry_run=True)
|
|
407
|
+
return integrations.sync(store, home, integration_agents(args, home, store, plan), dry_run=True, trust=trusting(args))
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def trusting(args):
|
|
411
|
+
"""Hooks are enabled/approved in each CLI unless --no-trust."""
|
|
412
|
+
return not getattr(args, "no_trust", False)
|
|
303
413
|
|
|
304
414
|
|
|
305
415
|
def integration_agents(args, home, store, plan=None, selected=None):
|
|
@@ -316,7 +426,7 @@ def integration_agents(args, home, store, plan=None, selected=None):
|
|
|
316
426
|
|
|
317
427
|
def integrations_command(args, home, store, interactive):
|
|
318
428
|
if args.action == "status":
|
|
319
|
-
return {"integrations": integrations.status(store, home)}
|
|
429
|
+
return {"integrations": integrations.status(store, home, probe=False)}
|
|
320
430
|
if not getattr(args, "global_scope", False):
|
|
321
431
|
raise CrowError("usage", "integrations " + args.action + " exige --global explícito (único escopo suportado).")
|
|
322
432
|
wanted = []
|
|
@@ -325,12 +435,12 @@ def integrations_command(args, home, store, interactive):
|
|
|
325
435
|
if args.action == "install" and not wanted:
|
|
326
436
|
raise CrowError("guide_requires_skill", "crow-behaviour não está instalada para nenhum agente; instale antes: "
|
|
327
437
|
"crow-skills install crow-behaviour --agents claude --global --yes")
|
|
328
|
-
preview = integrations.sync(store, home, wanted, dry_run=True)
|
|
438
|
+
preview = integrations.sync(store, home, wanted, dry_run=True, trust=trusting(args))
|
|
329
439
|
if preview["changes"]:
|
|
330
440
|
authorize(args, interactive, preview)
|
|
331
441
|
if getattr(args, "dry_run", False):
|
|
332
442
|
return {**preview, "applied": False}
|
|
333
|
-
return {**integrations.sync(store, home, wanted), "applied": True}
|
|
443
|
+
return {**integrations.sync(store, home, wanted, trust=trusting(args)), "applied": True}
|
|
334
444
|
|
|
335
445
|
|
|
336
446
|
def progress_command(args, home):
|
|
@@ -352,7 +462,7 @@ def mode_command(args, home):
|
|
|
352
462
|
wanted = args.action if args.action != "toggle" else ("off" if current["mode"] == "on" else "on")
|
|
353
463
|
current = hook.write_mode(home, wanted, "cli")
|
|
354
464
|
try:
|
|
355
|
-
installed = [r["integration"] for r in integrations.status(Store(home), home)
|
|
465
|
+
installed = [r["integration"] for r in integrations.status(Store(home), home, probe=False)
|
|
356
466
|
if r["status"] == "installed" and r["integration"].endswith(("-hooks", "-statusline", "-command", "tmux-status"))]
|
|
357
467
|
except CrowError:
|
|
358
468
|
installed = []
|
|
@@ -520,12 +630,13 @@ def autoupdate_command(args, home, store, interactive):
|
|
|
520
630
|
if args.action == "status":
|
|
521
631
|
return autoupdate.status(store, home)
|
|
522
632
|
if args.action == "run":
|
|
523
|
-
|
|
633
|
+
self_step = not (args.skills_only or args.no_self)
|
|
634
|
+
authorize(args, interactive, {"autoupdate": "run", "self_upgrade": self_step})
|
|
524
635
|
if getattr(args, "dry_run", False):
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
return
|
|
636
|
+
preview = autoupdate.preview(store, home, self_step=self_step, trust=trusting(args))
|
|
637
|
+
return dict(preview, summary=machine.upgrade_summary(preview.get("skills", []), True))
|
|
638
|
+
done = autoupdate.run(store, home, self_step=self_step, trust=trusting(args))
|
|
639
|
+
return dict(done, summary=machine.upgrade_summary(done["lastRun"].get("skills", []), False))
|
|
529
640
|
if args.action == "on":
|
|
530
641
|
preview = autoupdate.turn_on(store, home, dry_run=True, git_pull=args.git_pull)
|
|
531
642
|
if any(c["action"] != "unchanged" for c in preview["changes"]):
|
|
@@ -587,26 +698,130 @@ def uniflow_notice(home, store, targets, plan):
|
|
|
587
698
|
native[row["target"]]["skills"].append({"name": row["name"], "status": row["status"]})
|
|
588
699
|
if not native:
|
|
589
700
|
return {"native_duplicates": [], "uninspected": uninspected}
|
|
590
|
-
|
|
591
|
-
|
|
701
|
+
# One command per destination with only the names physically managed there: crossing names and agents
|
|
702
|
+
# would reach a Uniflow link (or an absent name) in another CLI and abort the uninstall.
|
|
703
|
+
# Destinations with the same names share one command (the usual case keeps its single command).
|
|
704
|
+
groups = {}
|
|
705
|
+
for item in native.values():
|
|
706
|
+
managed = tuple(sorted({s["name"] for s in item["skills"] if s["status"] != "unmanaged"}))
|
|
707
|
+
if managed:
|
|
708
|
+
groups.setdefault(managed, []).extend(a for a in item["agents"] if a not in groups.get(managed, []))
|
|
709
|
+
commands = ["crow-skills uninstall " + " ".join(names_) + " --agents " + ",".join(sorted(agents_)) + " --global"
|
|
710
|
+
for names_, agents_ in groups.items()]
|
|
592
711
|
return {"native_duplicates": list(native.values()), "uninspected": uninspected,
|
|
593
712
|
"note": "effect=skipped: o Uniflow não cria o link de um nome que o agente já tem nativo, e essa cópia vale no lugar do "
|
|
594
713
|
"store. effect=both: o Uniflow liga o store em ~/.codex/skills e o Codex também lê ~/.agents/skills, então vê as "
|
|
595
714
|
"duas cópias. Nada foi removido. Remover a cópia nativa de crow-behaviour remove também o guia daquele agente.",
|
|
596
|
-
"
|
|
597
|
-
|
|
715
|
+
"suggested_commands": commands, "suggested_command": " && ".join(commands) if commands else None}
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
def pick(source):
|
|
719
|
+
"""Terminal picker without dependencies: a group number or name, todas, or skill names. Default: essencial."""
|
|
720
|
+
available = groups_mod.load(source)
|
|
721
|
+
default = next((g["name"] for g in available if g["name"] == "essencial"), groups_mod.ALL)
|
|
722
|
+
print("Grupos de skills (a crow-behaviour entra com qualquer grupo):", file=sys.stderr)
|
|
723
|
+
for n, group in enumerate(available, 1):
|
|
724
|
+
print(terminal(" " + str(n).rjust(2) + ". " + group["name"].ljust(11) + " " + str(len(group["skills"])).rjust(2) +
|
|
725
|
+
" skills " + group["description"]), file=sys.stderr)
|
|
726
|
+
print(" " + str(len(available) + 1).rjust(2) + ". escolher skills individualmente", file=sys.stderr)
|
|
727
|
+
answer = prompt("Número(s) ou nome(s) separados por vírgula [" + default + "]:").strip()
|
|
728
|
+
tokens = [t.strip() for t in answer.split(",") if t.strip()] or [default]
|
|
729
|
+
if tokens == [str(len(available) + 1)]:
|
|
730
|
+
for name, item in source.catalog.items():
|
|
731
|
+
print(terminal(name + " — " + item.get("title", name)), file=sys.stderr)
|
|
732
|
+
tokens = [t.strip() for t in prompt("Skills separadas por vírgula:").split(",") if t.strip()]
|
|
733
|
+
if not tokens:
|
|
734
|
+
raise CrowError("usage", "Nenhuma skill selecionada.")
|
|
735
|
+
by_number = {str(n): g["name"] for n, g in enumerate(available, 1)}
|
|
736
|
+
chosen_groups = [by_number.get(t, t) for t in tokens if t in by_number or t in {g["name"] for g in available}]
|
|
737
|
+
skills = [t for t in tokens if t not in by_number and t not in {g["name"] for g in available}]
|
|
738
|
+
for name in skills:
|
|
739
|
+
source.require(name)
|
|
740
|
+
names = groups_mod.resolve(source, ",".join(chosen_groups)) if chosen_groups else []
|
|
741
|
+
names = list(dict.fromkeys(names + skills))
|
|
742
|
+
if not names:
|
|
743
|
+
raise CrowError("usage", "Nenhuma skill selecionada.")
|
|
744
|
+
return names, ",".join(chosen_groups) or None
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
def unmanaged_projections(store, home, rows):
|
|
748
|
+
"""A Uniflow link to a store entry crow-skills does not manage is not an install (same rule as setup)."""
|
|
749
|
+
if not any(r["status"] == "uniflow_link" for r in rows):
|
|
750
|
+
return rows
|
|
751
|
+
managed = guide.store_managed(home, store)
|
|
752
|
+
disabled = machine.store_disabled(home, store)
|
|
753
|
+
for row in rows:
|
|
754
|
+
if row["status"] == "uniflow_link" and row["name"] in disabled:
|
|
755
|
+
# Managed by crow-skills but disabled by Uniflow: reported as such, never as unmanaged (no --replace).
|
|
756
|
+
row.update(status="disabled", projection="uniflow_link_disabled",
|
|
757
|
+
note="Desabilitada no Uniflow (SKILL.md.disabled no store); reabilite no Uniflow para usar.")
|
|
758
|
+
elif row["status"] == "uniflow_link" and row["name"] not in managed:
|
|
759
|
+
row.update(status="unmanaged", projection="uniflow_link_unmanaged",
|
|
760
|
+
note="Link do Uniflow para um pacote do store que o crow-skills não gerencia; não conta como instalada. "
|
|
761
|
+
"Revise o store e, para gerenciá-lo com backup: crow-skills setup --agents uniflow --replace --yes")
|
|
762
|
+
return rows
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
def group_coverage(rows):
|
|
766
|
+
"""Per destination, which catalog groups are fully or partially installed (best effort)."""
|
|
767
|
+
try:
|
|
768
|
+
available = [g for g in groups_mod.load(Source(DEFAULT_SOURCE)) if g["name"] != groups_mod.ALL]
|
|
769
|
+
except (CrowError, OSError):
|
|
770
|
+
return []
|
|
771
|
+
by_target = {}
|
|
772
|
+
for row in rows:
|
|
773
|
+
item = by_target.setdefault(row["target"], {"target": row["target"], "agents": row["agents"], "names": set()})
|
|
774
|
+
if row["name"] and row["status"] in ("installed", "modified", "disabled", "uniflow_link"):
|
|
775
|
+
item["names"].add(row["name"])
|
|
776
|
+
return [{"target": v["target"], "agents": v["agents"],
|
|
777
|
+
"groups": [c for c in groups_mod.coverage(available, v["names"]) if c["state"] != "absent"]}
|
|
778
|
+
for v in by_target.values() if v["names"]]
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
def groups_command(args):
|
|
782
|
+
source = Source(getattr(args, "source", DEFAULT_SOURCE), getattr(args, "ref", "HEAD"), getattr(args, "fetch", False))
|
|
783
|
+
available = groups_mod.load(source)
|
|
784
|
+
return {"revision": source.revision, "groups": available, "always": groups_mod.BEHAVIOUR,
|
|
785
|
+
"note": "crow-behaviour entra com qualquer grupo; handoffs de outros grupos são sugeridos, não instalados "
|
|
786
|
+
"(use --with-handoffs)."}
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
def help_command(args, home, topic=None):
|
|
790
|
+
"""Short, task-oriented help; help <command> for one command; help flags for the global flags."""
|
|
791
|
+
p = parser()
|
|
792
|
+
if topic in (None, "", "help") and getattr(args, "command", None) in (None, "help"):
|
|
793
|
+
line, status = machine.status_line(home)
|
|
794
|
+
return {"topic": None, "sections": [{"title": t, "commands": [{"name": n, "summary": d, "example": e} for n, d, e in items]}
|
|
795
|
+
for t, items in ui.SECTIONS],
|
|
796
|
+
"status": status, "_status_line": line, "text": None}
|
|
797
|
+
if topic == "flags":
|
|
798
|
+
return {"topic": "flags", "text": ui.flags_help(p.common._actions)}
|
|
799
|
+
name = "setup" if topic == "init" else topic
|
|
800
|
+
if name not in p.commands:
|
|
801
|
+
raise CrowError("usage", "Comando desconhecido: " + str(topic) + ".")
|
|
802
|
+
return {"topic": name, "text": p.commands[name].format_help(), "examples": COMMANDS.get(name, ("", []))[1]}
|
|
598
803
|
|
|
599
804
|
|
|
600
805
|
def run(args):
|
|
806
|
+
if getattr(args, "command", None) == "init":
|
|
807
|
+
args.command = "setup"
|
|
808
|
+
if getattr(args, "show_help", False):
|
|
809
|
+
return help_command(args, checked(getattr(args, "home", Path.home())), args.command)
|
|
601
810
|
if args.version:
|
|
602
811
|
return {"version": __version__}
|
|
603
|
-
if not args.command:
|
|
604
|
-
raise CrowError("usage", "Escolha um comando; use crow-skills --help.")
|
|
605
812
|
home = checked(getattr(args, "home", Path.home()))
|
|
813
|
+
if not args.command or args.command == "help":
|
|
814
|
+
return help_command(args, home, getattr(args, "topic", None))
|
|
606
815
|
store = Store(home)
|
|
607
816
|
interactive = sys.stdin.isatty() and not getattr(args, "non_interactive", False) and not getattr(args, "json", False)
|
|
608
|
-
if
|
|
817
|
+
if args.command == "setup":
|
|
818
|
+
return machine.setup(args, home, store, interactive)
|
|
819
|
+
if args.command == "upgrade":
|
|
820
|
+
return machine.upgrade(args, home, store, interactive)
|
|
821
|
+
if getattr(args, "fetch", False) and (getattr(args, "dry_run", False) or args.command not in ("install", "update", "list", "info", "related", "guide", "groups")):
|
|
609
822
|
raise CrowError("usage", "--fetch requer um comando com fonte e não pode ser combinado com --dry-run.")
|
|
823
|
+
if args.command == "groups":
|
|
824
|
+
return groups_command(args)
|
|
610
825
|
if args.command == "agents":
|
|
611
826
|
return {"agents": inventory(home), "note": "Detecção por PATH/diretórios não comprova carregamento da skill."}
|
|
612
827
|
if args.command == "guide":
|
|
@@ -622,6 +837,8 @@ def run(args):
|
|
|
622
837
|
if args.command == "broadcast":
|
|
623
838
|
return broadcast_command(args, home)
|
|
624
839
|
if args.command == "resources":
|
|
840
|
+
if args.exec_prefix:
|
|
841
|
+
return resources.exec_prefix_data()
|
|
625
842
|
data = resources.probe(home, processes=not args.no_processes)
|
|
626
843
|
data["text"] = resources.render(data)
|
|
627
844
|
return data
|
|
@@ -629,25 +846,31 @@ def run(args):
|
|
|
629
846
|
return autoupdate_command(args, home, store, interactive)
|
|
630
847
|
if args.command in ("status", "doctor"):
|
|
631
848
|
targets = scope(args, home, read=True)
|
|
632
|
-
rows = store.status(targets)
|
|
849
|
+
rows = unmanaged_projections(store, home, store.status(targets))
|
|
633
850
|
pending = [t["id"] for t in store.pending()]
|
|
634
851
|
global_view = not (getattr(args, "project", None) or getattr(args, "dest", None))
|
|
635
852
|
names = [n.strip() for n in getattr(args, "agents", "").split(",")] if getattr(args, "agents", None) else list(guide.MECHANISMS)
|
|
636
853
|
guides = guide.status(store, home, names) if global_view else []
|
|
637
854
|
try:
|
|
638
|
-
|
|
855
|
+
# Only doctor asks the CLIs (codex app-server writes under ~/.codex); status stays read-only.
|
|
856
|
+
ui = integrations.status(store, home, probe=args.command == "doctor") if global_view else []
|
|
639
857
|
except CrowError as exc:
|
|
640
858
|
ui = [{"integration": "*", "status": "unsafe", "note": str(exc)}]
|
|
641
859
|
result = {"installations": rows, "guides": guides, "integrations": ui, "pending_transactions": pending,
|
|
642
860
|
"mode": hook.read_mode(home), "activation_verified": False,
|
|
643
861
|
"reload_hint": "Peça ao host para recarregar/listar skills; reinicie a sessão se necessário."}
|
|
862
|
+
result["groups"] = group_coverage(rows)
|
|
644
863
|
try:
|
|
645
864
|
result["autoupdate"] = autoupdate.status(store, home) if global_view else None
|
|
646
865
|
except CrowError as exc:
|
|
647
866
|
result["autoupdate"] = {"enabled": None, "healthy": False, "error": exc.code}
|
|
867
|
+
hooks = [r for r in ui_rows(ui) if r.get("status") == "installed" and isinstance(r.get("enabled"), dict)]
|
|
868
|
+
result["hooks"] = [{"integration": r["integration"], "agents": r.get("agents"), "state": r["enabled"].get("state"),
|
|
869
|
+
"fix": r["enabled"].get("fix")} for r in hooks]
|
|
648
870
|
if args.command == "doctor":
|
|
649
871
|
result.update(healthy=not pending and all(r["status"] not in ("modified", "missing", "unsafe", "external_link") for r in rows + guides)
|
|
650
|
-
and (result["autoupdate"] or {}).get("healthy", True) is not False
|
|
872
|
+
and (result["autoupdate"] or {}).get("healthy", True) is not False
|
|
873
|
+
and all(h["state"] not in machine.BAD_HOOKS for h in result["hooks"]),
|
|
651
874
|
agents=inventory(home))
|
|
652
875
|
return result
|
|
653
876
|
if args.command in ("rollback", "recover"):
|
|
@@ -694,18 +917,20 @@ def run(args):
|
|
|
694
917
|
return {"revision": source.revision, "skill": source.catalog[args.name],
|
|
695
918
|
"relationships": relationship_view(source, args.name, store, targets)}
|
|
696
919
|
targets = scope(args, home, interactive)
|
|
697
|
-
names = args.skills
|
|
698
|
-
|
|
699
|
-
|
|
920
|
+
names = list(args.skills)
|
|
921
|
+
group = getattr(args, "group", None)
|
|
922
|
+
if (names or group) and args.all:
|
|
923
|
+
raise CrowError("usage", "Use nomes/--group ou --all, não ambos.")
|
|
924
|
+
selection = None
|
|
925
|
+
if group is not None:
|
|
926
|
+
selection = groups_mod.parse(group)
|
|
927
|
+
names = list(dict.fromkeys(names + groups_mod.resolve(source, group)))
|
|
700
928
|
if args.command == "install" and not names and not args.all:
|
|
701
929
|
if not interactive:
|
|
702
|
-
raise CrowError("usage", "Escolha nomes
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
names = [n.strip() for n in names if n.strip()]
|
|
707
|
-
if not names:
|
|
708
|
-
raise CrowError("usage", "Nenhuma skill selecionada.")
|
|
930
|
+
raise CrowError("usage", "Escolha as skills: nomes, --group ou --all (ex.: crow-skills install --group essencial "
|
|
931
|
+
"--agents claude --global --yes; grupos: crow-skills groups).")
|
|
932
|
+
names, selection = pick(source)
|
|
933
|
+
selection = groups_mod.parse(selection) if selection else None
|
|
709
934
|
if args.command == "uninstall" and not names and not args.all:
|
|
710
935
|
raise CrowError("usage", "Uninstall exige nomes explícitos ou --all.")
|
|
711
936
|
if args.command == "install" and args.all:
|
|
@@ -722,6 +947,8 @@ def run(args):
|
|
|
722
947
|
ui = integrations_preview(args, home, store, plan)
|
|
723
948
|
summary = {"changes": plan["changes"], "revision": source.revision if source else None,
|
|
724
949
|
"relationships": {n: source.related(n) for n in names} if source else {},
|
|
950
|
+
**({"groups": selection, "suggested_handoffs": groups_mod.suggested_handoffs(source, names)}
|
|
951
|
+
if selection and not getattr(args, "with_handoffs", False) else {}),
|
|
725
952
|
"guides": guides, "integrations": ui, "handoffs_are_optional": True, "activation_verified": False}
|
|
726
953
|
notice = uniflow_notice(home, store, targets, plan) if args.command != "uninstall" else None
|
|
727
954
|
if notice is not None:
|
|
@@ -734,7 +961,7 @@ def run(args):
|
|
|
734
961
|
if not ui.get("disabled"):
|
|
735
962
|
# Best effort after the skills/guide transactions; a refusal is reported, never undoes them.
|
|
736
963
|
try:
|
|
737
|
-
summary["integrations"] = integrations.sync(store, home, integration_agents(args, home, store))
|
|
964
|
+
summary["integrations"] = integrations.sync(store, home, integration_agents(args, home, store), trust=trusting(args))
|
|
738
965
|
except (CrowError, OSError) as exc:
|
|
739
966
|
summary["integrations"] = {"changes": [], "skipped": [{"integration": "*", "reason": "Integrações não aplicadas (" +
|
|
740
967
|
(str(exc) or "falha de gravação") + "); skills e guia mantidos."}]}
|
|
@@ -744,10 +971,84 @@ def run(args):
|
|
|
744
971
|
"Operação de arquivos concluída; carregamento/ativação devem ser conferidos no host.")}
|
|
745
972
|
|
|
746
973
|
|
|
747
|
-
def
|
|
748
|
-
|
|
749
|
-
|
|
974
|
+
def ui_rows(rows):
|
|
975
|
+
return [r for r in rows if isinstance(r, dict)]
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
def public_data(data):
|
|
979
|
+
return {k: v for k, v in data.items() if not k.startswith("_")} if isinstance(data, dict) else data
|
|
980
|
+
|
|
981
|
+
|
|
982
|
+
def render_status(data, doctor=False, stream=None):
|
|
983
|
+
stream = stream or sys.stdout
|
|
984
|
+
mode = (data.get("mode") or {}).get("mode", "?")
|
|
985
|
+
auto = data.get("autoupdate") or {}
|
|
986
|
+
lines = [ui.paint(hook.indicator(mode, ui.unicode_on(stream)), "bold", stream) + " · autoupdate " +
|
|
987
|
+
("ligado" if auto.get("enabled") else "desligado") + " · crow-skills " + __version__, ""]
|
|
988
|
+
by_target = {}
|
|
989
|
+
for row in data["installations"]:
|
|
990
|
+
item = by_target.setdefault(row["target"], {"agents": row["agents"], "ok": 0, "via": 0, "bad": [], "kept": []})
|
|
991
|
+
if row.get("projection") == "uniflow_link_unmanaged":
|
|
992
|
+
item["kept"].append(row["name"] + " link do Uniflow não gerenciado (crow-skills setup --agents uniflow --replace --yes)")
|
|
993
|
+
if row["status"] == "uniflow_link":
|
|
994
|
+
item["ok"] += 1
|
|
995
|
+
item["via"] += 1 # projected by Uniflow from its store: installed for this CLI
|
|
996
|
+
elif row["status"] in ("installed", "disabled"):
|
|
997
|
+
item["ok"] += 1
|
|
998
|
+
elif row["status"] in ("modified", "missing", "unsafe", "external_link"):
|
|
999
|
+
item["bad"].append((row["name"] or "raiz") + " " + row["status"])
|
|
1000
|
+
rows = [[ui.mark("fail" if v["bad"] else ("warn" if v["kept"] else "ok"), stream), ",".join(v["agents"]),
|
|
1001
|
+
str(v["ok"]) + " skills" + (" (" + str(v["via"]) + " via uniflow)" if v["via"] else ""),
|
|
1002
|
+
"; ".join(v["bad"] + v["kept"])] for v in by_target.values() if v["ok"] or v["bad"] or v["kept"]]
|
|
1003
|
+
if rows:
|
|
1004
|
+
lines += ui.table(rows, ["", "agente", "instaladas", "problemas"], stream)
|
|
1005
|
+
cover = [[",".join(c["agents"]), ", ".join(g["group"] + (" ✓" if g["state"] == "complete" and ui.unicode_on(stream) else
|
|
1006
|
+
" completo" if g["state"] == "complete" else
|
|
1007
|
+
" " + str(g["installed"]) + "/" + str(g["total"]))
|
|
1008
|
+
for g in c["groups"])] for c in data.get("groups") or [] if c["groups"]]
|
|
1009
|
+
if cover:
|
|
1010
|
+
lines += ["", " Grupos (completos ✓ ou parciais n/total):" if ui.unicode_on(stream) else " Grupos:"]
|
|
1011
|
+
lines += ui.table(cover, stream=stream, indent=" ")
|
|
1012
|
+
else:
|
|
1013
|
+
lines.append(" Nenhuma skill instalada; comece com: crow-skills setup --yes")
|
|
1014
|
+
guides = [g for g in data.get("guides", []) if g.get("status") != "absent"]
|
|
1015
|
+
if guides:
|
|
1016
|
+
lines.append("")
|
|
1017
|
+
lines += ui.table([[ui.mark("ok" if g["status"] == "installed" else ("warn" if g["status"] == "outdated" else "fail"), stream),
|
|
1018
|
+
"guia " + g["agent"], ui.word(g["status"]), ui.clean(g.get("note") or "")] for g in guides], stream=stream)
|
|
1019
|
+
hooks = data.get("hooks") or []
|
|
1020
|
+
if hooks:
|
|
1021
|
+
lines.append("")
|
|
1022
|
+
lines += ui.table([[ui.mark("ok" if h["state"] in ("trusted", "not_required") else
|
|
1023
|
+
("fail" if h["state"] in machine.BAD_HOOKS else "warn"), stream),
|
|
1024
|
+
h["integration"], ui.word(h["state"]),
|
|
1025
|
+
ui.clean(h.get("fix") or "")] for h in hooks], stream=stream)
|
|
1026
|
+
if data.get("pending_transactions"):
|
|
1027
|
+
lines += ["", ui.mark("fail", stream) + " Transação pendente: crow-skills recover --yes"]
|
|
1028
|
+
if doctor:
|
|
1029
|
+
lines += ["", (ui.mark("ok", stream) + " healthy") if data.get("healthy") else
|
|
1030
|
+
(ui.mark("fail", stream) + " não saudável; detalhes: crow-skills doctor --json")]
|
|
1031
|
+
return "\n".join(lines)
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
def render_help(data, stream=None):
|
|
1035
|
+
if data.get("topic") is None:
|
|
1036
|
+
return ui.top_help(__version__, stream, data.get("_status_line"))
|
|
1037
|
+
return data["text"].rstrip("\n")
|
|
1038
|
+
|
|
1039
|
+
|
|
1040
|
+
def emit(command, data, as_json):
|
|
1041
|
+
envelope = {"schema_version": 1, "ok": True, "command": command, "data": public_data(data)}
|
|
1042
|
+
if as_json:
|
|
750
1043
|
print(json.dumps(envelope, ensure_ascii=False, separators=(",", ":")))
|
|
1044
|
+
elif command == "help":
|
|
1045
|
+
print(render_help(data))
|
|
1046
|
+
elif command == "setup":
|
|
1047
|
+
print(machine.render_setup(data))
|
|
1048
|
+
elif command == "upgrade" or (command == "autoupdate" and isinstance(data, dict) and "summary" in data):
|
|
1049
|
+
print(machine.render_upgrade(data))
|
|
1050
|
+
elif command in ("status", "doctor"):
|
|
1051
|
+
print(render_status(data, doctor=command == "doctor"))
|
|
751
1052
|
elif command == "progress":
|
|
752
1053
|
if data is not None:
|
|
753
1054
|
print(data["text"] or "crow: nenhuma execução registrada neste diretório.")
|
|
@@ -767,6 +1068,13 @@ def emit(command, data, machine):
|
|
|
767
1068
|
elif command == "events":
|
|
768
1069
|
if data is not None:
|
|
769
1070
|
print(data["text"] if "text" in data else "\n".join(json.dumps(e, ensure_ascii=False) for e in data["events"]))
|
|
1071
|
+
elif command == "groups":
|
|
1072
|
+
lines = []
|
|
1073
|
+
for g in data["groups"]:
|
|
1074
|
+
lines.append(ui.paint(g["name"], "cyan") + " (" + str(len(g["skills"])) + ") — " + terminal(g["description"]))
|
|
1075
|
+
lines.append(" " + ", ".join(g["skills"]))
|
|
1076
|
+
lines += ["", "crow-behaviour entra com qualquer grupo. Ex.: crow-skills install --group essencial --agents claude --global --yes"]
|
|
1077
|
+
print("\n".join(lines))
|
|
770
1078
|
elif command == "list":
|
|
771
1079
|
print("Fonte: " + data["revision"])
|
|
772
1080
|
for item in data["skills"]:
|
|
@@ -777,31 +1085,41 @@ def emit(command, data, machine):
|
|
|
777
1085
|
|
|
778
1086
|
def main(argv=None):
|
|
779
1087
|
argv = list(sys.argv[1:] if argv is None else argv)
|
|
780
|
-
|
|
1088
|
+
as_json = "--json" in argv
|
|
781
1089
|
command = None
|
|
782
1090
|
try:
|
|
783
1091
|
args = parser().parse_args(argv)
|
|
784
1092
|
command = args.command or ("version" if args.version else None)
|
|
1093
|
+
if command == "init":
|
|
1094
|
+
command = "setup"
|
|
1095
|
+
if command is None or getattr(args, "show_help", False):
|
|
1096
|
+
command = "help"
|
|
785
1097
|
result = run(args)
|
|
786
|
-
emit(command, result,
|
|
1098
|
+
emit(command, result, as_json)
|
|
1099
|
+
if command in ("setup", "upgrade") and isinstance(result, dict) and result.get("healthy") is False:
|
|
1100
|
+
return 1
|
|
787
1101
|
return 0
|
|
1102
|
+
except SystemExit as exc: # <command> --help printed by argparse
|
|
1103
|
+
return exc.code if isinstance(exc.code, int) else 0
|
|
788
1104
|
except CrowError as exc:
|
|
789
|
-
if
|
|
1105
|
+
if as_json:
|
|
790
1106
|
print(json.dumps({"schema_version": 1, "ok": False, "command": command,
|
|
791
1107
|
"error": {"code": exc.code, "message": str(exc)}}, ensure_ascii=False))
|
|
792
1108
|
else:
|
|
793
|
-
print(
|
|
1109
|
+
print(ui.error_text(exc.code, str(exc), command, sys.stderr), file=sys.stderr)
|
|
794
1110
|
return 2 if exc.code == "usage" else 1
|
|
795
1111
|
except (OSError, ValueError, KeyError, TypeError) as exc:
|
|
796
1112
|
# No traceback or subprocess stderr in machine output; credentials stay external.
|
|
797
|
-
if
|
|
1113
|
+
if as_json:
|
|
798
1114
|
print(json.dumps({"schema_version": 1, "ok": False, "command": command,
|
|
799
1115
|
"error": {"code": "io_error", "message": "Falha de leitura/gravação ou estado inválido; nenhuma conclusão de sucesso."}}))
|
|
800
1116
|
else:
|
|
801
|
-
print("Falha de leitura/gravação ou estado inválido.
|
|
1117
|
+
print(ui.error_text("io_error", "Falha de leitura/gravação ou estado inválido.", command, sys.stderr) +
|
|
1118
|
+
"\n Para corrigir: confira permissões e transações pendentes (crow-skills doctor; crow-skills recover --yes)",
|
|
1119
|
+
file=sys.stderr)
|
|
802
1120
|
return 1
|
|
803
1121
|
except KeyboardInterrupt:
|
|
804
|
-
if
|
|
1122
|
+
if as_json:
|
|
805
1123
|
print(json.dumps({"schema_version": 1, "ok": False, "command": command,
|
|
806
1124
|
"error": {"code": "interrupted", "message": "Operação interrompida; confira status/recover."}}))
|
|
807
1125
|
return 130
|