Manage agent skills across AI harnesses. Built with git source control in mind. By default, skills are symlinked — they live in one place and are linked into whichever harnesses and projects you choose, so updates to your skill repos propagate everywhere instantly. When you need a detached copy instead, use --import.
No extra bells and whistles. By design, this is not a skill browser or search engine. It doesn't include any built-in skills.
This a simple tool for explicitly specifying remote repositories / local directories as single sources of truth for agent skills. This let's you maintain version control via git without accidentally losing changes across multiple project directories.
demo-skillctl.mp4
# One-liner install from GitHub
curl -fsSL https://raw.githubusercontent.com/r3b1s/skillctl/main/install-remote.sh | bash
# Local install from a git clone
git clone https://github.com/r3b1s/skillctl.git
cd skillctl
bash install.sh
# Update an installer-managed copy
skillctl-update
# Uninstall an installer-managed copy
skillctl-uninstallYou can also install skillctl via the AUR (https://aur.archlinux.org/packages/skillctl).
Just use your preferred package manager:
# yay
yay -S skillctl
# paru
paru -S skillctl
When installed via a package manager, manage updates and uninstallation with that package manager. skillctl-update and skillctl-uninstall are only intended for copies installed by install.sh or install-remote.sh.
This repo includes GitHub Actions automation for publishing AUR updates when a GitHub release is published. The one-time AUR SSH setup and release steps are documented in docs/releasing.md.
skillctl has built-in support for the following AI coding harnesses:
| Harness | Per-project directory | Global directory |
|---|---|---|
| Claude Code | .claude/skills/<skill> |
~/.claude/skills/<skill> |
| Cursor | .cursor/skills/<skill> |
~/.cursor/skills/<skill> |
| KiloCode | .kilocode/skills/<skill> |
~/.kilocode/skills/<skill> |
| GitHub Copilot | .github/skills/<skill> |
~/.github/skills/<skill> |
| cline | .cline/skills/<skill> |
~/.cline/skills/<skill> |
| goose | .goose/skills/<skill> |
~/.goose/skills/<skill> |
| pi | .pi/skills/<skill> |
~/.pi/skills/<skill> |
| Qwen | .qwen/skills/<skill> |
~/.qwen/skills/<skill> |
| OpenAI Codex | .codex/skills/<skill> |
~/.codex/skills/<skill> |
| OpenCode | .agents/skills/<skill> |
~/.agents/skills/<skill> |
| Gemini CLI | .agents/skills/<skill> |
~/.agents/skills/<skill> |
| amp | .agents/skills/<skill> |
~/.agents/skills/<skill> |
| Warp | .agents/skills/<skill> |
~/.agents/skills/<skill> |
Many harnesses have converged on .agents/skills/ as a shared convention. When selecting harnesses during skillctl link, you'll see a "Most harnesses (.agents/)" shortcut at the top of the list — this targets the .agents/skills/ directory and covers Gemini, amp, Warp, OpenCode, and any other tool that follows the same convention. OpenAI Codex is separate and targets .codex/skills/.
Clone a remote skill repo or register a local one. Accepts HTTPS or SSH URLs.
# Public repo over HTTPS
skillctl install https://github.com/example/my-skills
# Your own private repo over SSH — edits you push are reflected everywhere immediately
skillctl install git@github.com:you/my-skills
# Local directory (useful during development)
skillctl install ~/dev/my-skillsThe repo must contain a skills/ directory at its root. Each subdirectory inside skills/ is treated as one skill.
Change the directory where skill repos are cloned or copied. By default, skillctl uses ~/.config/skillctl/repos. The configured path is stored in ~/.config/skillctl/config.toml.
If repos are already installed in the current active clone directory, skillctl will ask whether you want to migrate them. Successful migrations move the repo, tear down tracked symlinks for that repo, and re-link them to the new location. Repos that fail migration are added to ~/.config/skillctl/orphans for visibility.
# Change the active clone location
skillctl config set clone-at ~/Library/Mobile\ Documents/com~apple~CloudDocs/skillctl/repos
# Change the active clone location and request migration up front
skillctl config set clone-at ~/Dropbox/skillctl/repos --migrateIf you choose not to migrate, the new path becomes active immediately and the old repos are left in place as unmanaged leftovers.
Show the current config values, including the active clone location.
skillctl config listChange where skillctl stores its managed state. config keeps state in ~/.config/skillctl. clone-at stores it under <clone-at>/.skillctl-state/.
# Keep managed state in ~/.config/skillctl
skillctl config set state-dir config
# Store managed state alongside the cloned repos
skillctl config set state-dir clone-atWhen switching modes, skillctl moves the existing managed state files to the new location.
By default, skillctl stores its managed state in ~/.config/skillctl. If you want to sync tracked links and imports across multiple machines that share the same repo layout and filesystem paths, switch state-dir to clone-at so state lives inside the configured repo root instead.
When state-dir = "clone-at", skillctl stores its managed state in <clone-at>/.skillctl-state/ instead of ~/.config/skillctl/.
Symlink one or more skills into one or more harnesses. Without flags, links into the current directory. --global links into $HOME.
# Interactive: pick skills and harnesses via gum
skillctl link
# Link a specific skill globally
skillctl link my-skills/my-skill --global
# Link a skill into a specific project
skillctl link my-skills/my-skill --project ~/dev/myprojectIf multiple harnesses share the same target path (e.g. several use .agents/skills/), the symlink is only created once.
Use --import when you need a detached copy of a skill rather than a live symlink. The skill's files are copied directly into the target directory.
# Import a skill globally (copy, not symlink)
skillctl link my-skills/my-skill --import --global
# Import into a specific project
skillctl link my-skills/my-skill --import --project ~/dev/myproject
# Overwrite an existing imported target after confirmation
skillctl link my-skills/my-skill --import --force --project ~/dev/myproject
# Overwrite an existing imported target without prompting
skillctl link my-skills/my-skill --import --force --yes --project ~/dev/myprojectIf an import target already exists, skillctl link --import will leave it in place unless you add --force (or -f). With --force, skillctl shows one confirmation for the whole batch and lists every destination that will be overwritten. That overwrite applies whether or not the existing path is tracked by skillctl. Add --yes (or -y) to skip the prompt.
Managed symlinks and imported copies are tracked in skillctl's active state directory. By default this is ~/.config/skillctl/managed; with state-dir = "clone-at", it becomes <clone-at>/.skillctl-state/managed. Imported copies are not updated automatically when the source repo changes — skillctl update will warn you about stale imports and show you how to refresh them by re-running the link command with --import --force.
Remove symlinks and imported copies. Without a skill argument, shows all currently linked or imported skills to choose from.
# Interactive: pick linked/imported skills to remove
skillctl unlink
# Remove a specific skill's global symlinks
skillctl unlink my-skill --globalWhen removing an imported copy (as opposed to a symlink), you'll be warned that any local changes will be lost and prompted to confirm before deletion.
Clear a whole target location rather than removing skills one by one. By default, wipe removes tracked symlinks under the current directory's harness skill folders.
# Remove all tracked symlinks under the current project
skillctl wipe
# Remove all tracked symlinks under a specific project
skillctl wipe ~/dev/myproject
# Remove all tracked global symlinks
skillctl wipe --global
# Also remove tracked imported copies
skillctl wipe --imports
# Remove every tracked symlink everywhere
skillctl wipe --allwipe only removes files and directories explicitly tracked by skillctl. Unmanaged directories are ignored, even if they live under a harness skills folder. With --imports, wipe also removes tracked imported copies after an additional warning that local changes will be permanently deleted.
--all ignores the target directory and wipes every tracked symlink in the managed state file. Combined with --imports, it also removes every tracked imported copy.
Recreate tracked symlinks or imported copies that are missing but still present in the managed state file. This is mainly useful when you share state across multiple installations and want a machine to materialize links that another machine already recorded.
# Recreate missing tracked links under the current project
skillctl sync-state
# Recreate missing tracked global links
skillctl sync-state --global
# Recreate missing tracked imports too
skillctl sync-state --imports --all
# Overwrite conflicting existing targets after confirmation
skillctl sync-state --all --force
# Overwrite conflicting existing targets without prompting
skillctl sync-state --all --force --yessync-state only creates missing tracked targets by default. If a path already exists but does not match the tracked state, skillctl leaves it alone and reports it as a skipped conflict. Add --force (or -f) to overwrite those existing paths after one confirmation prompt for the whole batch. Add --yes (or -y) with --force to skip that prompt.
git pull all installed repos in the currently active clone directory. Because skills are symlinked, every project that references them picks up the changes with no further action.
skillctl updateRemove a repo and all symlinks pointing into it across your home directory.
skillctl uninstall my-skillsShow all installed repos from the active clone directory, their skills, and where each skill is currently linked. If migration left any orphaned repos behind, they are shown in a separate Orphans section.
skillctl listskillctl's default mode symlinks skills rather than copying them. Every harness directory that "has" a skill is actually pointing at a single source of truth — the cloned repo under the active clone-at directory. By default, that is ~/.config/skillctl/repos/. This means:
- Edit a skill once, every project sees it immediately.
- Use SSH URLs for your own repos:
git pushand the skill updates everywhere, no re-import needed. - Remove or update a repo with one command instead of hunting down copies across projects.
Use --import when you specifically need a detached, independent copy — for example, when a project needs to diverge from the canonical version of a skill.