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)
.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.
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", "grill-me"] }
= { = 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).
Commands
| 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 [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). - 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.
Design
See docs/spec.md.
License
MIT OR Apache-2.0.