Cross-platform CLI to centralize, deduplicate, and manage coding-agent skills for Claude Code and Codex across multiple repositories and machines.
Coding-agent skills (prompt libraries, scripts, references, adapters) tend to scatter across repos and machines. Copies diverge, renames break identity, and there is no single source of truth. skills-manager solves this by:
- Collecting skills from configured locations on a machine
- Deduplicating them deterministically (newest wins)
- Importing winners into a single canonical repository
- Building tool-specific dist targets (Claude / Codex)
- Relinking project repos to point at canonical copies via symlinks
After migration, each repo's .claude/skills/ and .agents/skills/ directories contain symlinks to the central skills-manager/skills/dist/ — no more independent copies that drift apart.
There is no manifest file listing "enabled skills." The active set of skills for a repo is exactly what is linked inside that repo's .claude/skills/ and .agents/skills/ directories. Links are the truth.
The file registry/skills-registry.json tracks identity — canonical slugs, aliases, rename history, provenance, content hashes. It exists so that:
- A skill renamed from
auto-convergertoautoconvergeris recognized as the same thing - Re-importing the same skill from another machine doesn't create duplicates
- You can query "where is this skill, what is its canonical name?"
The registry is not an enablement file. It does not control which repos use which skills.
When the same skill slug appears in multiple locations:
- Same content hash → exact duplicate, keep either
- Different hash → newest modification time wins
- Timestamp tie → prefer project > personal > legacy, then richer file tree, then lexicographic path
Losing copies are archived, never silently discarded.
cd /path/to/skills-manager
pip install -e .Or run directly:
python3 -m skills_manager.cli --helpRequires Python 3.11+. No external dependencies.
Create a machine-specific config file (JSON):
{
"machine_name": "linux-dev",
"skills_manager_repo": "/home/user/skills-manager",
"repo_roots": [
"/home/user/projects"
],
"global_skill_roots": [
"/home/user/.claude/skills",
"/home/user/.agents/skills"
],
"legacy_roots": [],
"quarantine_root": "/home/user/skills-manager/backups/quarantine"
}See config/machine.example.json and config/machine.example-windows.json for templates.
| Field | Description |
|---|---|
machine_name |
Human label for this machine |
skills_manager_repo |
Absolute path to this repository |
repo_roots |
Directories to scan for git repos (up to 3 levels deep) |
global_skill_roots |
Personal/global skill directories to scan |
legacy_roots |
Additional non-standard skill locations |
quarantine_root |
Where to move duplicate global skills (optional) |
skills-manager migrate --config config/machine.jsonRuns: collect → build → relink → quarantine. Add --no-quarantine to skip the last step.
# Collect and import from configured roots
skills-manager collect --config config/machine.json
# Build dist targets from canonical src
skills-manager build
# Relink all discovered repos to canonical dist
skills-manager relink --config config/machine.json
# Quarantine duplicate global skills
skills-manager quarantine --config config/machine.json# List all skills in the registry
skills-manager list
# Show where a skill lives (accepts aliases)
skills-manager where firecrawl# Add a skill link to a specific repo
skills-manager add-link --repo /path/to/repo --skill playwright --target both
# Remove a skill link
skills-manager remove-link --repo /path/to/repo --skill playwright --target claudeTarget options: claude, codex, both (default).
The collector scans only configured roots:
- Global roots:
~/.claude/skills,~/.agents/skills(one level of subdirectories) - Repo roots: finds git repos (up to 3 levels deep), then scans
.claude/skills/and.agents/skills/within each - Legacy roots: any additional configured directories
A directory is recognized as a skill if it contains SKILL.md.
skills-manager/
skills/
src/ # canonical editable source (one dir per skill)
firecrawl/
SKILL.md
rules/
.provenance.json # import metadata (auto-generated)
playwright/
SKILL.md
agents/
openai.yaml # Codex adapter
scripts/
dist/ # tool-specific emit targets (auto-generated)
claude/
firecrawl/ # SKILL.md + rules/ (no agents/)
playwright/ # SKILL.md + scripts/ (no agents/)
codex/
firecrawl/ # full copy including agents/ if present
playwright/ # full copy including agents/
- Claude dist: copies everything from src except
agents/(Codex-specific) and.provenance.json - Codex dist: copies everything from src except
.provenance.json
Both tools read SKILL.md as the primary skill definition. The only difference is whether the Codex adapter (agents/openai.yaml) is included.
When relinking a repo:
- For each canonical skill and each target (claude/codex):
- If a local (non-link) skill dir exists → back it up → remove it
- If a correct symlink already exists → skip (idempotent)
- Create symlink to the dist target
Backups go to backups/repo-originals/ with timestamps.
Global/personal skills that duplicate canonical ones can be moved to a quarantine directory. Only skills whose slug matches a registry entry are affected. Symlinks are left alone. Nothing is permanently deleted.
| OS | Strategy |
|---|---|
| Linux/macOS | os.symlink() — standard symlinks |
| Windows | Try os.symlink() first (works in Developer Mode). Fall back to mklink /J (NTFS junction, no elevation needed) |
If both fail on Windows, a clear error is reported.
registry/skills-registry.json:
{
"version": 1,
"updated_at": "2025-06-01T12:00:00+00:00",
"skills": [
{
"canonical_slug": "firecrawl",
"display_name": "firecrawl",
"aliases": ["fire-crawl"],
"current_source_relpath": "skills/src/firecrawl",
"adapter_targets": ["claude", "codex"],
"status": "active",
"provenance": [
{
"action": "imported",
"from_path": "/home/user/.agents/skills/firecrawl",
"scope": "personal",
"machine": "linux-dev",
"hash": "abc123...",
"at": "2025-06-01T12:00:00+00:00"
}
],
"latest_hash": "abc123...",
"first_seen_at": "2025-06-01T12:00:00+00:00",
"last_seen_at": "2025-06-01T12:00:00+00:00",
"supersedes": [],
"superseded_by": null
}
]
}- Set up
skills-manageras a git repo on Machine A - Run
skills-manager migrate --config config/machine-a.json - Commit and push
- Clone on Machine B
- Create
config/machine-b.jsonwith Machine B paths - Run
skills-manager migrate --config config/machine-b.json
The registry merges cleanly because slugs are deterministic and hashes prevent unnecessary overwrites.
pip install pytest
python3 -m pytest tests/ -v62 tests covering: models, collector, registry, deduplication, builder, linker, quarantine, Windows fallback (mocked), and a full end-to-end migration scenario.
- Skill detection requires
SKILL.mdin the directory root. Directories without it are ignored. - Frontmatter parsing is minimal (key: value lines between
---fences). Complex YAML is not supported. - Windows junctions don't support relative paths — absolute paths are used.
- Repo discovery stops at 3 levels of nesting by default. Deeply nested repos may need explicit
repo_rootsentries. - No conflict resolution UI — newest always wins. Manual curation requires editing
skills/src/directly. - No automatic sync — run
migratewhen you want to update. There is no daemon or watcher.