boost-skill-cli 1.0.1__py3-none-any.whl
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.
- boost_cli/__init__.py +5 -0
- boost_cli/__main__.py +8 -0
- boost_cli/cli.py +213 -0
- boost_cli/commands/__init__.py +0 -0
- boost_cli/commands/configuration.py +1087 -0
- boost_cli/commands/discovery.py +650 -0
- boost_cli/commands/info.py +595 -0
- boost_cli/commands/intelligence.py +1048 -0
- boost_cli/commands/pkg.py +828 -0
- boost_cli/commands/quality.py +1041 -0
- boost_cli/commands/taps.py +203 -0
- boost_cli/commands/team.py +679 -0
- boost_cli/core/__init__.py +0 -0
- boost_cli/core/agents.py +33 -0
- boost_cli/core/ai.py +115 -0
- boost_cli/core/catalog.py +144 -0
- boost_cli/core/config.py +112 -0
- boost_cli/core/frontmatter.py +160 -0
- boost_cli/core/gitutil.py +69 -0
- boost_cli/core/journal.py +70 -0
- boost_cli/core/lockfile.py +116 -0
- boost_cli/core/output.py +99 -0
- boost_cli/core/paths.py +111 -0
- boost_cli/core/policy.py +63 -0
- boost_cli/core/registry.py +112 -0
- boost_cli/core/store.py +247 -0
- boost_cli/core/util.py +139 -0
- boost_cli/errors.py +11 -0
- boost_skill_cli-1.0.1.dist-info/METADATA +831 -0
- boost_skill_cli-1.0.1.dist-info/RECORD +34 -0
- boost_skill_cli-1.0.1.dist-info/WHEEL +5 -0
- boost_skill_cli-1.0.1.dist-info/entry_points.txt +2 -0
- boost_skill_cli-1.0.1.dist-info/licenses/LICENSE +675 -0
- boost_skill_cli-1.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Registry (taps) commands: tap, untap, taps, outdated."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
from ..core import catalog, config, gitutil, journal, lockfile, paths, registry, store, util
|
|
9
|
+
from ..core import output as out
|
|
10
|
+
from ..errors import BoostError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _tilde(p) -> str:
|
|
14
|
+
"""Contract $HOME to ~ in a path-ish string for display."""
|
|
15
|
+
s = str(p)
|
|
16
|
+
for h in {str(paths.home()), str(paths.home().resolve())}:
|
|
17
|
+
if s == h:
|
|
18
|
+
return "~"
|
|
19
|
+
if s.startswith(h + os.sep):
|
|
20
|
+
return "~" + s[len(h):]
|
|
21
|
+
return s
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def cmd_tap(argv) -> int:
|
|
25
|
+
"""boost tap [SPEC] [--defaults] [--curated]"""
|
|
26
|
+
p = argparse.ArgumentParser(
|
|
27
|
+
prog="boost tap",
|
|
28
|
+
description="Add a GitHub repo as a skill registry")
|
|
29
|
+
p.add_argument("spec", nargs="?",
|
|
30
|
+
help="owner/repo, a git URL, or a local directory")
|
|
31
|
+
p.add_argument("--defaults", action="store_true",
|
|
32
|
+
help="tap the recommended public registries")
|
|
33
|
+
p.add_argument("--curated", action="store_true",
|
|
34
|
+
help="mark the tap as curated (★ in listings)")
|
|
35
|
+
args = p.parse_args(argv)
|
|
36
|
+
if not args.spec and not args.defaults:
|
|
37
|
+
p.error("provide a SPEC or --defaults")
|
|
38
|
+
|
|
39
|
+
rc = 0
|
|
40
|
+
if args.defaults:
|
|
41
|
+
existing = {t.name for t in registry.list_taps()}
|
|
42
|
+
for default in config.DEFAULT_TAPS:
|
|
43
|
+
if default["name"] in existing:
|
|
44
|
+
out.info(out.c("%s already tapped" % default["name"], out.DIM))
|
|
45
|
+
continue
|
|
46
|
+
try:
|
|
47
|
+
tap = registry.add(str(default["url"]), curated=True)
|
|
48
|
+
entries = catalog.rebuild_tap(tap)
|
|
49
|
+
except BoostError as e:
|
|
50
|
+
out.warn("could not tap %s: %s" % (default["name"], e.message))
|
|
51
|
+
rc = 1
|
|
52
|
+
continue
|
|
53
|
+
journal.log("tap", tap.name)
|
|
54
|
+
out.ok("tapped %s (%d skills) — %s"
|
|
55
|
+
% (tap.name, len(entries), default.get("focus", "")))
|
|
56
|
+
if args.spec:
|
|
57
|
+
tap = registry.add(args.spec, curated=args.curated)
|
|
58
|
+
entries = catalog.rebuild_tap(tap)
|
|
59
|
+
journal.log("tap", tap.name)
|
|
60
|
+
out.ok("Tapped %s (%d skills)" % (tap.name, len(entries)))
|
|
61
|
+
return rc
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def cmd_untap(argv) -> int:
|
|
65
|
+
"""boost untap NAME [--force]"""
|
|
66
|
+
p = argparse.ArgumentParser(
|
|
67
|
+
prog="boost untap",
|
|
68
|
+
description="Remove a registry tap")
|
|
69
|
+
p.add_argument("name", help="tap name (owner/repo or short alias)")
|
|
70
|
+
p.add_argument("-f", "--force", action="store_true",
|
|
71
|
+
help="skip the confirmation prompt")
|
|
72
|
+
p.add_argument("-y", "--yes", action="store_true", help=argparse.SUPPRESS)
|
|
73
|
+
args = p.parse_args(argv)
|
|
74
|
+
|
|
75
|
+
tap = registry.get(args.name)
|
|
76
|
+
dependent = sorted(n for n, e in lockfile.installed().items()
|
|
77
|
+
if e.get("tap") == tap.name)
|
|
78
|
+
if dependent:
|
|
79
|
+
out.warn("%d skill(s) installed from %s: %s"
|
|
80
|
+
% (len(dependent), tap.name, ", ".join(dependent)))
|
|
81
|
+
out.warn("installed skills keep working but lose their update source")
|
|
82
|
+
if not (args.force or args.yes) and not out.confirm(
|
|
83
|
+
"untap %s anyway?" % tap.name):
|
|
84
|
+
out.info("cancelled")
|
|
85
|
+
return 1
|
|
86
|
+
registry.remove(tap.name)
|
|
87
|
+
journal.log("untap", tap.name)
|
|
88
|
+
out.ok("untapped %s" % tap.name)
|
|
89
|
+
return 0
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _tap_updated(tap: "registry.Tap") -> str:
|
|
93
|
+
"""Last-commit date of a tap clone, else the cache's generated age."""
|
|
94
|
+
if tap.is_cloned:
|
|
95
|
+
try:
|
|
96
|
+
# --date=short --format=%cd == %cs, but works on git < 2.21 too
|
|
97
|
+
proc = gitutil.run(["-C", str(tap.path), "log", "-1",
|
|
98
|
+
"--date=short", "--format=%cd"], check=False)
|
|
99
|
+
if proc.returncode == 0 and proc.stdout.strip():
|
|
100
|
+
return proc.stdout.strip()
|
|
101
|
+
except BoostError:
|
|
102
|
+
pass
|
|
103
|
+
try:
|
|
104
|
+
data = json.loads(tap.cache_file.read_text())
|
|
105
|
+
return util.rel_time(data.get("generated", ""))
|
|
106
|
+
except (OSError, ValueError):
|
|
107
|
+
return "?"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def cmd_taps(argv) -> int:
|
|
111
|
+
"""boost taps [--json]"""
|
|
112
|
+
p = argparse.ArgumentParser(
|
|
113
|
+
prog="boost taps",
|
|
114
|
+
description="List all configured registry taps")
|
|
115
|
+
p.add_argument("--json", action="store_true",
|
|
116
|
+
help="machine-readable output")
|
|
117
|
+
args = p.parse_args(argv)
|
|
118
|
+
|
|
119
|
+
taps = []
|
|
120
|
+
total = 0
|
|
121
|
+
for tap in registry.list_taps():
|
|
122
|
+
skills = catalog.load_tap(tap)
|
|
123
|
+
total += len(skills)
|
|
124
|
+
taps.append({"name": tap.name, "url": tap.url, "curated": tap.curated,
|
|
125
|
+
"skills": len(skills), "updated": _tap_updated(tap)})
|
|
126
|
+
if args.json:
|
|
127
|
+
print(json.dumps(taps, indent=2))
|
|
128
|
+
return 0
|
|
129
|
+
if not taps:
|
|
130
|
+
out.info("no taps configured")
|
|
131
|
+
out.info(out.c("add the recommended registries with `boost tap --defaults`",
|
|
132
|
+
out.DIM))
|
|
133
|
+
return 0
|
|
134
|
+
rows = [(t["name"], str(t["skills"]), t["updated"],
|
|
135
|
+
"★" if t["curated"] else "", out.c(_tilde(t["url"]), out.DIM))
|
|
136
|
+
for t in taps]
|
|
137
|
+
out.table(rows, headers=("NAME", "SKILLS", "UPDATED", "", "URL"))
|
|
138
|
+
print()
|
|
139
|
+
out.dim("%d taps · %d skills" % (len(taps), total))
|
|
140
|
+
return 0
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def cmd_outdated(argv) -> int:
|
|
144
|
+
"""boost outdated [--json]"""
|
|
145
|
+
p = argparse.ArgumentParser(
|
|
146
|
+
prog="boost outdated",
|
|
147
|
+
description="Show skills with available updates")
|
|
148
|
+
p.add_argument("--json", action="store_true",
|
|
149
|
+
help="machine-readable output")
|
|
150
|
+
args = p.parse_args(argv)
|
|
151
|
+
|
|
152
|
+
results = []
|
|
153
|
+
heads = {} # tap name -> HEAD commit ("" when unknown)
|
|
154
|
+
for name, lk in sorted(lockfile.installed().items()):
|
|
155
|
+
tap_name = lk.get("tap", "local")
|
|
156
|
+
if tap_name == "local":
|
|
157
|
+
continue
|
|
158
|
+
matches = [e for e in catalog.find(name) if e["tap"] == tap_name]
|
|
159
|
+
if not matches:
|
|
160
|
+
continue
|
|
161
|
+
entry = matches[0]
|
|
162
|
+
latest = str(entry.get("version") or "0.0.0")
|
|
163
|
+
installed_v = str(lk.get("version") or "0.0.0")
|
|
164
|
+
stale, latest_disp = False, latest
|
|
165
|
+
if util.semver_gt(latest, installed_v):
|
|
166
|
+
stale = True
|
|
167
|
+
else:
|
|
168
|
+
if tap_name not in heads:
|
|
169
|
+
try:
|
|
170
|
+
tap = registry.get(tap_name)
|
|
171
|
+
heads[tap_name] = (gitutil.head_commit(tap.path)
|
|
172
|
+
if tap.is_cloned else "")
|
|
173
|
+
except BoostError:
|
|
174
|
+
heads[tap_name] = ""
|
|
175
|
+
head = heads[tap_name]
|
|
176
|
+
if head and head != lk.get("commit"):
|
|
177
|
+
try:
|
|
178
|
+
src = store.source_dir_for(entry)
|
|
179
|
+
except BoostError:
|
|
180
|
+
stale, latest_disp = True, "source missing"
|
|
181
|
+
else:
|
|
182
|
+
if util.sha256_dir(src) != lk.get("sha256"):
|
|
183
|
+
stale = True
|
|
184
|
+
latest_disp = "%s (%s)" % (latest, head[:7])
|
|
185
|
+
if stale:
|
|
186
|
+
results.append({"name": name, "installed": installed_v,
|
|
187
|
+
"latest": latest_disp, "tap": tap_name,
|
|
188
|
+
"pinned": bool(lk.get("pinned"))})
|
|
189
|
+
|
|
190
|
+
if args.json:
|
|
191
|
+
print(json.dumps(results, indent=2))
|
|
192
|
+
return 0
|
|
193
|
+
if not results:
|
|
194
|
+
out.ok("everything up to date")
|
|
195
|
+
return 0
|
|
196
|
+
rows = [(r["name"],
|
|
197
|
+
r["installed"] + (" (pinned)" if r["pinned"] else ""),
|
|
198
|
+
r["latest"], r["tap"]) for r in results]
|
|
199
|
+
out.table(rows, headers=("NAME", "INSTALLED", "LATEST", "TAP"))
|
|
200
|
+
print()
|
|
201
|
+
out.dim("%d outdated · `boost update` upgrades (pinned skills stay put)"
|
|
202
|
+
% len(results))
|
|
203
|
+
return 0
|