feat: wire skills.external_dirs into all remaining discovery paths

The config key skills.external_dirs and core resolution (get_all_skills_dirs,
get_external_skills_dirs in agent/skill_utils.py) already existed but several
code paths still only scanned SKILLS_DIR. Now external dirs are respected
everywhere:

- skills_categories(): scan all dirs for category discovery
- _get_category_from_path(): resolve categories against any skills root
- skill_manager_tool._find_skill(): search all dirs for edit/patch/delete
- credential_files.get_skills_directory_mount(): mount all dirs into
  Docker/Singularity containers (external dirs at external_skills/<idx>)
- credential_files.iter_skills_files(): list files from all dirs for
  Modal/Daytona upload
- tools/environments/ssh.py: rsync all skill dirs to remote hosts
- gateway _check_unavailable_skill(): check disabled skills across all dirs

Usage in config.yaml:
  skills:
    external_dirs:
      - ~/repos/agent-skills/hermes
      - /shared/team-skills
This commit is contained in:
Teknium
2026-04-03 21:14:34 -07:00
parent 5a98ce5973
commit ad4feeaf0d
8 changed files with 149 additions and 86 deletions

View File

@@ -349,19 +349,23 @@ def _check_unavailable_skill(command_name: str) -> str | None:
# Normalize: command uses hyphens, skill names may use hyphens or underscores
normalized = command_name.lower().replace("_", "-")
try:
from tools.skills_tool import SKILLS_DIR, _get_disabled_skill_names
from tools.skills_tool import _get_disabled_skill_names
from agent.skill_utils import get_all_skills_dirs
disabled = _get_disabled_skill_names()
# Check disabled built-in skills
for skill_md in SKILLS_DIR.rglob("SKILL.md"):
if any(part in ('.git', '.github', '.hub') for part in skill_md.parts):
# Check disabled skills across all dirs (local + external)
for skills_dir in get_all_skills_dirs():
if not skills_dir.exists():
continue
name = skill_md.parent.name.lower().replace("_", "-")
if name == normalized and name in disabled:
return (
f"The **{command_name}** skill is installed but disabled.\n"
f"Enable it with: `hermes skills config`"
)
for skill_md in skills_dir.rglob("SKILL.md"):
if any(part in ('.git', '.github', '.hub') for part in skill_md.parts):
continue
name = skill_md.parent.name.lower().replace("_", "-")
if name == normalized and name in disabled:
return (
f"The **{command_name}** skill is installed but disabled.\n"
f"Enable it with: `hermes skills config`"
)
# Check optional skills (shipped with repo but not installed)
from hermes_constants import get_hermes_home, get_optional_skills_dir