Add skill name resolution and enhanced install confirmation in Skills Hub
- Introduced a new function `_resolve_short_name` to convert short skill names to full identifiers, improving user experience during skill installation. - Updated the `do_install` function to utilize the new resolution method for identifiers without slashes, ensuring accurate skill fetching. - Enhanced the install confirmation process to include a disclaimer about third-party skills, emphasizing user responsibility and security awareness.
This commit is contained in:
parent
14e59706b7
commit
655303f2f1
1 changed files with 69 additions and 2 deletions
|
|
@ -30,6 +30,52 @@ _console = Console()
|
||||||
# Shared do_* functions
|
# Shared do_* functions
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _resolve_short_name(name: str, sources, console: Console) -> str:
|
||||||
|
"""
|
||||||
|
Resolve a short skill name (e.g. 'pptx') to a full identifier by searching
|
||||||
|
all sources. If exactly one match is found, returns its identifier. If multiple
|
||||||
|
matches exist, shows them and asks the user to use the full identifier.
|
||||||
|
Returns empty string if nothing found or ambiguous.
|
||||||
|
"""
|
||||||
|
from tools.skills_hub import unified_search
|
||||||
|
|
||||||
|
c = console or _console
|
||||||
|
c.print(f"[dim]Resolving '{name}'...[/]")
|
||||||
|
|
||||||
|
results = unified_search(name, sources, source_filter="all", limit=20)
|
||||||
|
|
||||||
|
# Filter to exact name matches (case-insensitive)
|
||||||
|
exact = [r for r in results if r.name.lower() == name.lower()]
|
||||||
|
|
||||||
|
if len(exact) == 1:
|
||||||
|
c.print(f"[dim]Resolved to: {exact[0].identifier}[/]")
|
||||||
|
return exact[0].identifier
|
||||||
|
|
||||||
|
if len(exact) > 1:
|
||||||
|
c.print(f"\n[yellow]Multiple skills named '{name}' found:[/]")
|
||||||
|
table = Table()
|
||||||
|
table.add_column("Source", style="dim")
|
||||||
|
table.add_column("Trust", style="dim")
|
||||||
|
table.add_column("Identifier", style="bold cyan")
|
||||||
|
for r in exact:
|
||||||
|
trust_style = {"trusted": "green", "community": "yellow"}.get(r.trust_level, "dim")
|
||||||
|
table.add_row(r.source, f"[{trust_style}]{r.trust_level}[/]", r.identifier)
|
||||||
|
c.print(table)
|
||||||
|
c.print("[bold]Use the full identifier to install a specific one.[/]\n")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# No exact match — check if there are partial matches to suggest
|
||||||
|
if results:
|
||||||
|
c.print(f"[yellow]No exact match for '{name}'. Did you mean one of these?[/]")
|
||||||
|
for r in results[:5]:
|
||||||
|
c.print(f" [cyan]{r.name}[/] — {r.identifier}")
|
||||||
|
c.print()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
c.print(f"[bold red]Error:[/] No skill named '{name}' found in any source.\n")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def do_search(query: str, source: str = "all", limit: int = 10,
|
def do_search(query: str, source: str = "all", limit: int = 10,
|
||||||
console: Optional[Console] = None) -> None:
|
console: Optional[Console] = None) -> None:
|
||||||
"""Search registries and display results as a Rich table."""
|
"""Search registries and display results as a Rich table."""
|
||||||
|
|
@ -84,6 +130,12 @@ def do_install(identifier: str, category: str = "", force: bool = False,
|
||||||
auth = GitHubAuth()
|
auth = GitHubAuth()
|
||||||
sources = create_source_router(auth)
|
sources = create_source_router(auth)
|
||||||
|
|
||||||
|
# If identifier looks like a short name (no slashes), resolve it via search
|
||||||
|
if "/" not in identifier:
|
||||||
|
identifier = _resolve_short_name(identifier, sources, c)
|
||||||
|
if not identifier:
|
||||||
|
return
|
||||||
|
|
||||||
c.print(f"\n[bold]Fetching:[/] {identifier}")
|
c.print(f"\n[bold]Fetching:[/] {identifier}")
|
||||||
|
|
||||||
bundle = None
|
bundle = None
|
||||||
|
|
@ -126,9 +178,19 @@ def do_install(identifier: str, category: str = "", force: bool = False,
|
||||||
f"{len(result.findings)}_findings")
|
f"{len(result.findings)}_findings")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Confirm with user
|
# Confirm with user — always show risk warning regardless of source
|
||||||
if not force:
|
if not force:
|
||||||
c.print(f"\n[bold]Install '{bundle.name}' to skills/{category + '/' if category else ''}{bundle.name}?[/]")
|
c.print()
|
||||||
|
c.print(Panel(
|
||||||
|
"[bold yellow]You are installing a third-party skill at your own risk.[/]\n\n"
|
||||||
|
"External skills can contain instructions that influence agent behavior,\n"
|
||||||
|
"shell commands, and scripts. Even after automated scanning, you should\n"
|
||||||
|
"review the installed files before use.\n\n"
|
||||||
|
f"Files will be at: [cyan]skills/{category + '/' if category else ''}{bundle.name}/[/]",
|
||||||
|
title="Disclaimer",
|
||||||
|
border_style="yellow",
|
||||||
|
))
|
||||||
|
c.print(f"[bold]Install '{bundle.name}'?[/]")
|
||||||
try:
|
try:
|
||||||
answer = input("Confirm [y/N]: ").strip().lower()
|
answer = input("Confirm [y/N]: ").strip().lower()
|
||||||
except (EOFError, KeyboardInterrupt):
|
except (EOFError, KeyboardInterrupt):
|
||||||
|
|
@ -153,6 +215,11 @@ def do_inspect(identifier: str, console: Optional[Console] = None) -> None:
|
||||||
auth = GitHubAuth()
|
auth = GitHubAuth()
|
||||||
sources = create_source_router(auth)
|
sources = create_source_router(auth)
|
||||||
|
|
||||||
|
if "/" not in identifier:
|
||||||
|
identifier = _resolve_short_name(identifier, sources, c)
|
||||||
|
if not identifier:
|
||||||
|
return
|
||||||
|
|
||||||
meta = None
|
meta = None
|
||||||
for src in sources:
|
for src in sources:
|
||||||
meta = src.inspect(identifier)
|
meta = src.inspect(identifier)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue