feat(skills): fix browse cap, add source links + copy buttons + category cleanup (#37143)
Skills discovery surfaced ~136 of 88k skills in the CLI and gave community skills no clickable source on the docs page. Three coupled fixes: CLI browse: - hermes skills browse capped at 50 because the per-source limit dict had no 'hermes-index' key — when the centralized index is available the router skips external APIs and serves only the index, so the default-50 fallthrough silently truncated the whole hub. Add hermes-index: 5000. Browse now loads 5367 (269 pages) instead of 136. - Add an Identifier column + install/inspect hint to the browse table so users can act on what they see without a second 'search'. - Route the TUI browse_skills() helper through parallel_search_sources so it inherits the same index-aware source-skip (was double-counting); expose identifier in its output. Docs Skills Hub page: - Synthesize a sourceUrl for every community skill (github tree URL, clawhub / skills.sh / lobehub / browse.sh detail pages), preferring the adapter's explicit extra.detail_url/source_url/repo_url. Expanded cards now show 'View source' for community skills (was nothing) and keep 'View full documentation' for built-in/optional. 99% coverage. - Add a Copy button on the install command. - Add a loading state instead of flashing '0 skills / No skills found' while the 45MB catalog fetches. Category cleanup: - _guess_category fell back to tags[0] verbatim, producing ~430 junk one-off categories (version strings, brand names: '0.10.7 Dev', 'Doramagic Crystal'). Now only curated buckets are accepted; unknowns fold into 'Other'. Widen the tag->category map so common community tags route to real buckets. 430 -> 173 categories, top 20 all meaningful. Tests: tests/website/test_extract_skills.py covers _source_url synthesis + precedence and _guess_category curation (13 tests). All 27 skills-hub CLI tests still pass. Docusaurus build verified; expanded cards confirmed in browser for both community (View source) and built-in (View full docs).
This commit is contained in:
+35
-18
@@ -335,7 +335,14 @@ def do_browse(page: int = 1, page_size: int = 20, source: str = "all",
|
||||
# Collect results from all (or filtered) sources in parallel.
|
||||
# Per-source limits are generous — parallelism + 30s timeout cap prevents hangs.
|
||||
_TRUST_RANK = {"builtin": 3, "trusted": 2, "community": 1}
|
||||
# NOTE: when the centralized index is available, parallel_search_sources
|
||||
# skips the external API sources and serves everything from "hermes-index".
|
||||
# That source MUST therefore carry a high limit, or browse silently caps
|
||||
# the entire hub at the default (50) — it shipped that way and surfaced
|
||||
# ~136 of 88k skills. The external-source limits below only apply when the
|
||||
# index is unavailable (offline / first run before the cache populates).
|
||||
_PER_SOURCE_LIMIT = {
|
||||
"hermes-index": 5000,
|
||||
"official": 200, "skills-sh": 200, "well-known": 50,
|
||||
"github": 200, "clawhub": 500, "claude-marketplace": 100,
|
||||
"lobehub": 500, "browse-sh": 500,
|
||||
@@ -396,18 +403,22 @@ def do_browse(page: int = 1, page_size: int = 20, source: str = "all",
|
||||
# Build table
|
||||
table = Table(show_header=True, header_style="bold")
|
||||
table.add_column("#", style="dim", width=4, justify="right")
|
||||
table.add_column("Name", style="bold cyan", max_width=25)
|
||||
table.add_column("Description", max_width=50)
|
||||
table.add_column("Name", style="bold cyan", max_width=22)
|
||||
table.add_column("Description", max_width=44)
|
||||
table.add_column("Source", style="dim", width=12)
|
||||
table.add_column("Trust", width=10)
|
||||
# The identifier is what you pass to `hermes skills install`. Browse used
|
||||
# to omit it entirely, so users couldn't act on what they saw without a
|
||||
# second `search`. overflow="fold" keeps long slugs copy-pasteable.
|
||||
table.add_column("Identifier", style="dim", overflow="fold", no_wrap=False)
|
||||
|
||||
for i, r in enumerate(page_items, start=start + 1):
|
||||
trust_style = {"builtin": "bright_cyan", "trusted": "green",
|
||||
"community": "yellow"}.get(r.trust_level, "dim")
|
||||
trust_label = "★ official" if r.source == "official" else r.trust_level
|
||||
|
||||
desc = r.description[:50]
|
||||
if len(r.description) > 50:
|
||||
desc = r.description[:44]
|
||||
if len(r.description) > 44:
|
||||
desc += "..."
|
||||
|
||||
table.add_row(
|
||||
@@ -416,6 +427,7 @@ def do_browse(page: int = 1, page_size: int = 20, source: str = "all",
|
||||
desc,
|
||||
r.source,
|
||||
f"[{trust_style}]{trust_label}[/]",
|
||||
r.identifier,
|
||||
)
|
||||
|
||||
c.print(table)
|
||||
@@ -439,7 +451,9 @@ def do_browse(page: int = 1, page_size: int = 20, source: str = "all",
|
||||
c.print(f" [yellow]⚡ Slow sources skipped: {', '.join(timed_out)} "
|
||||
f"— run again for cached results[/]")
|
||||
|
||||
c.print("[dim]Tip: 'hermes skills search <query>' searches deeper across all registries[/]\n")
|
||||
c.print("[dim]Tip: 'hermes skills inspect <identifier>' to preview, "
|
||||
"'hermes skills install <identifier>' to install, "
|
||||
"'hermes skills search <query>' to search deeper[/]\n")
|
||||
|
||||
|
||||
def do_install(identifier: str, category: str = "", force: bool = False,
|
||||
@@ -725,24 +739,27 @@ def browse_skills(page: int = 1, page_size: int = 20, source: str = "all") -> di
|
||||
|
||||
Returns ``{"items": [...], "page": int, "total_pages": int, "total": int}``.
|
||||
"""
|
||||
from tools.skills_hub import GitHubAuth, create_source_router
|
||||
from tools.skills_hub import (
|
||||
GitHubAuth, create_source_router, parallel_search_sources,
|
||||
)
|
||||
|
||||
page_size = max(1, min(page_size, 100))
|
||||
_TRUST_RANK = {"builtin": 3, "trusted": 2, "community": 1}
|
||||
_PER_SOURCE_LIMIT = {"official": 100, "skills-sh": 100, "well-known": 25, "github": 100, "clawhub": 50,
|
||||
# "hermes-index" must carry a high limit: when the index is available the
|
||||
# router skips external API sources and serves everything from it, so a
|
||||
# low cap here silently truncates the whole hub (see do_browse note).
|
||||
_PER_SOURCE_LIMIT = {"hermes-index": 5000, "official": 100, "skills-sh": 100,
|
||||
"well-known": 25, "github": 100, "clawhub": 50,
|
||||
"claude-marketplace": 50, "lobehub": 50, "browse-sh": 500}
|
||||
auth = GitHubAuth()
|
||||
sources = create_source_router(auth)
|
||||
all_results: list = []
|
||||
for src in sources:
|
||||
sid = src.source_id()
|
||||
if source != "all" and sid != source and sid != "official":
|
||||
continue
|
||||
try:
|
||||
limit = _PER_SOURCE_LIMIT.get(sid, 50)
|
||||
all_results.extend(src.search("", limit=limit))
|
||||
except Exception:
|
||||
continue
|
||||
# Delegate to the shared parallel walker so this inherits the index-aware
|
||||
# source-skip logic — querying hermes-index AND the external APIs at once
|
||||
# would double-count every skill.
|
||||
all_results, _counts, _timed_out = parallel_search_sources(
|
||||
sources, query="", per_source_limits=_PER_SOURCE_LIMIT,
|
||||
source_filter=source, overall_timeout=30,
|
||||
)
|
||||
if not all_results:
|
||||
return {"items": [], "page": 1, "total_pages": 1, "total": 0}
|
||||
seen: dict = {}
|
||||
@@ -759,7 +776,7 @@ def browse_skills(page: int = 1, page_size: int = 20, source: str = "all") -> di
|
||||
page_items = deduped[start : min(start + page_size, total)]
|
||||
return {
|
||||
"items": [{"name": r.name, "description": r.description, "source": r.source,
|
||||
"trust": r.trust_level} for r in page_items],
|
||||
"trust": r.trust_level, "identifier": r.identifier} for r in page_items],
|
||||
"page": page,
|
||||
"total_pages": total_pages,
|
||||
"total": total,
|
||||
|
||||
Reference in New Issue
Block a user