spm — skill package manager
Declare AI skills as git dependencies in ai.json, and spm wires them into your
AI tool (Claude Code and GitHub Copilot CLI) without ever copying skills into
your repo. No symlinks in the project, no fragile .gitignore rules.
How it works
ai.json ──resolve──▶ ai.lock ──fetch──▶ ~/.spm/store/<repo>@<sha> (global cache, one clone per commit)
│
└─project──▶ ~/.spm/vendors/<target>/<project>/ (assembled marketplace)
│
└─register─▶ vendor picks it up (see below)
ai.json— you author it, commit it. Declares target vendors + skill deps.ai.lock— generated, commit it. Pins every version selector to an immutable commit SHA → reproducible installs.- Global store (
~/.spm/store) — each repo@commit fetched once, shared across all projects. - Vendor projection (
~/.spm/vendors) — a self-contained plugin marketplace assembled outside your repo. Both vendors require skills to physically live inside a plugin dir, so spm copies them here, never into your project tree. - Registration differs per vendor:
- Claude — spm writes a pointer to the marketplace into
.claude/settings.local.json(gitignored by convention). Declarative, per-project, zero VCS footprint. - Copilot CLI — spm shells out to
copilot plugin marketplace add+copilot plugin install. Copilot marketplaces/plugins are user-global (no project-local config), so registration is global. spm names the registration by a stable, path-independent project id stored inai.lock(spm-xxxxxxxx), so a moved or re-cloned checkout re-registers the same entry instead of leaving a duplicate. Orphaned registrations (whose local dir no longer exists) are pruned automatically on eachspm install/clean. Requires thecopilotCLI on PATH.
- Claude — spm writes a pointer to the marketplace into
On a fresh clone, teammates run spm install — it rebuilds their own store and re-registers from ai.lock. Same model as node_modules.
ai.json
targets lists one or more vendors (claude, copilot) — skills resolve once
and project into each independently.
Schema & validation
ai.json is described by a JSON Schema at schema/ai.schema.json
(draft-07). spm embeds it and validates every ai.json on load, reporting all
violations at once with their JSON path:
error: in ai.json: ai.json does not match schema:
at /skills/x: {"git":"u"} is not valid under any of the schemas listed in the 'oneOf' keyword
Add a "$schema" reference for editor autocompletion/validation:
Version selectors (exactly one per skill):
| field | meaning | locked to |
|---|---|---|
tag |
git tag (annotated tags deref to commit) | resolved SHA |
branch |
branch tip at install/update time | resolved SHA |
commit |
exact commit | itself |
path (optional) selects a subdirectory — for monorepos holding many skills.
Repo URLs (HTTPS & SSH)
git accepts any URL the system git understands:
SSH auth goes through your ssh-agent / keys — spm never handles credentials.
Private HTTPS repos use your git credential helper. spm runs git with
GIT_TERMINAL_PROMPT=0, so a missing credential fails with a clear error
instead of hanging on a prompt (helpers and ssh-agent still work).
Installation
spm ships as a single self-contained binary (needs the system git on PATH
at runtime, plus the copilot CLI if you target copilot).
Prebuilt binary — download the asset for your platform from the
latest release and put it on
your PATH:
# example: Apple Silicon macOS
&&
Assets: spm-x86_64-unknown-linux-gnu, spm-aarch64-unknown-linux-gnu,
spm-x86_64-apple-darwin, spm-aarch64-apple-darwin,
spm-x86_64-pc-windows-msvc.exe.
From crates.io (the crate is spm-cli; it installs a binary named spm):
From source:
&&
# or: cargo install --path .
Commands
||) \ # add + install a skill
Design notes
- Cross-OS: shells out to the system
git(no libgit2 build deps); no symlinks; all paths viastd::path. Runs on Linux, macOS, Windows. SPM_HOMEoverrides the store/vendor root (default~/.spm) — used by tests.- Vendor adapters: adding a target means implementing one
Vendortrait (src/vendor/). Bothclaudeandcopilotassemble the same plugin-marketplace layout (marketplace.json→plugin.json→skills/<name>/SKILL.md); they differ only in how the marketplace is registered.
Development
make check runs the full CI gate locally (fmt-check + clippy + test).
A pre-commit hook (fmt + clippy) installs itself automatically via
cargo-husky — just run cargo test
(or cargo build) once after cloning and the hook lands in .git/hooks. The
hook source lives in .cargo-husky/hooks/. Bypass a
single commit with git commit --no-verify.