skillcask
A lockfile-first package manager for AI agent skills: directories with a SKILL.md, read by
Claude Code, Codex, Junie and others.
skillcask installs skills the way Cargo or Composer install libraries. It reads a manifest with version constraints and commits a lock with content hashes. In CI it does frozen installs that fail on drift, and it links every skill into each agent's skill directory. It ships as a single static binary with no runtime, so it works in a repository in any language.
Skills come from Composer repositories or straight from GitHub. Packagist serves public skill
packages; GitLab's Composer registry, Satis, Private Packagist or the static repository built by
skillcask publish serve private ones. Any GitHub repository with SKILL.md files works too,
which covers most of what skills.sh lists. skillcask does not need PHP,
Composer or Node.
Install
macOS, Linux
# or, without Homebrew:
|
Windows (PowerShell)
powershell -ExecutionPolicy Bypass -c "irm https://github.com/puniserv/skillcask/releases/latest/download/skillcask-installer.ps1 | iex"
The two installer scripts download the binary for your platform from the latest release and put
it in ~/.cargo/bin (%USERPROFILE%\.cargo\bin on Windows).
npm, bun
The npm package is a thin wrapper that downloads the release binary for your platform from GitHub. It does not check a checksum itself; where that matters, use Homebrew or a release archive and verify it.
Cargo
Release archives for macOS, Linux and Windows are on the releases page, with sha256 checksums and GitHub build provenance attestations:
Use
add creates:
skills.toml # manifest, commit it
skills.lock # lock, commit it
.agents/skills/<name>/ # installed skill (gitignored)
.agents/skills/.skillcask-installed # what this machine installed (gitignored)
.claude/skills/<name> -> ../../.agents/skills/<name>
.junie/skills/<name> -> ../../.agents/skills/<name>
.gitignore # managed block
Project skills live in .skills/<name>/SKILL.md ([agents] local). skillcask links each one
into the store (.agents/skills/<name> -> ../../.skills/<name>) and from there into every agent
directory. These links are committed, so they work in every clone. A directory written by hand
straight into .agents/skills/ is linked the same way. A project skill with the same name as a
locked one is an error. When a skill with the same name exists in your home (~/.claude/skills,
~/.agents/skills, …), install and doctor print a warning: Claude Code runs the personal one,
Codex lists both.
skills.toml
[]
# Packagist is the implicit last repository.
= { = "composer", = "https://git.example.com/api/v4/group/42/-/packages/composer/packages.json" }
[]
= "^3.34"
= { = "^1.0", = "acme" }
= { = "1.4.2", = "acme", = ["review"] }
= { = true, = "^1.3", = ["tdd", "code-review"], = { = "mp-code-review" } }
= { = true, = ["pdf"] } # default branch
[]
= [".claude/skills", ".junie/skills"]
= ".skills" # project skills
[]
= "3d" # cooldown for add/update; "0" disables
= true # false: every version must be exact
= true # false: drop the implicit Packagist repository
Constraints follow Composer semantics: ^0.3 means >=0.3.0 <0.4.0, ~1.2 means
>=1.2 <2.0, and pre-releases are only selected when the constraint names a stability
(^1.0@beta).
Name conflicts
Skills have no namespaces: an agent knows a skill by its name. Two skills with the same name in
one project are an error. Install one of them under another name with rename:
= { = true, = { = "mp-code-review" } }
skillcask then installs .agents/skills/mp-code-review and changes the name: line in its
SKILL.md; the lock records the original name and the hash of the renamed copy. Nothing else in
the skill changes, so text that mentions the old name (/code-review) stays as it is.
add, install and update offer the rename when they hit a conflict in a terminal.
Personal skills (--global)
skillcask --global (-g) manages skills for your user instead of a project, with the same
manifest, lock and verification:
skills.toml,skills.lockand an optionalauth.jsonlive in~/.config/skillcask($XDG_CONFIG_HOME/skillcask;SKILLCASK_HOMEoverrides both). Keep them in your dotfiles.- Skills go to
~/.agents/skills(read by Codex), with links in~/.claude/skillsand~/.junie/skills([agents] targets, relative to home). Links are absolute, so a symlinked~/.claudeworks.[agents] localdoes not apply. - Personal skills you wrote by hand stay where they are and are not linked anywhere.
- The list of managed paths is
~/.agents/skills/.skillcask-installed, local to the machine; nothing touches git.
Commands
Every command except publish takes -g, --global.
| Command | |
|---|---|
init |
Create skills.toml. |
add <vendor/name>[@constraint] [--repository r] |
Resolve, lock and install. |
add github:<owner/repo>[/skill][@version-or-ref] |
The same from a GitHub repository, see below. |
remove <vendor/name>… |
Drop from manifest and lock; delete files, links and gitignore lines. |
install [--frozen] |
Install the lock. --frozen (implied by CI) fails when the lock is out of date. |
update [vendor/name…] |
Re-resolve within constraints. |
outdated |
Newer versions, and when cooled-down ones become eligible. |
status |
Exit 1 when manifest, lock or installed files disagree. |
doctor [--fix] |
Broken or committed links, stale gitignore block, modified skills. |
search <query> |
Packagist (type=ai-agent-skill) and listed private packages. |
publish --vendor v --base-url URL --out DIR [--inline] [--dist-url TPL] [paths…] |
Build a static Composer repository from a monorepo. |
<other> |
Runs skillcask-<other> from PATH, like git and cargo plugins. |
Exit codes: 0 ok, 1 manifest/lock/status problems, 2 authentication, 3 network, 4 hash mismatch, 5 IO or archive error.
Skills from GitHub and skills.sh
skills.sh lists skills as owner/repo/skill, for example
mattpocock/skills/grill-me. Put github: in front and add it:
All skills of one repository share one entry in skills.toml:
= { = true, = "^1.3", = ["grill-me", "tdd"] }
Which version you get:
- Repository with version tags (
v1.3.1):addwritesversion = "^<major.minor>", like for Composer packages, andupdatemoves within it. The release time of a tag is its commit date. - Repository without tags: the entry follows the default branch;
ref = "main"names a branch, tag or commit instead. The lock pins the commit sha, soinstallkeeps installing that commit until you runupdate. @picks explicitly:github:owner/repo@^2.0(version tags),github:owner/repo@mainor@<commit sha>(ref).- With
policy.allow-ranges = falsea branch counts as a range:addwrites the full commit sha asref, and a hand-written branch name is rejected.
Skills are found by their SKILL.md anywhere in the repository, except inside another skill.
The install name is the frontmatter name; when that is not a valid name (Jira Automation), the
directory name is used. skills = [...] takes these names.
GitHub allows 60 anonymous API requests per hour. add, update and outdated use a few per
entry; install from the lock uses none, it downloads the pinned archive. Set GITHUB_TOKEN
(GitHub Actions has one) or Composer's "github-oauth": {"github.com": "<token>"} in auth.json
for a higher limit and for private repositories. SKILLCASK_GITHUB_API points at GitHub
Enterprise (https://ghe.example.com/api/v3).
skills.sh itself is not queried: its API needs a Vercel token. Search on the website, then add what you picked.
Security
- The lock pins the version, the commit reference, the sha256 of the downloaded archive and
the tree hash of every installed skill.
installverifies both hashes, so what gets installed is what was reviewed. - HTTPS only, verified against the system trust store, so corporate CAs work. Verification
cannot be turned off;
SKILLCASK_CA_FILEreplaces the trust store with a PEM bundle. - Archives with absolute paths,
.., devices or symlinks that leave their skill are rejected. No symlink is ever written from a package. Size and file count are capped (--max-download,--max-unpacked,--max-files). - File names must be valid on every OS: Windows-reserved names,
<>:"|?*\, control characters and paths that collide by case or Unicode form fail a skill that contains them, and are skipped outside the selected skills. - No package code runs, ever. No telemetry.
min-release-agefor GitHub sources reads commit dates, which the committer sets. A backdated commit passes the cooldown; reviewing whatadd/updatechange inskills.lockis the real check there.- Credentials come from Composer's
auth.json/COMPOSER_AUTH(http-basic,bearer,gitlab-token,gitlab-oauth) and are never printed. In GitLab CI usehttp-basicwith the usergitlab-ci-tokenand the password$CI_JOB_TOKEN.
Publishing skills
One repository per skill: a composer.json with "type": "ai-agent-skill" (the
netresearch convention) and semver
tags. Packagist or GitLab's Composer registry picks up the tags; skillcask plays no part.
Monorepo: give each SKILL.md a version in its frontmatter and run
This writes packages.json, p2/acme/<name>.json and deterministic zips under dist/. Published
versions are immutable, and re-running on unchanged skills changes no file. Serve the directory
over HTTPS.
Hosts that cannot serve / inside file names (GitLab's Generic Package Registry) take a single
file instead:
--inline puts every package into packages.json and writes no p2/. Upload each
dist/<vendor>/<name>/<version>.zip to the URL the template gives, and commit packages.json to
a repository. A repository url with a query string is used as given, so clients can read the
file through the GitLab raw files API
(.../repository/files/composer%2Fpackages.json/raw?ref=main) with a gitlab-token.
Design
See docs/specs/2026-10-19-skillcask-v1.md.
License
MIT OR Apache-2.0.