@andespindola/crow-skills 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/crow_skills/__init__.py +1 -1
- package/crow_skills/agents.py +138 -4
- package/crow_skills/autoupdate.py +17 -4
- package/crow_skills/broadcast.py +138 -20
- package/crow_skills/cli.py +79 -24
- package/crow_skills/guide.py +10 -3
- package/crow_skills/hook.py +32 -14
- package/crow_skills/machine.py +143 -11
- package/crow_skills/projection.py +69 -27
- package/crow_skills/resources.py +5 -3
- package/crow_skills/storage.py +96 -11
- package/crow_skills/ui.py +1 -1
- package/docs/cli.md +35 -11
- package/package.json +1 -1
- package/release.json +16 -16
|
@@ -1,37 +1,31 @@
|
|
|
1
1
|
"""Uniflow's projection of its store into Codex's own skills dir ($CODEX_HOME/skills, default ~/.codex/skills).
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Since 0.15.0 crow-skills installs for codex in that same directory, like it does for every other CLI, so a
|
|
4
|
+
managed copy and a projected link can no longer carry the same name in two places. Codex still scans
|
|
5
|
+
~/.agents/skills as well, so a copy left there by an older version is still listed twice: those legacy copies
|
|
6
|
+
are what duplicates() reports, and removing them is the migration away from the shared folder.
|
|
6
7
|
|
|
7
8
|
A link counts as the codex install of a skill ("via uniflow") only when it resolves to <store>/<name> and
|
|
8
9
|
that store entry is crow-skills-managed (guide.store_managed); a store entry Uniflow disabled is reported as
|
|
9
10
|
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:
|
|
11
|
+
alias Codex deduplicates by canonical path: never altered nor removed. Note that the LINK being untouched
|
|
12
|
+
does not make the command succeed — that name now lives in the codex destination, and a symlinked entry
|
|
13
|
+
there cannot be managed, so install/setup report it (status "unsafe") and exit 1 until the user resolves it.
|
|
11
14
|
"""
|
|
12
15
|
import os
|
|
13
16
|
from pathlib import Path
|
|
14
17
|
|
|
15
|
-
from .agents import destinations, real_home, uniflow_store
|
|
18
|
+
from .agents import codex_home, codex_skills_dir, destinations, legacy_shared_dir, real_home, uniflow_store
|
|
16
19
|
from .source import CrowError
|
|
17
20
|
|
|
18
21
|
|
|
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
22
|
def links(home, store):
|
|
32
23
|
"""{name: "managed" | "disabled" | "modified" | "unmanaged" | "alias" | "other"} for crow-* symlinks in Codex's own
|
|
33
24
|
skills dir: to an intact managed / Uniflow-disabled / edited-or-incomplete managed / hand-written store entry, to our
|
|
34
|
-
|
|
25
|
+
legacy shared copy, or elsewhere. Only "managed" counts as the codex install and allows removing a legacy
|
|
26
|
+
duplicate. "alias" is a link into the legacy ~/.agents/skills: Codex deduplicates it by canonical path, and
|
|
27
|
+
crow-skills never alters it — but it occupies the name in the destination, so that name is reported
|
|
28
|
+
unmanageable rather than installed."""
|
|
35
29
|
directory = codex_skills_dir(home)
|
|
36
30
|
try:
|
|
37
31
|
if directory.is_symlink() or not directory.is_dir():
|
|
@@ -42,14 +36,16 @@ def links(home, store):
|
|
|
42
36
|
if not entries:
|
|
43
37
|
return {}
|
|
44
38
|
store_dir = uniflow_store(home)
|
|
45
|
-
|
|
39
|
+
# The alias case is a link into the LEGACY shared folder, never into Codex's own dir (where the link
|
|
40
|
+
# itself lives): comparing against the destination would now test a link against itself.
|
|
41
|
+
legacy = legacy_shared_dir(home)
|
|
46
42
|
from .guide import store_statuses
|
|
47
43
|
statuses = store_statuses(home, store) # only an intact ("installed") store entry is a working codex install
|
|
48
44
|
result = {}
|
|
49
45
|
for link in entries:
|
|
50
46
|
name = link.name
|
|
51
47
|
resolved = os.path.realpath(link)
|
|
52
|
-
if resolved == os.path.realpath(
|
|
48
|
+
if resolved == os.path.realpath(legacy / name):
|
|
53
49
|
result[name] = "alias"
|
|
54
50
|
elif (resolved == os.path.realpath(store_dir / name) and (store_dir / name).is_dir()
|
|
55
51
|
and not (store_dir / name).is_symlink()):
|
|
@@ -62,7 +58,13 @@ def links(home, store):
|
|
|
62
58
|
|
|
63
59
|
|
|
64
60
|
def status_rows(home, store, rows):
|
|
65
|
-
"""Rows for `status`/`doctor`: Codex's view through Uniflow's links,
|
|
61
|
+
"""Rows for `status`/`doctor`: Codex's view through Uniflow's links, classified.
|
|
62
|
+
|
|
63
|
+
Codex's own dir IS the codex destination since 0.15.0, so `store.status()` already lists these
|
|
64
|
+
links (as a bare `uniflow_link`). These rows ENRICH the existing one for each name — managed,
|
|
65
|
+
disabled, edited or unmanaged, each with its reason — and are only appended when the base scan
|
|
66
|
+
did not report the name. Appending unconditionally would count every projected skill twice.
|
|
67
|
+
"""
|
|
66
68
|
directory = str(codex_skills_dir(home))
|
|
67
69
|
extra = []
|
|
68
70
|
for name, kind in sorted(links(home, store).items()):
|
|
@@ -84,7 +86,22 @@ def status_rows(home, store, rows):
|
|
|
84
86
|
"activation_verified": False, "projection": "uniflow_link_unmanaged",
|
|
85
87
|
"note": "Link do Uniflow para um pacote do store que o crow-skills não gerencia; não conta como instalada. "
|
|
86
88
|
"Revise o store e, para gerenciá-lo com backup: crow-skills setup --agents uniflow --replace --yes"})
|
|
87
|
-
return extra
|
|
89
|
+
return merge_rows(rows, extra, directory)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def merge_rows(rows, extra, directory):
|
|
93
|
+
"""Each enriched row replaces the base row of the same name in the same directory; the rest is
|
|
94
|
+
appended. Returns the merged list (the caller keeps its own list object)."""
|
|
95
|
+
merged, used = [], set()
|
|
96
|
+
for row in rows:
|
|
97
|
+
richer = next((e for e in extra if e["name"] == row.get("name") and row.get("target") == directory), None)
|
|
98
|
+
if richer is None:
|
|
99
|
+
merged.append(row)
|
|
100
|
+
continue
|
|
101
|
+
used.add(richer["name"])
|
|
102
|
+
merged.append(dict(row, **{k: v for k, v in richer.items() if k != "revision" or row.get("revision") is None}))
|
|
103
|
+
merged += [e for e in extra if e["name"] not in used]
|
|
104
|
+
return merged
|
|
88
105
|
|
|
89
106
|
|
|
90
107
|
def projected(agent, home, store):
|
|
@@ -99,17 +116,42 @@ def native_target(agent, target, home):
|
|
|
99
116
|
return agent == "codex" and os.path.abspath(target["path"]) == os.path.abspath(destinations("codex", home)[0]["path"])
|
|
100
117
|
|
|
101
118
|
|
|
119
|
+
def legacy_leftovers(home, store):
|
|
120
|
+
"""Names crow-skills still has installed in the legacy shared folder, INDEPENDENT of Uniflow.
|
|
121
|
+
|
|
122
|
+
duplicates() answers "what duplicates a projection", so on a machine without Uniflow it is
|
|
123
|
+
always empty — and an install made by 0.14.0 (which put codex's copies in that folder) would be
|
|
124
|
+
invisible to status/doctor while Codex kept loading it. This is the signal for that."""
|
|
125
|
+
legacy = legacy_shared_dir(home)
|
|
126
|
+
if not legacy.is_dir() or legacy.is_symlink():
|
|
127
|
+
return []
|
|
128
|
+
target = {"path": str(legacy), "agents": ["codex"]}
|
|
129
|
+
try:
|
|
130
|
+
records = store.state(target["path"])["installed"]
|
|
131
|
+
if not records:
|
|
132
|
+
return []
|
|
133
|
+
rows = {r["name"]: r for r in store.status([target], sorted(records)) if r["name"]}
|
|
134
|
+
except (CrowError, OSError):
|
|
135
|
+
return []
|
|
136
|
+
return sorted(n for n, r in rows.items() if r["status"] in ("installed", "modified", "missing"))
|
|
137
|
+
|
|
138
|
+
|
|
102
139
|
def duplicates(home, store):
|
|
103
|
-
"""
|
|
140
|
+
"""Copies left in the legacy shared folder (~/.agents/skills) that Codex lists a second time.
|
|
104
141
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
142
|
+
Codex scans its own dir and ~/.agents/skills; until 0.15.0 crow-skills installed for codex in the
|
|
143
|
+
latter. A copy still there duplicates what Uniflow projects into Codex's own dir, so it is the thing
|
|
144
|
+
to migrate away. Returns {"remove": [...], "modified": [...], "shared": [...]}: "remove" are
|
|
145
|
+
crow-skills-managed and intact; "modified" were edited locally (kept unless --overwrite-modified);
|
|
146
|
+
"shared" are also recorded for another agent of that dir, which may rely on them."""
|
|
108
147
|
names = projected("codex", home, store)
|
|
109
148
|
result = {"remove": [], "modified": [], "shared": []}
|
|
110
149
|
if not names:
|
|
111
150
|
return result
|
|
112
|
-
|
|
151
|
+
legacy = legacy_shared_dir(home)
|
|
152
|
+
if not legacy.is_dir() or legacy.is_symlink():
|
|
153
|
+
return result
|
|
154
|
+
target = {"path": str(legacy), "agents": ["codex"]}
|
|
113
155
|
try:
|
|
114
156
|
rows = {r["name"]: r for r in store.status([target], sorted(names)) if r["name"]}
|
|
115
157
|
records = store.state(target["path"])["installed"]
|
package/crow_skills/resources.py
CHANGED
|
@@ -785,9 +785,10 @@ def fixture_reading():
|
|
|
785
785
|
return data
|
|
786
786
|
|
|
787
787
|
|
|
788
|
-
def probe(home, processes=True, use_cache=False, platform=None):
|
|
788
|
+
def probe(home, processes=True, use_cache=False, platform=None, write_through=True):
|
|
789
789
|
"""The crow-resources/1 document. use_cache: reuse a reading younger than cacheSeconds
|
|
790
|
-
(hooks, status lines, snapshot); processes: also scan agent CLI process trees
|
|
790
|
+
(hooks, status lines, snapshot); processes: also scan agent CLI process trees;
|
|
791
|
+
write_through: refresh the cache file on a miss (false for a side-effect-free probe)."""
|
|
791
792
|
cfg, meta = load_config(home)
|
|
792
793
|
reading, age, cached = None, 0, False
|
|
793
794
|
data = fixture_reading()
|
|
@@ -799,7 +800,8 @@ def probe(home, processes=True, use_cache=False, platform=None):
|
|
|
799
800
|
cached = reading is not None
|
|
800
801
|
if reading is None:
|
|
801
802
|
reading = read_machine(platform)
|
|
802
|
-
|
|
803
|
+
if write_through:
|
|
804
|
+
write_cache(home, reading)
|
|
803
805
|
agents = read_processes(platform) if processes else None
|
|
804
806
|
return build(reading, cfg, meta, agents, cached, age)
|
|
805
807
|
|
package/crow_skills/storage.py
CHANGED
|
@@ -19,6 +19,18 @@ from .agents import uniflow_store
|
|
|
19
19
|
from .source import CrowError, MAX_FILE, MAX_PACKAGE, MAX_FILES, SHA, digest, file_entry, safe_relative, same_source, source_identity, valid_name
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
def resolved(path):
|
|
23
|
+
"""checked() sobre o caminho FÍSICO: usado onde o alvo vem GRAVADO numa transação.
|
|
24
|
+
|
|
25
|
+
Um alvo registrado antes de o diretório virar dotfiles continua apontando para o caminho antigo.
|
|
26
|
+
Exigir ali um caminho livre de symlink quebrava rollback e recover e chegava a TRAVAR o perfil
|
|
27
|
+
(recover recusava, rollback dizia fase incompatível, e todo install passava a exigir recovery).
|
|
28
|
+
Resolver primeiro mantém a garantia: o que é escrito continua passando por checked().
|
|
29
|
+
"""
|
|
30
|
+
from .agents import physical
|
|
31
|
+
return checked(physical(path))
|
|
32
|
+
|
|
33
|
+
|
|
22
34
|
def checked(path):
|
|
23
35
|
path = Path(os.path.abspath(Path(path).expanduser()))
|
|
24
36
|
for part in reversed((path, *path.parents)):
|
|
@@ -132,6 +144,22 @@ def validate_entries(entries):
|
|
|
132
144
|
raise CrowError("invalid_state", "Hash/tipo de arquivo inválido no estado.")
|
|
133
145
|
|
|
134
146
|
|
|
147
|
+
def retargeted(state, target):
|
|
148
|
+
"""O manifesto gravado precisa declarar o caminho de HOJE (resolvido), não o de quando a
|
|
149
|
+
transação rodou: `validate_state` exige essa igualdade, e sem isto um rollback restaurava um
|
|
150
|
+
estado que o próprio leitor recusava depois com invalid_state."""
|
|
151
|
+
if not isinstance(state, dict):
|
|
152
|
+
return state
|
|
153
|
+
return dict(state, target=str(resolved(target)))
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def same_manifest(a, b):
|
|
157
|
+
"""Dois manifestos descrevem o mesmo conteúdo, ignorando a string do caminho do alvo."""
|
|
158
|
+
if a is None or b is None:
|
|
159
|
+
return a == b
|
|
160
|
+
return {k: v for k, v in a.items() if k != "target"} == {k: v for k, v in b.items() if k != "target"}
|
|
161
|
+
|
|
162
|
+
|
|
135
163
|
def validate_state(data, target):
|
|
136
164
|
try:
|
|
137
165
|
if (data["schema_version"] != 1 or data["target"] != str(target) or
|
|
@@ -153,12 +181,50 @@ class Store:
|
|
|
153
181
|
self.root = self.home / ".local/state/crow-skills"
|
|
154
182
|
|
|
155
183
|
def state_path(self, target):
|
|
156
|
-
key = hashlib.sha256(str(
|
|
184
|
+
key = hashlib.sha256(str(resolved(target)).encode()).hexdigest()
|
|
157
185
|
return self.root / "targets" / key / "state.json"
|
|
158
186
|
|
|
187
|
+
def aliased_state_path(self, target):
|
|
188
|
+
"""Estado gravado sob um APELIDO do mesmo destino (antes da resolução de symlink, 0.16.0).
|
|
189
|
+
|
|
190
|
+
A chave é o sha do caminho; um estado escrito quando ~/.claude ainda era diretório comum
|
|
191
|
+
está sob outra chave e ficaria invisível depois que o usuário move tudo para dotfiles —
|
|
192
|
+
`uninstall` responderia not_managed e a cópia instalada viraria órfã. Varre os destinos
|
|
193
|
+
conhecidos (poucos) e devolve o que resolve para o MESMO caminho físico."""
|
|
194
|
+
from .agents import physical
|
|
195
|
+
directory = self.root / "targets"
|
|
196
|
+
try:
|
|
197
|
+
if directory.is_symlink() or not directory.is_dir():
|
|
198
|
+
return None
|
|
199
|
+
entries = list(os.scandir(directory))
|
|
200
|
+
except OSError:
|
|
201
|
+
return None
|
|
202
|
+
wanted = physical(target)
|
|
203
|
+
found = []
|
|
204
|
+
for entry in entries:
|
|
205
|
+
candidate = Path(entry.path) / "state.json"
|
|
206
|
+
try:
|
|
207
|
+
data = read_json(candidate)
|
|
208
|
+
except (CrowError, OSError, ValueError):
|
|
209
|
+
continue
|
|
210
|
+
recorded = data.get("target") if isinstance(data, dict) else None
|
|
211
|
+
if isinstance(recorded, str) and recorded != str(target) and physical(recorded) == wanted:
|
|
212
|
+
found.append(candidate)
|
|
213
|
+
# Mais de um apelido para o MESMO caminho físico é ambíguo: escolher o primeiro do scandir
|
|
214
|
+
# (ordem arbitrária de hash) podia reportar uma skill gerenciada como `unmanaged` e, com
|
|
215
|
+
# --replace, sobrescrevê-la como se fosse lixo alheio — exatamente o dano que o apelido
|
|
216
|
+
# existe para evitar. Sem resposta única, nenhuma resposta.
|
|
217
|
+
return found[0] if len(found) == 1 else None
|
|
218
|
+
|
|
159
219
|
def state(self, target):
|
|
160
|
-
target =
|
|
220
|
+
target = resolved(target)
|
|
161
221
|
path = checked(self.state_path(target))
|
|
222
|
+
if not path.exists():
|
|
223
|
+
alias = self.aliased_state_path(target)
|
|
224
|
+
if alias is not None:
|
|
225
|
+
data = read_json(alias)
|
|
226
|
+
# Reescreve o alvo para o caminho físico; a próxima mutação grava sob a chave nova.
|
|
227
|
+
return validate_state(dict(data, target=str(target)), target)
|
|
162
228
|
if not path.exists():
|
|
163
229
|
return {"schema_version": 1, "target": str(target), "installed": {}}
|
|
164
230
|
return validate_state(read_json(path), target)
|
|
@@ -177,11 +243,17 @@ class Store:
|
|
|
177
243
|
try:
|
|
178
244
|
if data["id"] != txid or data["phase"] not in ("prepared", "restoring", "committed", "rolled_back"):
|
|
179
245
|
raise ValueError("phase")
|
|
246
|
+
if "linked" in data and not re.fullmatch(r"[a-f0-9]{32}", data["linked"] or ""):
|
|
247
|
+
raise ValueError("linked")
|
|
180
248
|
targets = data["before"]
|
|
181
249
|
if not isinstance(targets, dict) or set(targets) != set(data["after"]):
|
|
182
250
|
raise ValueError("targets")
|
|
183
251
|
for target, before in targets.items():
|
|
184
|
-
|
|
252
|
+
# Absoluto e normalizado, mas NÃO exigimos que continue livre de symlink: o destino
|
|
253
|
+
# pode ter virado dotfiles depois desta transação, e toda ESCRITA ainda passa por
|
|
254
|
+
# checked(). Exigir aqui derrubava status, doctor, install, uninstall e recover de
|
|
255
|
+
# TODOS os agentes por causa de um journal antigo.
|
|
256
|
+
if target != os.path.abspath(target):
|
|
185
257
|
raise ValueError("path")
|
|
186
258
|
validate_state(before, target)
|
|
187
259
|
validate_state(data["after"][target], target)
|
|
@@ -221,7 +293,7 @@ class Store:
|
|
|
221
293
|
|
|
222
294
|
def work(self, target, txid):
|
|
223
295
|
from .guide import shielded
|
|
224
|
-
target =
|
|
296
|
+
target = resolved(target)
|
|
225
297
|
key = hashlib.sha256(str(target).encode()).hexdigest()[:20]
|
|
226
298
|
# Beside the discovery dir, unless that parent is protected (the Uniflow
|
|
227
299
|
# config): then staging/backups stay in the private state root instead.
|
|
@@ -232,7 +304,7 @@ class Store:
|
|
|
232
304
|
"""Backups of a transaction, wherever the version that made it put them: older
|
|
233
305
|
transactions kept them beside the target even where that is now protected."""
|
|
234
306
|
primary = self.work(target, txid)
|
|
235
|
-
target =
|
|
307
|
+
target = resolved(target)
|
|
236
308
|
key = hashlib.sha256(str(target).encode()).hexdigest()[:20]
|
|
237
309
|
for candidate in (primary, target.parent / ".crow-skills" / key / txid, self.root / "work" / key / txid):
|
|
238
310
|
if os.path.lexists(candidate):
|
|
@@ -344,7 +416,11 @@ class Store:
|
|
|
344
416
|
}
|
|
345
417
|
return {"before": before, "after": after, "items": items, "packages": packages, "changes": output}
|
|
346
418
|
|
|
347
|
-
def apply(self, plan):
|
|
419
|
+
def apply(self, plan, linked=None):
|
|
420
|
+
"""linked: a committed transaction this one completes, so a rollback of that one undoes this
|
|
421
|
+
one first. Used by the migration out of the legacy shared folder, which runs as its own
|
|
422
|
+
uninstall right after an install: without the link, rolling back the install alone would
|
|
423
|
+
remove the new copies while the legacy ones stayed removed, leaving the user with none."""
|
|
348
424
|
if not plan["items"]:
|
|
349
425
|
return None
|
|
350
426
|
with self.lock():
|
|
@@ -359,6 +435,8 @@ class Store:
|
|
|
359
435
|
txid = uuid.uuid4().hex
|
|
360
436
|
data = {k: plan[k] for k in ("before", "after", "items")}
|
|
361
437
|
data.update(id=txid, phase="prepared", created_at=datetime.now(timezone.utc).isoformat())
|
|
438
|
+
if linked:
|
|
439
|
+
data["linked"] = linked
|
|
362
440
|
journal = self.root / "transactions" / (txid + ".json")
|
|
363
441
|
try:
|
|
364
442
|
for target in plan["before"]:
|
|
@@ -389,7 +467,7 @@ class Store:
|
|
|
389
467
|
if item["new"] is not None:
|
|
390
468
|
os.replace(work / "new" / item["name"], dest)
|
|
391
469
|
for target, state in data["after"].items():
|
|
392
|
-
atomic_json(self.state_path(target), state)
|
|
470
|
+
atomic_json(self.state_path(target), retargeted(state, target))
|
|
393
471
|
data["phase"] = "committed"
|
|
394
472
|
atomic_json(journal, data)
|
|
395
473
|
except (Exception, KeyboardInterrupt):
|
|
@@ -415,10 +493,14 @@ class Store:
|
|
|
415
493
|
operations = []
|
|
416
494
|
for target, before in data["before"].items():
|
|
417
495
|
current = self.state(target)
|
|
418
|
-
|
|
496
|
+
# O campo "target" dentro do manifesto guarda o caminho de QUANDO a transação rodou; se
|
|
497
|
+
# o diretório virou dotfiles depois, ele difere do caminho resolvido de hoje e as duas
|
|
498
|
+
# estruturas nunca batem, recusando um rollback perfeitamente válido. A comparação é
|
|
499
|
+
# sobre o CONTEÚDO (as skills instaladas), não sobre a string do caminho.
|
|
500
|
+
if not same_manifest(current, before) and not same_manifest(current, data["after"][target]):
|
|
419
501
|
raise CrowError("recovery_conflict", "Manifesto mudou após a transação; recuperação recusada.")
|
|
420
502
|
for item in reversed(data["items"]):
|
|
421
|
-
dest =
|
|
503
|
+
dest = resolved(Path(item["target"])) / item["name"]
|
|
422
504
|
old = self.existing_work(item["target"], data["id"]) / "old" / item["name"]
|
|
423
505
|
if item.get("link") is not None:
|
|
424
506
|
if same_link(old, item["link"]):
|
|
@@ -454,7 +536,7 @@ class Store:
|
|
|
454
536
|
if backup is not None:
|
|
455
537
|
os.replace(backup, dest)
|
|
456
538
|
for target, state in data["before"].items():
|
|
457
|
-
atomic_json(self.state_path(target), state)
|
|
539
|
+
atomic_json(self.state_path(target), retargeted(state, target))
|
|
458
540
|
data["phase"] = "rolled_back"
|
|
459
541
|
atomic_json(self.root / "transactions" / (data["id"] + ".json"), data)
|
|
460
542
|
|
|
@@ -471,7 +553,10 @@ class Store:
|
|
|
471
553
|
return rollback(self, data, self.home, recovery)
|
|
472
554
|
if not recovery:
|
|
473
555
|
for target, state in data["after"].items():
|
|
474
|
-
|
|
556
|
+
# same_manifest, e não ==: o campo "target" do manifesto guarda o caminho de
|
|
557
|
+
# quando a transação rodou, e um diretório que virou dotfiles depois faria toda
|
|
558
|
+
# comparação falhar, recusando um rollback válido por diferença de string.
|
|
559
|
+
if not same_manifest(self.state(target), state):
|
|
475
560
|
raise CrowError("rollback_conflict", "Há mudanças posteriores; rollback antigo recusado.")
|
|
476
561
|
self._restore(data)
|
|
477
562
|
|
package/crow_skills/ui.py
CHANGED
|
@@ -160,7 +160,7 @@ FIXES = {
|
|
|
160
160
|
"unmanaged_conflict": "revise o conteúdo; para substituir com backup, acrescente --replace",
|
|
161
161
|
"unmanaged_link": "acrescente --replace para trocar só o link por uma cópia gerenciada",
|
|
162
162
|
"guide_requires_skill": "crow-skills setup --yes",
|
|
163
|
-
"unknown_agent": "use --agents com: claude, codex, gemini, devin,
|
|
163
|
+
"unknown_agent": "use --agents com: claude, codex, gemini, devin, uniflow (um diretório fora desses: --dest)",
|
|
164
164
|
"unknown_group": "crow-skills groups (lista os grupos; todas = catálogo inteiro)",
|
|
165
165
|
"source_mismatch": "use install com --source para trocar de origem",
|
|
166
166
|
"unsupported_platform": "agende crow-skills upgrade --yes por conta própria (ex.: cron)",
|
package/docs/cli.md
CHANGED
|
@@ -18,7 +18,7 @@ crow-skills init --yes # alias
|
|
|
18
18
|
| CLI | Binário | Configuração | Agente do instalador |
|
|
19
19
|
|---|---|---|---|
|
|
20
20
|
| Claude Code | `claude` | `~/.claude` | claude |
|
|
21
|
-
| Codex CLI | `codex` | `~/.codex` | codex (`~/.
|
|
21
|
+
| Codex CLI | `codex` | `~/.codex` | codex (`$CODEX_HOME/skills`, padrão `~/.codex/skills`) |
|
|
22
22
|
| Gemini CLI | `gemini` | `~/.gemini/settings.json` | gemini |
|
|
23
23
|
| Antigravity | `agy` | `~/.gemini/antigravity-cli` | gemini (mesmo diretório de skills; hooks e status line próprios) |
|
|
24
24
|
| Devin CLI | `devin` | `~/.config/devin` (ou XDG) | devin |
|
|
@@ -183,14 +183,25 @@ Erros retornam `ok:false` e `error.code/message`. Saídas reais incluem alteraç
|
|
|
183
183
|
|
|
184
184
|
| Adaptador | Usuário | Projeto |
|
|
185
185
|
|---|---|---|
|
|
186
|
-
| codex | `~/.
|
|
186
|
+
| codex | `$CODEX_HOME/skills` (padrão `~/.codex/skills`) | `.codex/skills` |
|
|
187
187
|
| claude | `~/.claude/skills` | `.claude/skills` |
|
|
188
188
|
| gemini | `~/.gemini/skills` | `.gemini/skills` |
|
|
189
189
|
| devin | `~/.config/devin/skills` (ou XDG_CONFIG_HOME) | `.devin/skills` |
|
|
190
|
-
| shared | `~/.agents/skills` | `.agents/skills` |
|
|
191
190
|
| uniflow | store do Uniflow: `~/.config/uniflow/skills` (veja [Uniflow](#uniflow)) | não existe; recusado com `unsupported_scope` |
|
|
192
191
|
|
|
193
|
-
|
|
192
|
+
Cada adaptador grava no diretório PRÓPRIO do seu CLI: o crow-skills atua em cada CLI diretamente e não mantém pasta compartilhada de skills. Até a 0.14.0 o codex era a exceção, instalando em `~/.agents/skills`, o que fazia o crow-skills agir sobre um diretório em vez de sobre um agente: aparecia um agente `shared` que não é CLI nenhum, e o Codex, que varre as duas raízes, listava a mesma skill duas vezes. O adaptador `shared` foi removido e `~/.agents/skills` virou apenas legado: nada é instalado lá, e o que ficou de versões anteriores é apontado como duplicata para você remover (veja [Codex e a projeção do Uniflow](#codex-e-a-projeção-do-uniflow)). Para gravar num diretório fora dos adaptadores, use `--dest` explicitamente.
|
|
193
|
+
|
|
194
|
+
#### Diretório de skills em symlink (dotfiles)
|
|
195
|
+
|
|
196
|
+
Desde a 0.16.0 o destino de cada adaptador é resolvido para o caminho FÍSICO (`agents.physical`, via `os.path.realpath`): `~/.codex -> /dotfiles/codex` instala em `/dotfiles/codex/skills`, e o symlink é preservado. Vale para os cinco CLIs e também para `--dest`. Antes isso era recusado com `unsafe_destination` em todos eles.
|
|
197
|
+
|
|
198
|
+
O controle de segurança não foi enfraquecido, foi deslocado: `storage.checked()` continua recusando escrever ATRAVÉS de um symlink, e agora valida o destino real, o que é mais preciso. As proteções por LOCALIZAÇÃO continuam valendo depois da resolução: um diretório de skills que resolve para dentro de `~/.config/uniflow` é recusado com `protected_path`, sem tocar a config.
|
|
199
|
+
|
|
200
|
+
Duas consequências que vale conhecer:
|
|
201
|
+
|
|
202
|
+
- **O arquivo de prompt do agente não é resolvido.** `CLAUDE.md`, `AGENTS.md` e `rules/crow-skills.md` seguem recusados quando o caminho passa por symlink, porque gravar ali reescreveria um arquivo dentro do seu repositório de dotfiles. Isso não derruba mais o comando: o agente aparece em `guides.skipped` com `code: "unsafe_destination"` e as skills são instaladas. Para ter o guia nesse caso, grave-o você mesmo no caminho físico ou use `--no-guide` para não voltar a tentar.
|
|
203
|
+
- **Apontar o diretório de um CLI exatamente para o store do Uniflow é RECUSADO numa mutação** (`protected_path`, com a dica de usar `--agents uniflow`). Gravar no store vale para todos os CLIs que o Uniflow projeta, não só para o selecionado, e a resolução tornou esse caminho alcançável a partir da seleção de um CLI. A checagem roda só na mutação: `destinations()` é usado por `status`, `doctor` e pelos avisos do uniflow para inspecionar o destino de outro agente, e recusar ali derrubaria leitura.
|
|
204
|
+
- **As integrações (status line, hooks, comando) também passam por `protect()`**, então num diretório de agente em symlink elas são recusadas junto com o guia e aparecem em `integrations.skipped`. Um diretório `.crow-skills` de transações/backup aparece dentro do caminho físico (o repositório de dotfiles) e não é podado depois do commit.
|
|
194
205
|
|
|
195
206
|
`agents` só verifica PATH e caminhos, sem iniciar agente/LLM. `status`/`doctor` conferem arquivos e estado de instalação, não provam carregamento, ativação, independência dos workers ou execução correta da skill. Gemini oferece `/skills reload`; para outros hosts, confira sua lista/reload ou reinicie a sessão conforme a versão. Nenhum system prompt base é alterado; o guia abaixo e os hooks do modo crow, aplicados por padrão em `--global` e desligáveis com `--no-guide` e `--no-integrations`, são aditivos: só informam ao agente o modo escolhido pelo usuário.
|
|
196
207
|
|
|
@@ -214,18 +225,31 @@ O Uniflow guarda as skills num store próprio e cria, ao subir cada CLI, links d
|
|
|
214
225
|
- Só `--global`: o Uniflow não tem store de projeto (`--project` falha com `unsupported_scope`).
|
|
215
226
|
- Sem guia: o Uniflow infere o uso da crow-behaviour por conta própria, então `guide` recusa `uniflow` (`unsupported_guide`) e `install` informa isso em `guides.skipped`. Integrações de interface também não se aplicam.
|
|
216
227
|
- Detecção: `agents` marca `detected` quando `uniflow` está no PATH (`executable_found`) ou o diretório de configuração existe (`config_found`).
|
|
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`).
|
|
228
|
+
- 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`). O codex se comporta igual desde a 0.15.0, por instalar no diretório dele (`effect: skipped`); antes ele instalava na pasta compartilhada e o Codex via a mesma skill duas vezes. `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
229
|
- 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
230
|
|
|
231
|
+
#### Exercitar um hook sem efeito colateral (`--probe`)
|
|
232
|
+
|
|
233
|
+
`crow_hook.py hook --host <host> --event <evento> --probe` calcula exatamente o que o hook entregaria naquele evento — modo crow, aviso de memória e o bloco do crow-broadcast — e **não grava nada**: nenhum evento no barramento, nenhum snapshot de sessão, nenhuma presença, nenhuma escrita de modo. É o que usar para conferir se o hook de um CLI está cabeado e funcionando.
|
|
234
|
+
|
|
235
|
+
Sem isso, validar um hook rodando-o de verdade registra uma sessão com o `session_id` usado no teste. Essa sessão falsa conta como viva para TODOS os outros agentes da máquina pela janela de `stale_minutes` (30 min por padrão): entra na contagem do bloco, na relevância por repositório e nos avisos de "não troque de branch neste clone". Uma validação dos cinco CLIs chega a inventar dez ou mais sessões `working`.
|
|
236
|
+
|
|
220
237
|
#### Codex e a projeção do Uniflow
|
|
221
238
|
|
|
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
|
|
239
|
+
O Codex lê duas raízes de skills do usuário: `$CODEX_HOME/skills` (padrão `~/.codex/skills`, onde o crow-skills instala e o Uniflow liga o store) e a legada `~/.agents/skills`, onde o crow-skills instalava para o codex até a 0.14.0. Ele segue symlinks e lista duas vezes uma skill de mesmo nome quando as duas entradas apontam para diretórios diferentes — hoje isso só acontece com uma cópia deixada na pasta legada. Instalar no diretório do próprio Codex é seguro ao lado do Uniflow: ele não sobrescreve um nome que já existe ali (registra `skipped`) e só apaga um diretório que tenha o marcador `.uniflow-store.json`, que apenas ele escreve. Por isso:
|
|
223
240
|
|
|
224
241
|
- 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
|
|
226
|
-
- `install --agents codex` não cria cópia
|
|
242
|
+
- `setup` e `upgrade` não criam cópia própria para esses nomes e removem, pelo `uninstall` normal contra a pasta legada (`--dest ~/.agents/skills`, com backup, transação e `rollback`), as cópias gerenciadas e intactas que ficaram lá duplicando 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 outro agente daquele diretório fica, porque outro host pode depender dela; o motivo e o comando (`uninstall ... --dest ~/.agents/skills`) aparecem no resumo.
|
|
243
|
+
- `install --agents codex` não cria cópia de um nome projetado (`codex_projection.via_uniflow` no resumo) e nunca remove nada: as cópias legadas aparecem em `codex_projection.duplicates`, com `suggested_command`. `--agents shared` não existe mais e é recusado com `unknown_agent`; para gravar num diretório específico, use `--dest`.
|
|
227
244
|
- 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
|
-
-
|
|
245
|
+
- Migração da pasta compartilhada antiga: em `setup`, `upgrade` e no autoupdate, depois de instalar no diretório do Codex, o crow-skills remove de `~/.agents/skills` as skills que ELE instalou lá e que estão íntegras, pelo `uninstall` normal (`--dest ~/.agents/skills`, com backup, transação e `rollback`); o resumo do agente traz isso em `legacy_shared`. Isso cobre também uma skill que o Uniflow não projeta (a própria crow-behaviour, tipicamente), que não apareceria como duplicata e ficaria lá para sempre. Uma cópia editada, ou também registrada para outro agente daquele diretório, é mantida e reportada em `kept` com o comando. Nada que não tenha sido instalado pelo crow-skills é tocado, e uma falha na migração nunca derruba o resultado do agente (vira `legacy_shared.state: warn`).
|
|
246
|
+
- A cópia legada só sai quando aquele nome REALMENTE funciona no diretório do Codex — instalação íntegra nossa, ou link saudável do Uniflow. Se a entrada do store do Uniflow está quebrada, o link ocupa o nome sem funcionar e a cópia legada é a única skill que o Codex tem: nesse caso ela é mantida e reportada (`legacy_shared_without_replacement`), porque removê-la tiraria a skill em vez de migrá-la. O mesmo vale quando o nome no diretório do Codex é um apelido apontando para a própria cópia legada: remover o alvo deixaria um link pendurado. Cada nome recebe UMA razão em `kept`: uma razão mais específica já registrada para ele (projeção quebrada, destino inseguro) prevalece.
|
|
247
|
+
- **Mudanças de comportamento do destino do codex (0.15.0).** Com o destino no diretório próprio do Codex, uma situação que antes não afetava o codex passa a afetá-lo, exatamente como já valia para claude, gemini e devin: (a) a partir da 0.16.0 um `~/.codex`/`$CODEX_HOME` em symlink é RESOLVIDO para o caminho físico (veja a seção de dotfiles acima); na 0.15.0 ele era recusado com `unsafe_destination`; (b) se houver um link de qualquer tipo ocupando o nome no diretório (inclusive o apelido para a pasta legada), aquele nome não pode ser gerenciado: ele é reportado com status `unsafe` e o comando sai com 1, sem alterar o link. Não é uma regra nova do codex, é a mesma regra dos outros CLIs passando a valer para ele.
|
|
248
|
+
- **`$CODEX_HOME` relativo é ignorado.** Um valor relativo seria resolvido contra o diretório de trabalho DESTE processo, que não é o do Codex, então o destino dependeria de onde o comando foi rodado e poderia cair dentro de um repositório seu. Só valor absoluto (ou com `~`) é honrado; caso contrário vale `<home>/.codex`.
|
|
249
|
+
- **Fallback perdido quando a entrada do store do Uniflow está quebrada.** Até a 0.14.0 o codex tinha duas raízes, então uma entrada quebrada no store ainda deixava uma cópia funcional na pasta compartilhada. Agora o link do Uniflow ocupa o nome no diretório do Codex: a entrada quebrada é reportada (`uniflow_link_modified`, com o comando que conserta o store) e a skill fica sem versão funcional até você consertar. A cópia legada, se existir, NÃO é removida nesse caso — é justamente o que o guarda de substituição protege.
|
|
250
|
+
- **Aviso `codex_legacy_shared` em `status`/`doctor`.** Numa máquina sem Uniflow nada do que ficou na pasta compartilhada duplica uma projeção, então o aviso de duplicatas não apareceria e uma instalação feita pela 0.14.0 ficaria invisível, ainda carregada pelo Codex. Esse aviso novo lista essas skills, com o caminho e o comando (`setup --agents codex --yes`, que migra, ou `uninstall … --dest <pasta legada>`).
|
|
251
|
+
- **Rollback encadeado.** A migração roda como um `uninstall` próprio logo depois do install e fica atrelada a ele (campo `linked` no journal da transação). Um `rollback` da transação do install desfaz PRIMEIRO a migração, restaurando as cópias legadas, e só então remove a instalação nova: o estado final é o anterior ao comando. Sem esse encadeamento, desfazer só o install deixaria as duas pastas vazias.
|
|
252
|
+
- Um link `~/.codex/skills/crow-x` → `~/.agents/skills/crow-x` (apontando para a pasta legada) é só um apelido (o Codex o deduplica pelo caminho canônico): nunca é alterado nem removido.
|
|
229
253
|
- `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
254
|
|
|
231
255
|
|
|
@@ -246,7 +270,7 @@ crow-skills guide uninstall --agents codex --global --yes
|
|
|
246
270
|
|
|
247
271
|
Aplicação automática (padrão em `install`, `update` e `uninstall` com `--global`, desligada por `--no-guide`):
|
|
248
272
|
|
|
249
|
-
- O guia é planejado junto com as skills, a partir do conjunto de skills que o agente terá depois do comando, e conferido antes de gravar qualquer coisa. Install/update que deixam a crow-behaviour instalada aplicam o guia ou o regeneram; mudança de outra skill gerenciada regenera um guia já instalado (também quando a mudança chega pelo diretório
|
|
273
|
+
- O guia é planejado junto com as skills, a partir do conjunto de skills que o agente terá depois do comando, e conferido antes de gravar qualquer coisa. Install/update que deixam a crow-behaviour instalada aplicam o guia ou o regeneram; mudança de outra skill gerenciada regenera um guia já instalado (também quando a mudança chega pelo diretório do codex). Uninstall da crow-behaviour remove o guia, devolvendo o arquivo que `--replace` havia deslocado; uninstall de outra skill regenera o guia instalado e nunca cria um novo.
|
|
250
274
|
- Recusa do guia (arquivo não gerenciado sem `--replace`, guia editado/ausente sem `--overwrite-modified`, chave `developer_instructions` alheia, symlink, hard link, prompt compartilhado ou Uniflow) falha o comando inteiro, com código do guia e sugestão de `--replace`, `--overwrite-modified` ou `--no-guide`, sem gravar skills nem guia. Em install/update, `--replace` e `--overwrite-modified` valem para skills e guia.
|
|
251
275
|
- `shared`, `uniflow`, `--project` e `--dest` não têm mecanismo: o comando segue e informa em `guides.skipped`. Sem Python 3.11+ (tomllib), o guia do codex também é pulado e informado.
|
|
252
276
|
- `--dry-run` inclui `guides.changes` e `guides.preview` (conteúdo que seria gravado). A saída JSON traz `guides` com `changes`, `skipped`, `revision` e `transaction`.
|
|
@@ -527,6 +551,6 @@ Cada execução:
|
|
|
527
551
|
|
|
528
552
|
1. Atualiza o próprio crow-skills conforme a instalação: Homebrew (`brew upgrade andersonflima/tap/crow-skills`), npm global (`npm install -g @andespindola/crow-skills@latest`). Checkout Git só com `autoupdate on --git-pull`, e só se estiver limpo em `main`/`master` (`git pull --ff-only`); caso contrário, é pulado com o motivo. Saída de brew/npm/git não é guardada nem impressa, só código de saída e versões.
|
|
529
553
|
2. Se a versão mudou, a versão nova (o launcher) faz o passo seguinte, para usar o snapshot novo como fonte.
|
|
530
|
-
3. Para cada agente com skills gerenciadas (claude, codex
|
|
554
|
+
3. Para cada agente com skills gerenciadas (claude, codex/`$CODEX_HOME/skills`, gemini, devin, uniflow), roda o equivalente a `update <skills> --agents <agente> --global --yes`: regenera guia, integrações e hooks; a [identidade do pacote](#origem-e-versões) evita `source_mismatch` depois do upgrade. Skills editadas localmente (`modified`), ausentes ou instaladas de outra origem (`other_origin`, por exemplo de um clone Git) ficam como estão e aparecem em `kept`; nada é sobrescrito. Uma reaplicação dos hooks troca as entradas do crow-skills no lugar, sem mudar a posição das suas. Uma recusa num agente (por exemplo, guia editado) aparece em `error` e não interrompe os outros.
|
|
531
555
|
|
|
532
556
|
O resultado fica em `~/.local/state/crow-skills/autoupdate/state.json` (`lastRun` e as últimas 20 execuções), aparece em `autoupdate status`, em `status`/`doctor` (doctor `healthy:false` se a última execução falhou ou o agendador foi alterado/removido) e em `events snapshot`. Duas execuções simultâneas são recusadas (`busy`).
|
package/package.json
CHANGED
package/release.json
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"crow_skills/__init__.py": {
|
|
19
19
|
"executable": false,
|
|
20
20
|
"kind": "file",
|
|
21
|
-
"sha256": "
|
|
21
|
+
"sha256": "6a988b314e5b6bb3f2db799d74aec9717c6c3dfa59b4b1ebdeb1e3697fad498b"
|
|
22
22
|
},
|
|
23
23
|
"crow_skills/__main__.py": {
|
|
24
24
|
"executable": false,
|
|
@@ -28,22 +28,22 @@
|
|
|
28
28
|
"crow_skills/agents.py": {
|
|
29
29
|
"executable": false,
|
|
30
30
|
"kind": "file",
|
|
31
|
-
"sha256": "
|
|
31
|
+
"sha256": "1c2f61e6729c88f0ddc218fba0c5159869e5226d4f7bc0fd2181dd688d4272fe"
|
|
32
32
|
},
|
|
33
33
|
"crow_skills/autoupdate.py": {
|
|
34
34
|
"executable": false,
|
|
35
35
|
"kind": "file",
|
|
36
|
-
"sha256": "
|
|
36
|
+
"sha256": "8de4a6f3f7ce773dafb4c4f9b217d399becbd98b0b0638a80e27e13450e3a0f6"
|
|
37
37
|
},
|
|
38
38
|
"crow_skills/broadcast.py": {
|
|
39
39
|
"executable": false,
|
|
40
40
|
"kind": "file",
|
|
41
|
-
"sha256": "
|
|
41
|
+
"sha256": "3d25bd5fa2bdbe7e6ec78ce9c44a3b96f13d48b2ceea076c792ffbecd5bbbf1f"
|
|
42
42
|
},
|
|
43
43
|
"crow_skills/cli.py": {
|
|
44
44
|
"executable": false,
|
|
45
45
|
"kind": "file",
|
|
46
|
-
"sha256": "
|
|
46
|
+
"sha256": "3182cabd5cf63e780e37f0ae0ef5b2b1090d91debaf90d54765dc80a08e6f864"
|
|
47
47
|
},
|
|
48
48
|
"crow_skills/events.py": {
|
|
49
49
|
"executable": false,
|
|
@@ -58,12 +58,12 @@
|
|
|
58
58
|
"crow_skills/guide.py": {
|
|
59
59
|
"executable": false,
|
|
60
60
|
"kind": "file",
|
|
61
|
-
"sha256": "
|
|
61
|
+
"sha256": "ab75772bc70758b3c3b16339dcf0ec572af358dd0378f7fc3844dbf039590563"
|
|
62
62
|
},
|
|
63
63
|
"crow_skills/hook.py": {
|
|
64
64
|
"executable": false,
|
|
65
65
|
"kind": "file",
|
|
66
|
-
"sha256": "
|
|
66
|
+
"sha256": "3a2b3df94cbd655f88f087a60640c016a5669c06657e97236c841ae19c3a3257"
|
|
67
67
|
},
|
|
68
68
|
"crow_skills/integrations.py": {
|
|
69
69
|
"executable": false,
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"crow_skills/machine.py": {
|
|
74
74
|
"executable": false,
|
|
75
75
|
"kind": "file",
|
|
76
|
-
"sha256": "
|
|
76
|
+
"sha256": "8b7187afae9ad85369fdc8a918e19b32bab9d95776246cda6670a963dff19df3"
|
|
77
77
|
},
|
|
78
78
|
"crow_skills/npm_entry.py": {
|
|
79
79
|
"executable": false,
|
|
@@ -88,12 +88,12 @@
|
|
|
88
88
|
"crow_skills/projection.py": {
|
|
89
89
|
"executable": false,
|
|
90
90
|
"kind": "file",
|
|
91
|
-
"sha256": "
|
|
91
|
+
"sha256": "927cabbdde134c5c504abf6e762026295e6a7a38e8bee1f45a608d21b716be4a"
|
|
92
92
|
},
|
|
93
93
|
"crow_skills/resources.py": {
|
|
94
94
|
"executable": false,
|
|
95
95
|
"kind": "file",
|
|
96
|
-
"sha256": "
|
|
96
|
+
"sha256": "8cb3fe3a24ebd1d84d2c8b135f4c10b76928b3b94d5aa921c847855edb6213a6"
|
|
97
97
|
},
|
|
98
98
|
"crow_skills/source.py": {
|
|
99
99
|
"executable": false,
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"crow_skills/storage.py": {
|
|
104
104
|
"executable": false,
|
|
105
105
|
"kind": "file",
|
|
106
|
-
"sha256": "
|
|
106
|
+
"sha256": "7cbc25b39ada200e516079b81a77770e641b267cd5e5868686eb0d87a6604ea8"
|
|
107
107
|
},
|
|
108
108
|
"crow_skills/trust.py": {
|
|
109
109
|
"executable": false,
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
"crow_skills/ui.py": {
|
|
114
114
|
"executable": false,
|
|
115
115
|
"kind": "file",
|
|
116
|
-
"sha256": "
|
|
116
|
+
"sha256": "24b5cb9aea11603db79c6c10628952263c265251f737cc3621db6872f8c15a91"
|
|
117
117
|
},
|
|
118
118
|
"docs/cli-security.md": {
|
|
119
119
|
"executable": false,
|
|
@@ -123,12 +123,12 @@
|
|
|
123
123
|
"docs/cli.md": {
|
|
124
124
|
"executable": false,
|
|
125
125
|
"kind": "file",
|
|
126
|
-
"sha256": "
|
|
126
|
+
"sha256": "0f425d8d694e1103cd9e56b8b6f6332756bcb8415ca04ec55e0db2f201481807"
|
|
127
127
|
},
|
|
128
128
|
"package.json": {
|
|
129
129
|
"executable": false,
|
|
130
130
|
"kind": "file",
|
|
131
|
-
"sha256": "
|
|
131
|
+
"sha256": "64613f54bedd2d62023be224e30de2dd87bbc0ac7664ed0c49ee8e20a6454ade"
|
|
132
132
|
},
|
|
133
133
|
"skills/crow-accessibility/SKILL.md": {
|
|
134
134
|
"executable": false,
|
|
@@ -716,7 +716,7 @@
|
|
|
716
716
|
"sha256": "e37132ef8b060548191fbf14a5a18a9dd3acd84ea9a35e802174455b9e374d3d"
|
|
717
717
|
}
|
|
718
718
|
},
|
|
719
|
-
"revision": "
|
|
719
|
+
"revision": "23cca75e8117dd6110db7e2565b0ce0b740b8efb",
|
|
720
720
|
"schema_version": 1,
|
|
721
|
-
"version": "0.
|
|
721
|
+
"version": "0.16.0"
|
|
722
722
|
}
|