feat: hermes skills — enable/disable individual skills and categories (#642)

Add interactive skill configuration via `hermes skills` command,
mirroring the existing `hermes tools` pattern.

Changes:
- hermes_cli/skills_config.py (new): skills_command() entry point with
  curses checklist UI + numbered fallback. Supports global and
  per-platform disable lists, individual skill toggle, and category toggle.
- hermes_cli/main.py: register `hermes skills` subcommand
- tools/skills_tool.py: add _is_skill_disabled() and filter disabled
  skills in _find_all_skills(). Resolves platform from argument,
  HERMES_PLATFORM env var, then falls back to global disabled list.

Config schema (config.yaml):
  skills:
    disabled: [skill-a]                 # global
    platform_disabled:
      telegram: [skill-b]               # per-platform override

22 unit tests, 2489 passed, 0 failed.

Closes #642
This commit is contained in:
teyrebaz33 2026-03-09 07:02:06 +03:00
parent 2036c22f88
commit 7241e8784a
4 changed files with 556 additions and 0 deletions

View file

@ -219,6 +219,29 @@ def _parse_tags(tags_value) -> List[str]:
return [t.strip().strip('"\'') for t in tags_value.split(',') if t.strip()]
def _is_skill_disabled(name: str, platform: str = None) -> bool:
"""Check if a skill is disabled in config, globally or for a specific platform.
Platform is resolved from the ``platform`` argument, then the
``HERMES_PLATFORM`` env var, then falls back to the global disabled list.
"""
import os
try:
from hermes_cli.config import load_config
config = load_config()
skills_cfg = config.get("skills", {})
# Resolve platform
resolved_platform = platform or os.getenv("HERMES_PLATFORM")
if resolved_platform:
platform_disabled = skills_cfg.get("platform_disabled", {}).get(resolved_platform)
if platform_disabled is not None:
return name in platform_disabled
# Fall back to global disabled list
return name in skills_cfg.get("disabled", [])
except Exception:
return False
def _find_all_skills() -> List[Dict[str, Any]]:
"""
Recursively find all skills in ~/.hermes/skills/.
@ -249,6 +272,9 @@ def _find_all_skills() -> List[Dict[str, Any]]:
continue
name = frontmatter.get('name', skill_dir.name)[:MAX_NAME_LENGTH]
# Skip disabled skills
if _is_skill_disabled(name):
continue
description = frontmatter.get('description', '')
if not description: