skill-groups
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+.
Why
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.
Supported agents
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.
Install
The package is a zero-dependency Python 3.9+ CLI named sg.
pip install skill-groups
After 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 ....
Quickstart
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\project
This 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: updated
sg 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.
Group definitions (schema v1)
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.
Cache and mount mechanism
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.
Windows notes
- 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.
Exit codes
| 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.
Git sources
sg group add web serve --type git --repo owner/repo --path skills/web --rev v1
# added serve to group web
repoaccepts 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.
Troubleshooting: sg doctor
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).
Development
python -m unittest discover tests -v
The suite runs 236 tests covering the CLI surface, group validation, caching, git sources, updating, mounting, multi-agent projects, standalone skills, and isolation.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file skill_groups-0.2.0.tar.gz.
File metadata
- Download URL: skill_groups-0.2.0.tar.gz
- Upload date:
- Size: 45.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
07167c696015efdf7dd05faf773591af8317be1abcef8a7db61dfae2dabd1b78
|
|
| MD5 |
7ff640e1150187b6ba9f1da22be15976
|
|
| BLAKE2b-256 |
7d08f9373a6f24e152a1b97f483c39d6139dbe80419107543830a5a6f83a2fda
|
File details
Details for the file skill_groups-0.2.0-py3-none-any.whl.
File metadata
- Download URL: skill_groups-0.2.0-py3-none-any.whl
- Upload date:
- Size: 30.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
acde37b0381f521d8f6b6df6c40b9bf4de4b9730f92ede21f0687f8a9df99e55
|
|
| MD5 |
0fc9ecc97d562ccde980d44f1b0497b0
|
|
| BLAKE2b-256 |
75a127cac1f2cfee9e06babbbaa9fc41b7a09acab036fb945e7807b2a7a3da50
|