fix(skills-hub): stop ellipsis-truncating the Identifier column (#33810)

`hermes skills search` rendered the Identifier column with the default
overflow behaviour, so long slugs (notably browse-sh — every browse-sh
skill ends in a `-XXXXXX` hash that's part of the identifier) were cut
to `browse-sh/weathe…`. Users copied the visible string into
`hermes skills install` and got a not-found error because the hash was
gone.

Set overflow="fold" on the Identifier column in both search tables
(`do_search` and the `_resolve_short_name` multi-match table) so long
slugs wrap onto a second line instead of getting eaten. Also add a
`--json` flag to `hermes skills search` (and the `/skills search`
slash variant) for scripting — emits a list of {name, identifier,
source, trust_level, description} objects with the full identifier,
which is the right shape for copy-paste pipelines too.

Closes #33674.
This commit is contained in:
Teknium 2026-05-28 04:53:13 -07:00 committed by GitHub
parent 5e1f793430
commit e0572a6def
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 143 additions and 9 deletions

View file

@ -12656,6 +12656,11 @@ Examples:
],
)
skills_search.add_argument("--limit", type=int, default=10, help="Max results")
skills_search.add_argument(
"--json",
action="store_true",
help="Output JSON instead of a table (full identifiers, scripting-friendly)",
)
skills_install = skills_subparsers.add_parser("install", help="Install a skill")
skills_install.add_argument(

View file

@ -58,7 +58,9 @@ def _resolve_short_name(name: str, sources, console: Console) -> str:
table = Table()
table.add_column("Source", style="dim")
table.add_column("Trust", style="dim")
table.add_column("Identifier", style="bold cyan")
# overflow="fold" keeps the full slug visible (wraps instead of ellipsis-truncating)
# so users can copy it for `hermes skills install`.
table.add_column("Identifier", style="bold cyan", overflow="fold", no_wrap=False)
for r in exact:
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
@ -244,15 +246,39 @@ def _prompt_for_category(c: Console, existing: List[str]) -> str:
def do_search(query: str, source: str = "all", limit: int = 10,
console: Optional[Console] = None) -> None:
"""Search registries and display results as a Rich table."""
console: Optional[Console] = None, as_json: bool = False) -> None:
"""Search registries and display results as a Rich table.
When ``as_json=True`` writes a JSON array of result records to stdout
(one object per skill: ``name``, ``identifier``, ``source``,
``trust_level``, ``description``) and skips the table render. This is
the scripting / copy-paste handle: the full identifier is always
intact, even for browse-sh slugs that the table would otherwise wrap.
"""
from tools.skills_hub import GitHubAuth, create_source_router, unified_search
c = console or _console
c.print(f"\n[bold]Searching for:[/] {query}")
auth = GitHubAuth()
sources = create_source_router(auth)
if as_json:
# Avoid Rich status spinner contaminating stdout — JSON consumers
# expect a clean parseable stream.
results = unified_search(query, sources, source_filter=source, limit=limit)
payload = [
{
"name": r.name,
"identifier": r.identifier,
"source": r.source,
"trust_level": r.trust_level,
"description": r.description,
}
for r in results
]
print(json.dumps(payload, indent=2))
return
c.print(f"\n[bold]Searching for:[/] {query}")
with c.status("[bold]Searching registries..."):
results = unified_search(query, sources, source_filter=source, limit=limit)
@ -265,7 +291,11 @@ def do_search(query: str, source: str = "all", limit: int = 10,
table.add_column("Description", max_width=60)
table.add_column("Source", style="dim")
table.add_column("Trust", style="dim")
table.add_column("Identifier", style="dim")
# overflow="fold" keeps the full slug visible (wraps instead of
# ellipsis-truncating). Browse.sh slugs end in a `-XXXXXX` hash that
# is part of the actual identifier — truncating it makes copy-paste
# into `hermes skills install` fail.
table.add_column("Identifier", style="dim", overflow="fold", no_wrap=False)
for r in results:
trust_style = {"builtin": "bright_cyan", "trusted": "green", "community": "yellow"}.get(r.trust_level, "dim")
@ -280,7 +310,8 @@ def do_search(query: str, source: str = "all", limit: int = 10,
c.print(table)
c.print("[dim]Use: hermes skills inspect <identifier> to preview, "
"hermes skills install <identifier> to install[/]\n")
"hermes skills install <identifier> to install "
"(--json for scripting)[/]\n")
def do_browse(page: int = 1, page_size: int = 20, source: str = "all",
@ -1390,7 +1421,8 @@ def skills_command(args) -> None:
if action == "browse":
do_browse(page=args.page, page_size=args.size, source=args.source)
elif action == "search":
do_search(args.query, source=args.source, limit=args.limit)
do_search(args.query, source=args.source, limit=args.limit,
as_json=getattr(args, "json", False))
elif action == "install":
do_install(args.identifier, category=args.category, force=args.force,
skip_confirm=getattr(args, "yes", False),
@ -1511,10 +1543,11 @@ def handle_skills_slash(cmd: str, console: Optional[Console] = None) -> None:
elif action == "search":
if not args:
c.print("[bold red]Usage:[/] /skills search <query> [--source skills-sh|well-known|github|official] [--limit N]\n")
c.print("[bold red]Usage:[/] /skills search <query> [--source skills-sh|well-known|github|official] [--limit N] [--json]\n")
return
source = "all"
limit = 10
as_json = False
query_parts = []
i = 0
while i < len(args):
@ -1527,10 +1560,14 @@ def handle_skills_slash(cmd: str, console: Optional[Console] = None) -> None:
except ValueError:
pass
i += 2
elif args[i] == "--json":
as_json = True
i += 1
else:
query_parts.append(args[i])
i += 1
do_search(" ".join(query_parts), source=source, limit=limit, console=c)
do_search(" ".join(query_parts), source=source, limit=limit,
console=c, as_json=as_json)
elif action == "install":
if not args: