Declaratively manage agent skills.
skm installs the files of a declared exact version of a skill into the
correct location for an agent (Claude Code or Codex), and guarantees
reproducibility: the same skm.lock, on any machine, via
skm sync --frozen, produces skill directories with byte-for-byte identical
content hashes.
It does two things and only two things:
- Place the declared content at the right place.
- Make that placement reproducible.
It deliberately does not parse SKILL.md semantics, perform security
scanning, or manage plugins / hooks / settings.
- Dual manifest model (pnpm-style): a per-project
skm.tomland a global~/.config/skm/skm.toml, selected with-g/--global. - Four source types:
git,tar,zip, andlocal. - Content lock, not dependency lock:
skm.lockrecords the SHA-256 of the installed file tree;--frozen/--lockedgate only on that. - Multi-agent deployment: one skill can deploy to both
claude,codexand more; each lands atomically and independently. - Safe by default: skm only touches skills it installed itself. Foreign (manually placed) directories are never auto-deleted.
- Content-addressed cache under
~/.cache/skm/for fast, offline-capable re-installs.
Platform support: macOS and Linux (including WSL). Native Windows is blocked at compile time; run skm inside WSL.
Requires a Rust toolchain (1.85+, edition 2024) and the system git.
# From a checkout:
cargo install --path .
# Or build a release binary:
cargo build --release # → target/release/skm# 1. Create a manifest in your project, then edit skm.toml:
# uncomment `agents = […]` under [defaults] and pick your agent(s).
# (Or skip this and pass --agent on each `skm add`.)
skm init
# 2. Add skills. Specs are auto-detected: owner/repo → GitHub, ./path →
# local, URL suffix → tar/zip, otherwise git. Each command edits the
# manifest, resolves, locks, and deploys in one step.
skm add anthropics/skills --subdir docx # GitHub owner/repo + subdirectory
skm add anthropics/skills@v1.2 # pin with an inline @ref
skm add ./vendor/reviewer # local directory
skm add foo.tar.gz --sha256 <hex> # archive, byte-verified
# 3. Remove a skill from the manifest (the on-disk copy is pruned on the
# next sync; pass --no-sync to defer both the lock update and deploy).
skm rm docx
# 4. See what is installed.
skm status
# 5. Reproduce exactly elsewhere (CI):
skm sync --frozenFlat, Cargo-style. Exactly one of git / tar / zip / local per skill.
version = 1 # required
[defaults]
agents = ["agents"] # default agents; each skill may override
# (left unset by `skm init` — choose one or more, or pass --agent on each add)
[[skills]]
name = "docx" # install directory name (unique)
git = "https://github.com/anthropics/skills"
ref = "main" # optional: branch / tag / 40-hex commit
subdir = "docx" # optional: subdirectory inside the source
[[skills]]
name = "my-tool"
zip = "https://example.com/skills/my-tool.zip"
sha256 = "a1b2c3…" # optional: verifies the downloaded bytes
subdir = "my-tool"
agents = ["agents", "claude"]
[[skills]]
name = "team-reviewer"
local = "./vendor/reviewer" # relative to this skm.tomlSkills install under:
| scope | agents | claude | codex |
|---|---|---|---|
global (-g) |
~/.agents/skills/ |
~/.claude/skills/ |
~/.codex/skills/ |
| project (default) | <project>/.agents/skills/ |
<project>/.claude/skills/ |
<project>/.codex/skills/ |
| command | description |
|---|---|
skm init [--force] |
Write a commented skm.toml template. |
skm lock [--upgrade [<name>…]] |
Resolve the manifest → write skm.lock (no deploy). |
skm sync [--frozen] [--locked] [--dry-run] [--offline] [--prune] [--no-prune] [--yes] |
The core convergence command: lock new/changed, deploy, prune. |
skm add <spec> [opts] (a) |
Edit manifest → lock → sync. --no-sync defers deploy. |
skm update [<name>…] [--dry-run] [--offline] [--no-prune] [--yes] (up, upgrade) |
Re-resolve mutable refs and deploy (lock --upgrade + sync). |
skm remove <name>… (rm) |
Remove from the manifest; prune on the next sync. |
skm status (st) |
Show the state of every (skill, agent). |
skm doctor |
Diagnose the environment, cache, and exec-bit integrity. |
skm cache <dir|clean> |
Inspect or clear the global source cache. |
skm update re-resolves mutable refs (a git branch/tag, or an archive URL
without a pinned sha256) and deploys the result in one step — the convenience
equivalent of skm lock --upgrade followed by skm sync. Bare skm update
upgrades every skill; pass names to narrow it (skm update docx my-tool).
Immutable pins (a 40-hex git commit, or a declared archive sha256) are no-ops,
so an update never moves a version you deliberately froze.
- default — lock new/changed items, deploy, and prune managed extras (with confirmation).
--frozen— content is fully determined by the lock; do not re-resolve.agentsis still read from the manifest. A cache miss still hits the network unless--offlineis given.--locked— assertlock ≡ manifest, then deploy. Does not write the lock. The CI gate.--dry-run— print the change plan; never write the lock or skills.--offline— never touch the network; use only the local cache.
| code | meaning |
|---|---|
| 0 | success, no change |
| 1 | general error |
| 2 | drift reported by status / doctor |
| 3 | lock mismatch under --locked/--frozen |
| 4 | network error |
| 5 | permission / IO error |
| 6 | lock missing under --frozen/--locked |
| 10 | --dry-run detected pending changes |
A skill directory on disk is owned by skm if and only if its name appears
in the skm.lock that owns that root. Owned-but-undeclared directories are
managed extras and are pruned by default. Directories skm never installed
are foreign and are never auto-deleted. local sources are mutable path
dependencies (re-read every sync); for absolute reproducibility, use git or
tar.
- git uses your system git credential helper / SSH agent. All remote git
calls run with
GIT_TERMINAL_PROMPT=0, so a missing credential fails fast (exit 4) instead of hanging on a prompt — important in CI. For private repos, configure a non-interactive helper first:gh auth setup-git, the system keychain, orGIT_ASKPASS. - zip / tar downloads do not support HTTP authentication. For an
authenticated private archive, use a
gitsource, or pre-download it and point alocalsource at it.
skm sync --locked # assert lock ≡ manifest, then deploy
skm sync --frozen # content fully from the lock; ignore manifest source edits
skm sync --frozen --offline # air-gapped: never touch the network--lockedis the gate: exit 3 if the manifest and lock disagree, exit 6 if the lock is missing — so commit yourskm.lock.- Pruning needs a TTY to confirm; in CI pass
--yes(or--no-prune), otherwise a prune-needed sync errors.
- Lost
skm.lock— run a plainskm sync: it re-resolves, rewrites the lock, and re-claims on-disk directories whose content matches.--locked/--frozeninstead fail loudly (they cannot assert ownership without a lock). If a directory on disk differs from what would be installed, sync aborts rather than overwrite it — resolve by hand (rm -r <dir>) and re-run. - Leftover
*.skm-new.*/*.skm-old.*(crash mid-deploy) — the nextskm syncrolls back or cleans them automatically, andskm doctorreports them. In the rare case skm cannot auto-roll-back, it prints the exactrm -rf <final> && mv <old> <final>command to restore the previous version. - Reclaim disk space —
skm cache cleanclears the global source cache. Run it when no sync is in progress (it refuses while one is actively downloading).
cargo build # debug build
cargo test # unit + integration tests (no external network)
cargo fmt --all # format
cargo clippy --all-targets -- -D warnings # lint (matches CI)Integration tests build local git repositories and serve zip/tar archives from a throwaway in-process HTTP server, so the whole suite runs offline.
Environment overrides (handy for CI / sandboxes):
SKM_CONFIG_DIR— overrides~/.config/skm/.SKM_CACHE_DIR— overrides~/.cache/skm/.
See CLAUDE.md for architecture, invariants, and design rationale.
Behavior is specified by the black-box suite in tests/.