Skillpack
2026 · open source · claude code + codex
A skill is a markdown file that tells a coding agent when to act and how. I had the same eight copied into several repos, and each copy drifted. Skillpack is the one folder they live in now. Every repo pulls it as a git subtree, and a hook pushes edits back. Code on GitHub.
Edit where you use it
The plugin route is fine for reading: install once and forget it. But a skill only improves while it is in use, and the plugin copy is not the one in use. Every fix I made landed in one repo and stayed there.
So the subtree is the canonical copy: a normal folder in the consumer repo, edited in place. A hook carries each change upstream before the session ends. The next repo to start a session merges it, no pull request. Both agents read the same files through symlinks, so there is no Claude version and no Codex version.
Two hooks, one script
sync.sh --start runs at SessionStart. It merges what the last background fetch brought in, no network needed, then fetches again in the background. The session never waits on GitHub.
sync.sh --stop runs at Stop. It commits only the skills folder, so a merge never runs over unrelated uncommitted work. Then it merges upstream if it moved, and pushes. State is two hashes in .git/skills-sync: the folder tree last synced and the upstream commit it matched. When they match, the script exits without touching git.
a normal session
$ sync.sh --startskills-sync: merged upstream changes into .claude/shared-skills # ... a session that edits skills/unslop/SKILL.md ... $ sync.sh --stopskills-sync: pushed .claude/shared-skills to upstream
same line edited on both sides
$ sync.sh --stopskills-sync: CONFLICT merging upstream into .claude/shared-skills (in: skills/handoff/SKILL.md). Resolve: git subtree merge --squash ...$ echo $?2
Bootstrapping was the hard part. A folder made by hand has no subtree history, and git subtree push fails one way against an empty upstream and another way against a missing branch. The test script builds a bare upstream in a temp directory, wires three consumer repos to it, and walks fifteen scenarios through them: fresh install, edits from either side, concurrent edits to different files, the same line changed on both sides, an empty upstream, an unreachable one. About twenty seconds, no network.
The catalog is a map
catalog.yaml puts each skill in exactly one category and lists, per category, other people's skills I rate. A script regenerates the README tables from it plus each skill's frontmatter, and exits 1 if a folder on disk is missing from the catalog or the catalog names a folder that does not exist. The README cannot describe a tree that is not there.
Three categories hold none of my skills yet and exist for the links. Deliberate: someone looking for an interview drill should find jobforge, and the coach it derives from, in the same place they found unslop.
- skills
- 8 · one SKILL.md each
- catalog
- 8 categories · 5 hold my skills, all link out
- install paths
- 2 · plugin marketplace, git subtree
- agents
- Claude Code, Codex
- sync
- 157 lines of bash · both directions
- sync tests
- 15 scenarios, sandboxed, no network
- catalog build
- exits 1 when tree and catalog disagree
- status
- MIT · github.com/ong6/skillpack
What stays out
Only generic skills go in. Anything naming a private path, an account or a price stays in the consumer repo beside the subtree. The store this grew out of keeps more of those than it publishes. The eight here survived being pulled out of that context: writing, web extraction, diagrams, a PDF builder, a session handoff, and the feedback loop that patches whichever skill or rule earned the complaint.