fix(skills): distinguish local skills from builtin in 'skills list'

Use _read_manifest() to identify true bundled skills. Non-hub,
non-builtin skills are now labeled 'local' instead of 'builtin'.
Adds --source local filter and updates summary counts.

Cherry-picked from PR #869 by Jah-yee.
Fixes #861

Co-authored-by: OpenClaw <openclaw@sparklab.ai>
This commit is contained in:
OpenClaw 2026-03-11 09:16:39 -07:00 committed by teknium1
parent 2e4ccbc806
commit effb44e4bd
2 changed files with 17 additions and 6 deletions

View file

@ -2278,7 +2278,7 @@ For more help on a command:
skills_inspect.add_argument("identifier", help="Skill identifier")
skills_list = skills_subparsers.add_parser("list", help="List installed skills")
skills_list.add_argument("--source", default="all", choices=["all", "hub", "builtin"])
skills_list.add_argument("--source", default="all", choices=["all", "hub", "builtin", "local"])
skills_audit = skills_subparsers.add_parser("audit", help="Re-scan installed hub skills")
skills_audit.add_argument("name", nargs="?", help="Specific skill to audit (default: all)")

View file

@ -409,6 +409,7 @@ def do_inspect(identifier: str, console: Optional[Console] = None) -> None:
def do_list(source_filter: str = "all", console: Optional[Console] = None) -> None:
"""List installed skills, distinguishing builtins from hub-installed."""
from tools.skills_hub import HubLockFile, ensure_hub_dirs
from tools.skills_sync import _read_manifest
from tools.skills_tool import _find_all_skills
c = console or _console
@ -416,6 +417,9 @@ def do_list(source_filter: str = "all", console: Optional[Console] = None) -> No
lock = HubLockFile()
hub_installed = {e["name"]: e for e in lock.list_installed()}
# Read bundled manifest to distinguish true builtins from local skills
bundled_skills = _read_manifest()
all_skills = _find_all_skills()
table = Table(title="Installed Skills")
@ -432,22 +436,29 @@ def do_list(source_filter: str = "all", console: Optional[Console] = None) -> No
if hub_entry:
source_display = hub_entry.get("source", "hub")
trust = hub_entry.get("trust_level", "community")
else:
elif name in bundled_skills:
source_display = "builtin"
trust = "builtin"
else:
# User-provided local skills (e.g., from ~/.hermes/skills/)
source_display = "local"
trust = "local"
if source_filter == "hub" and not hub_entry:
continue
if source_filter == "builtin" and hub_entry:
if source_filter == "builtin" and source_display != "builtin":
continue
if source_filter == "local" and source_display != "local":
continue
trust_style = {"builtin": "bright_cyan", "trusted": "green", "community": "yellow"}.get(trust, "dim")
trust_style = {"builtin": "bright_cyan", "trusted": "green", "community": "yellow", "local": "dim"}.get(trust, "dim")
trust_label = "official" if source_display == "official" else trust
table.add_row(name, category, source_display, f"[{trust_style}]{trust_label}[/]")
c.print(table)
c.print(f"[dim]{len(hub_installed)} hub-installed, "
f"{len(all_skills) - len(hub_installed)} builtin[/]\n")
f"{len([s for s in all_skills if s['name'] in bundled_skills])} builtin, "
f"{len([s for s in all_skills if s['name'] not in hub_installed and s['name'] not in bundled_skills])} local[/]\n")
def do_audit(name: Optional[str] = None, console: Optional[Console] = None) -> None:
@ -1014,7 +1025,7 @@ def _print_skills_help(console: Console) -> None:
" [cyan]search[/] <query> Search registries for skills\n"
" [cyan]install[/] <identifier> Install a skill (with security scan)\n"
" [cyan]inspect[/] <identifier> Preview a skill without installing\n"
" [cyan]list[/] [--source hub|builtin] List installed skills\n"
" [cyan]list[/] [--source hub|builtin|local] List installed skills\n"
" [cyan]audit[/] [name] Re-scan hub skills for security\n"
" [cyan]uninstall[/] <name> Remove a hub-installed skill\n"
" [cyan]publish[/] <path> --repo <r> Publish a skill to GitHub via PR\n"