ez skill management

managing and updating skills turned out to be tedious with mulitple skill repos, containing myriad skills that i didn't really want to add in to my context, while keeping others.

the workflow is quite simple:

  1. edit the SOURCES dict to add or drop a source or skill.
  2. uv run skills.py installs everything.

the below python file is all that's needed (placed in ~/.claude/skills/skills.py):

import shutil
import subprocess
import sys
from pathlib import Path

DIR = Path(__file__).resolve().parent
# the agent to be triggered by `skills` (https://github.com/vercel-labs/skills)
AGENT = 'claude-code'

# example of skill configs:
# (empty list = fetch everything from that source)
SOURCES = {
    'kunchenguid/lavish-axi': [],
    'mattpocock/skills': [
        'diagnosing-bugs',
        'grill-me',
        'grill-with-docs',
        'grilling',
        'improve-codebase-architecture',
        'tdd',
        'writing-great-skills',
    ],
}


def add():
    for source, skills in SOURCES.items():
        flags = [f for skill in skills for f in ('--skill', skill)]
        cmd = ['npx', 'skills', 'add', source, '-g', '-y', '-a', AGENT, *flags]
        print('+', ' '.join(cmd))
        subprocess.run(cmd, check=True)

    persistent_dir = DIR / 'persistent-skills'
    for skill_dir in sorted(persistent_dir.iterdir()) if persistent_dir.is_dir() else []:
        target = DIR / skill_dir.name
        if target.is_symlink() and not target.exists():
            target.unlink()
        if not target.exists() and not target.is_symlink():
            target.symlink_to(skill_dir)
            print(f'+ linked local skill: {skill_dir.name}')


def clean():
    for entry in DIR.iterdir():
        if entry.is_dir() and not entry.is_symlink() and entry.name != 'persistent-skills':
            shutil.rmtree(entry)


if __name__ == '__main__':
    action = sys.argv[1] if len(sys.argv) > 1 else 'add'
    {'add': add, 'clean': clean}[action]()

add() spawns a process to call npx skills add <source> -g -y -a claude-code --skill <name> ... per source.
then, it symlinks custom skills persistent-skills/ (own skills) into the main folder.

clean() deletes all skills except persistent-skills