Organize AI-agent skills into named groups and mount them per project — the dotfiles of the AI skill world
中文 ·
Collect the skills you actually use into named groups (e.g. python, web, sql), declare which groups a project needs, and let one command mount the right skills into the right agent directory. Zero dependencies, works on Python 3.9+.
Skill libraries grow fast. When your global store has 160 skills and every new project needs a different subset, you stop re-picking them by hand and start asking "which project already solved this?"
skill-groups gives you three layers instead of one pile:
| Layer | Where it lives | What it holds |
|---|---|---|
| Global | SG_HOME (default ~/.sg) |
every skill you've ever collected, cached by content |
| Group | SG_HOME/groups/<name>.json |
a named, reusable bundle of skills with sources |
| Project | .sg.json + a skills dir |
which groups this project mounts, and where |
Projects stay portable because a project declares groups, not raw paths. The skills dir that gets mounted (.agents/skills, .claude/skills, ...) is a real agent-recognized location, so there is zero per-agent configuration: the agent already scans that directory. The group definitions and the cache live outside the project, so the project itself stays clean, and the mounts are re-creatable with one command.
Each agent already reads a conventional skills directory inside the project root. skill-groups just mounts skills into that directory, so it works with any agent that scans these paths.
| Agent | Skills directory |
|---|---|
| Standard (Cursor, Gemini CLI, Copilot CLI, others) | .agents/skills |
| Claude Code | .claude/skills |
| Codex | .codex/skills |
| OpenCode | .opencode/skills |
Pick the target with sg init --agent claude (choices: agents, claude, codex, opencode; default agents). --agent is repeatable — a project can mount into several agents at once: sg init --agent claude --agent opencode serves both .claude/skills and .opencode/skills from one declaration.
Claude Code users: you MUST run
sg init --agent claude— Claude Code does not scan.agents/skillsyet (tracked in anthropics/claude-code#16345); the default would silently load nothing. And after mounting, fully restart your agent — skills are registered at process start; a new session inside a long-running agent (e.g./newin OpenCode) does not re-scan.
The package is a zero-dependency Python 3.9+ CLI named sg.
pip install skill-groupsAfter install, sg is on your PATH and sg --help works from any directory.
Developers: from a clone of this repo, pip install -e . installs in editable mode (the build backend is hatchling; offline use pip install -e . --no-build-isolation).
Zero-install alternative: you don't have to install anything. From the repo root, python -m sg runs the same CLI directly, because the sg package is importable from the current directory. Every example below works with either sg ... or python -m sg ....
Run these in a new project directory. Expected output is shown below each command.
# 1. turn the current directory into a skill-groups project
sg init
# initialized C:\path\to\projectThis writes .sg.json (schema v2) and adds the agent skills dir (.agents/skills by default) to your .gitignore. Use sg init --agent claude --agent opencode to serve several agents from one project; sg init --force --agent codex retargets an existing project (groups and standalone skills are kept).
# 2. create a group
sg group create python --description "Python tooling"
# created group python# 3. add skills to the group from local folders
sg group add python lint --type local --path C:\path\to\lint-skill
# added lint to group python
sg group add python format --type local --path C:\path\to\format-skill
# added format to group python# 4. mount every skill in the group into this project
sg use python
# used: python# 4b. mount a standalone skill too (no group required)
sg use python --skill git-commit-writer --path "C:\path\to\git-commit-writer"
# used: python + git-commit-writer--skill/--path can be repeated (they must come in pairs). Standalone skills are recorded in .sg.json under skills (group is ungrouped in sg ls) and are fully covered by sg status/sg sync/sg unuse --skill <id>.
# 5. see what's mounted
sg ls
# format (python, local)
# lint (python, local)# 6. check status while the skills are mounted
sg status
# format (python): ok
# lint (python): ok# 7. drop the group again (unmounts its skills)
sg unuse python
# unused: python# 8. repair mounts to match the declaration
sg sync
# format: unchanged
# lint: unchanged# 9. refresh git-sourced skills to their latest upstream
sg update
# lint: unchanged
# pytest: updatedsg status prints one line per mounted skill (skill (group): state, where state is ok, missing-link, drift, conflict, or stale), and sg sync repairs mounts to match the declaration (actions: unchanged, remounted, relinked, renewed, removed, skipped). Both exit 0; sg status --check exits 1 as soon as any skill is not ok — the hook for CI and pre-commit compliance checks. sg update re-fetches every git-source skill (branch/tag/default-branch revs; pinned commit shas and local sources are left alone), reporting updated when the resolved sha moved, and rewrites sg.lock accordingly.
A group is a JSON file at <SG_HOME>/groups/<name>.json. You can build it with sg group add, or write it by hand:
{
"name": "python",
"description": "Python tooling",
"skills": [
{
"id": "lint",
"source": { "type": "local", "path": "C:/skills/lint" }
},
{
"id": "pytest",
"source": {
"type": "git",
"repo": "owner/repo",
"path": "skills/pytest",
"rev": "main"
}
}
]
}Rules enforced on every read:
nameis a non-empty string and may not contain/or\.- Skill
idmust be unique within the group. localsources require a stringpath;gitsources require a stringrepo(pathandrevare optional).
Inspect your groups with sg group list and sg group show python. Remove a group (its definition file) with sg group rm python — note that projects whose .sg.json still declares the removed group will report unknown group on the next sg use/sg sync.
Every skill is fetched once into a content-addressed cache, then mounted into the project. The flow is: group → source → cache → mount.
- Cache layout:
<SG_HOME>/cache/<key>/<skill_id>/ <key>is a 10-hex sha256 of the source identity. For git sources the requestedrevis part of the identity, so asking for a new rev gets a fresh cache directory and a stale cache can never be served for the wrong rev.- Local sources are copied on first fetch (gated by the presence of
SKILL.md, so re-running is a no-op). Git sources are shallow-cloned once into<skill_dir>/.repoand reused on later calls.
Mounting happens in one of three modes, set with sg init --mode ...:
| Mode | Behavior |
|---|---|
auto (default) |
Windows: junction, falling back to symlink, then copy. Other platforms: symlink, falling back to copy. |
symlink |
Always a directory symlink. |
copy |
Always a full copy. |
When auto falls back to copy, it prints a warning to stderr (warning: fell back to copy for <skill_id>). Prefer junction/symlink over copy: copies go stale when the cached skill changes, and sg unuse has to delete real files instead of one link.
sg use is all-or-nothing. Before any mount it resolves every source and pre-flights every target link; if the same skill id is declared by two groups with different sources, or a target path already exists with different content, the whole batch aborts and nothing is changed.
- Junctions need no admin rights.
mklink /Jworks in a normal user session, which is whyautotries junctions first on Windows. - Symlinks need Developer Mode (Settings → Privacy & Security → For developers → Developer Mode) or an elevated shell. If a symlink can't be created,
autosilently falls back to a copy. - Paths with spaces are safe. All git and mount commands use subprocess list form, never shell string concatenation, so a repo path like
C:\My Skills\repoworks. - Unmounting never touches the target: junctions are removed as reparse points (
os.rmdir/cmd /c rmdir), never with a recursive delete.
| Code | Meaning | Example |
|---|---|---|
0 |
Success | any command that completes |
1 |
User error: bad input, conflicts, unknown group | error: group already exists: python |
2 |
Environment error or unexpected failure; also CLI usage errors | error: git not found on PATH, unknown command |
User errors print error: <message> to stderr and leave your state untouched. Environment errors (missing git, unwritable cache, corrupt config) print the same way but signal a setup problem, not an input problem.
sg group add web serve --type git --repo owner/repo --path skills/web --rev v1
# added serve to group webrepoaccepts anowner/reposhorthand (resolved tohttps://github.com/<owner/repo>.git), a full URL, or a local repository path.pathselects a subdirectory of the repo as the skill content; omit it to use the repo root.revaccepts a branch name, a tag, or a full 40-character commit sha. A branch/tag becomes a shallow--branchclone; a sha is fetched and checked out directly.- The lock (
sg.lock) records the actual resolved HEAD inresolved_sha, sosg statuscan later tell you whether the mounted skill matches what the declaration resolved to. sg updaterefreshes branch/tag/default-branch sources in place (fetch first, so a network failure leaves your mounts untouched); a pinned sha is immutable by definition and never refreshed.
sg doctor reports the environment, which is the first stop when a git source won't fetch or a mount fails:
> sg doctor
python: 3.13.3
git: available
sg_home: C:\Users\me\.sg
sg_home_writable: yes
junction: supported
| Line | Healthy | If not |
|---|---|---|
git |
available |
install git and put it on PATH; git sources cannot fetch without it |
sg_home |
your expected home path | set SG_HOME to redirect all global state, e.g. $env:SG_HOME="$env:TEMP\sg-test" in PowerShell |
sg_home_writable |
yes |
fix permissions on SG_HOME; the cache and groups live there |
junction |
supported on Windows |
mounts fall back to symlink/copy automatically |
The default SG_HOME is ~/.sg. Its layout: config.json (global config), groups/ (group definitions), cache/ (fetched skills).
python -m unittest discover tests -vThe suite runs 238 tests covering the CLI surface, group validation, caching, git sources, updating, mounting, multi-agent projects, standalone skills, and isolation.
MIT