Package manager, lockfile, and bundler for Claude Code / Cursor / AGENT.md agent skills. Resolve dependencies. Pin sha256 hashes. Bundle and sign. Single Go binary, zero runtime deps.
skillpack init
skillpack add ./skills
skillpack install # writes skillpack.lock
skillpack verify # CI: exit 1 if anything drifted
skillpack bundle # produces a deterministic *.skl tarball
skillpack sign --key priv.key mypack.sklThe agent-skill ecosystem is exploding. In the last six months, vendors and the community have shipped:
- Anthropic SKILL.md — the canonical Claude Code skill format with YAML frontmatter
- Cursor
.cursorrules— Cursor IDE rule files driving the editor's inline assistant - AGENT.md — the cross-vendor proposal for a portable agent manifest (used by opencli, googleworkspace/cli, and others)
skill.yaml— pure-YAML manifests for tooling-first skill catalogs
There are at least six trending GitHub repositories with combined stars above 100,000 that cluster around this wedge:
googleworkspace/cli(22K stars) — "40+ agent skills included"antigravity-awesome-skills(28K stars) — curated catalog of skillsawesome-claude-code(33K stars) — the canonical "list of skills" repoopencli(8K stars, 632 stars/day) — runtime dispatcher reading AGENT.mdclaude-code-ultimate-guide— docs site for the ecosystemawesome-openclaw-agents— community-curated agent registry
And yet — zero of these repositories solve the lifecycle problem. They all answer "what skills exist?" but none answer:
- How do I pin the exact version of a skill across machines?
- How do I detect when a skill on disk has drifted from what my CI expects?
- How do I bundle 20 skills into a single distributable artifact?
- How do I sign that artifact so my team can trust it?
- How do I declare that skill A depends on skill B at version
^1.2.0?
opencli is a runtime dispatcher (different layer). googleworkspace/cli
ships its own bundle (vendor-locked). awesome-claude-code is a hand-curated
markdown list (no tooling). The lifecycle hole is wide open, and skillpack
fills it.
skillpack is the npm/cargo/pip of agent skills — except it works across every vendor format from day one.
| Layer | What it does |
|---|---|
| Parse | Read SKILL.md, .cursorrules, AGENT.md, and skill.yaml; normalize all four into a single canonical record |
| Resolve | Honor requires: semver constraints between skills; produce a deterministic install order via topological sort |
| Hash | Compute a content-addressed sha256 fingerprint per skill (LF-normalized, frontmatter-sorted, key-canonicalized) |
| Lock | Write skillpack.lock — a deterministic JSON file with no timestamps and stable ordering |
| Bundle | Produce a *.skl tarball (gzipped, fixed mtime, sorted file order, PAX format) — byte-identical across two runs |
| Sign | Detached ed25519 signatures over the bundle bytes |
| Verify | CI mode that exits non-zero on hash drift, version drift, or missing skills |
Every byte of output is deterministic. Two machines parsing the same source files produce byte-identical lockfiles and tarballs. This is what makes skillpack a real package manager and not a glorified zip script.
go install github.com/JSLEEKR/skillpack/cmd/skillpack@latestgit clone https://github.com/JSLEEKR/skillpack.git
cd skillpack
go build -ldflags="-s -w" -o skillpack ./cmd/skillpackThe resulting binary is under 5 MB with zero runtime dependencies.
Drop it in your $PATH and you're done.
Download from the Releases page.
mkdir my-pack && cd my-pack
skillpack init --name my-packThis creates a skillpack.yaml workspace manifest:
name: my-pack
version: 0.1.0
skills:
- ./skillsDrop a SKILL.md (or .cursorrules, AGENT.md, skill.yaml) under ./skills:
---
name: code-review
version: 1.2.0
description: Carefully review code changes for issues
license: MIT
author: jslee
tools:
- git
- bash
requires:
- base-agent ^1.0.0
---
# Code Review Skill
When asked to review code, follow these steps:
1. ...$ skillpack resolve
Install order (2 skills):
1. base-agent@1.0.0 [skill.md] — skills/base-agent/SKILL.md
2. code-review@1.2.0 [skill.md] — skills/code-review/SKILL.md
$ skillpack install
skillpack: wrote skillpack.lock (2 skills)The resulting skillpack.lock:
{
"version": 1,
"generated_by": "skillpack",
"skills": [
{
"name": "base-agent",
"version": "1.0.0",
"format": "skill.md",
"hash": "sha256:b6a8...",
"source": "skills/base-agent/SKILL.md"
},
{
"name": "code-review",
"version": "1.2.0",
"format": "skill.md",
"hash": "sha256:f3c0...",
"source": "skills/code-review/SKILL.md",
"requires": ["base-agent ^1.0.0"]
}
]
}$ skillpack verify
skillpack: verify OKExit code 0. Wire it into your pipeline:
# .github/workflows/ci.yml
- name: Verify skill lockfile
run: skillpack verifyIf any skill drifts (someone edited a body, bumped a version, deleted a file),
verify exits with code 1 and prints exactly what changed.
$ skillpack bundle -o my-pack.skl
skillpack: wrote my-pack.skl (4218 bytes, 2 skills)
hash: sha256:5e9d...
$ skillpack keygen --priv priv.key --pub pub.key
$ skillpack sign --key priv.key my-pack.skl
skillpack: wrote signature my-pack.skl.sig
$ skillpack sign --verify --pubkey pub.key my-pack.skl
skillpack: signature OK for my-pack.sklDistribute my-pack.skl + my-pack.skl.sig + pub.key and your users can
verify provenance with one command.
skillpack speaks all four of the de facto agent-skill manifest formats:
---
name: my-skill
version: 1.0.0
description: What this skill does
license: MIT
author: alice
tools:
- bash
- git
requires:
- other-skill ^1.0.0
---
The body of the skill goes here.---
name: my-cursor-rules
version: 0.3.0
description: Code style for our team
globs:
- "**/*.ts"
- "**/*.tsx"
alwaysApply: true
---
Always use tabs.
Never use any.
Prefer named exports.---
name: portable-bot
version: 2.0.0
description: A bot that works in any vendor's runtime
vendor: anthropic
models:
- claude-3.5-sonnet
permissions:
- filesystem
- network
tools:
- bash
---
This agent helps you...name: yaml-only
version: 1.0.0
description: A skill defined entirely in YAML
license: Apache-2.0
tools:
- curl
- jq
body: |
This skill talks to APIs.skillpack normalizes all four into the same canonical record, so you can mix and match formats inside a single workspace and the lockfile will treat them identically.
Every byte of skillpack's output is deterministic. The following invariants are tested:
| Invariant | Test |
|---|---|
skillpack install produces byte-identical lockfile across two runs |
lockfile.TestMarshalDeterministic |
skillpack bundle produces byte-identical tarball across two runs |
bundle.TestBundleDeterministic |
| Tarball is identical regardless of input skill order | bundle.TestBundleDeterministicRegardlessOfInputOrder |
| Hash is identical regardless of frontmatter key order | hasher.TestHashFrontmatterOrderInsensitive |
| Hash is identical regardless of tools/requires order | hasher.TestHashToolsOrderInsensitive |
| Hash is identical regardless of CRLF vs LF line endings | parser.TestParseSkillMDCRLF + canonical normalization |
| Hash is identical regardless of UTF-8 BOM presence | parser.TestParseSkillMDBOM |
| Resolver order is identical across runs (lexicographic tiebreak) | resolver.TestResolveDeterministic |
How we achieve this:
- Lockfile:
encoding/jsonwith sorted struct fields, manually sorted skills slice, manually sorted requires within each entry, LF line endings, trailing newline. - Tarball: sorted file order, fixed
mtime(1970-01-02 to dodge zero-mtime quirks),uid/gid = 0,Uname/Gname = "",tar.FormatPAX, gzip with fixed header name. - Hashing: canonical pre-image is line-oriented
key=value\nform with sorted keys, body normalized to LF + single trailing newline. - No timestamps anywhere in any hashable code path.
skillpack uses distinct exit codes so CI pipelines can branch on the failure mode:
| Code | Meaning | When |
|---|---|---|
0 |
OK | Operation succeeded |
1 |
Drift | verify found a hash or version mismatch (expected failure mode) |
2 |
Parse error | A skill file is malformed (YAML, frontmatter, manifest) |
3 |
IO error | Filesystem, permission, or missing-file error |
4 |
Internal error | An unexpected bug in skillpack itself |
5 |
Usage error | Invalid CLI flags or missing required arguments |
6 |
Security | sign --verify failed (tampered bundle or wrong key) — treat as a hard-fail, never a routine lock refresh |
CI example:
skillpack verify
case $? in
0) echo "All skills clean" ;;
1) echo "Drift detected — open a PR to update skillpack.lock" ; exit 1 ;;
2) echo "Skill file is broken — block merge" ; exit 1 ;;
6) echo "SIGNATURE TAMPER — do not merge, investigate" ; exit 1 ;;
*) echo "skillpack itself failed — investigate" ; exit 1 ;;
esacskillpack is intentionally small: ~5,300 lines of Go split across 13 internal packages. Each package has a single responsibility and a clean interface.
cmd/skillpack/main.go entry point (5 lines)
internal/
├── cli/ cobra command tree
│ ├── root.go root command + Execute
│ ├── init.go skillpack init
│ ├── add.go skillpack add
│ ├── resolve.go skillpack resolve
│ ├── install.go skillpack install
│ ├── verify.go skillpack verify
│ ├── bundle.go skillpack bundle
│ ├── sign.go skillpack sign / keygen
│ └── lock.go skillpack lock
├── workspace/ manifest + parser + resolver glue
├── manifest/ skillpack.yaml read/write
├── parser/ multi-format parser
│ ├── skillmd.go SKILL.md
│ ├── cursorrules.go .cursorrules
│ ├── agentmd.go AGENT.md
│ ├── skillyaml.go skill.yaml
│ └── helpers.go normalizeRequires, dedupSorted
├── skill/ canonical Skill record
├── semver/ constraint matching (^/~/x/...)
├── resolver/ topological sort with semver checks
├── hasher/ sha256 content addressing
├── lockfile/ deterministic JSON lockfile
├── bundle/ deterministic tar.gz writer
├── signer/ ed25519 detached signatures
├── verify/ CI drift detection
└── exitcode/ typed errors -> exit codes
Dependency direction always flows downward — cli depends on workspace,
workspace depends on parser + resolver + manifest, and so on. There
are no cycles, and the leaf packages (skill, semver, exitcode) have no
internal dependencies.
221 tests across all layers:
| Package | Tests | What it covers |
|---|---|---|
parser |
32 | All four formats, CRLF, BOM, missing fields, bad YAML, requires (list/map), v-prefix versions |
cli |
27 | init, add, resolve, install, verify (clean/drift), bundle, sign, keygen, lock, JSON output, error paths |
lockfile |
19 | Roundtrip, sort order, LF only, trailing newline, missing/negative/future version, atomic write, duplicate-name rejection (Cycle K) |
semver |
17 | Caret, tilde, comparators, x-ranges, BestMatch, normalize, edge cases |
hasher |
17 | Determinism, frontmatter order, tools/requires order, body/version/name sensitivity, collision-resistance across comma/pipe/=/newline ambiguity |
signer |
16 | Generate, sign, verify, tampered, wrong key, CRLF in key file, file roundtrip, trailing garbage, multi-line body |
bundle |
16 | Determinism, multiple formats, header validation, path safety, list mode, tainted-archive hardening |
skill |
16 | Canonical record validation, name rules (./../leading-dot/whitespace), constraint parsing, sorting |
resolver |
14 | Linear chain, diamond, cycle, self-cycle, missing dep, version conflict, deterministic ordering, duplicates |
manifest |
14 | Roundtrip, sort, missing fields, bad YAML, write/read, skills-path validation |
verify |
12 | Clean, drift hash, drift version, missing, extra, sorted findings, parse error, JSON snake_case schema pin (Cycle J) |
exitcode |
8 | Wrap/Classify, nil-safe, layered wrap preservation |
workspace |
8 | Load happy, missing manifest, missing dep, recursive discover, dedup, ignore .git |
docsmeta |
5 | Doc-accuracy meta-tests: ROUND_LOG/CHANGELOG/README test-count pins + badge pin + per-package table sum + meta-meta self-consistency (Cycle J, L) |
Run them yourself:
go test ./... # all green, ~5 seconds
go test -race ./... # race detector clean
go vet ./... # vet clean| Tool | Layer | Format support | Lockfile | Hashing | Bundling | Signing | Status |
|---|---|---|---|---|---|---|---|
| skillpack | Lifecycle | All 4 | ✅ | ✅ sha256 | ✅ deterministic | ✅ ed25519 | This project |
opencli |
Runtime dispatch | AGENT.md | ❌ | ❌ | ❌ | ❌ | Different layer |
googleworkspace/cli |
Vendor bundle | Vendor-specific | ❌ | ❌ | Bundled at build | ❌ | Vendor-locked |
awesome-claude-code |
Curation | Markdown list | ❌ | ❌ | ❌ | ❌ | No tooling |
claude-code-ultimate-guide |
Docs | Docs | ❌ | ❌ | ❌ | ❌ | Docs site |
| npm/cargo/pip | Code packages | N/A | ✅ | ✅ | ✅ | Sometimes | Wrong domain |
skillpack is the only tool that closes the lifecycle loop for agent skills.
Things that are explicitly out of scope for v1.0 (and may land in 1.x):
- Federated registry — v1.0 has no centralized registry. Skills come from local paths, file:// URIs, http(s):// URLs, and git URLs only. A federated index is V2 and only if traction warrants.
- Skill audit — security analysis of skill bundles (prompt injection,
shell calls, oversized contexts). See the runner-up
skillauditproposal. - Skill catalog UI — local index + search over installed skills.
- Schema validation — strict JSON Schema validation per format.
- Lockfile resolution caching — for very large workspaces.
Bug reports, feature ideas, and pull requests welcome at github.com/JSLEEKR/skillpack/issues.
Before opening a PR:
go test -race ./... # must be green
go vet ./... # must be cleanThe Generator/Evaluator separation in our build pipeline means every change goes through an independent eval pass before merging.
MIT © 2026 JSLEEKR. See LICENSE for the full text.
- The Anthropic Claude Code team for shipping
SKILL.mdas a parseable format - The Cursor team for
.cursorrules - The opencli, googleworkspace/cli, and awesome-claude-code communities for proving the demand for skill tooling
golang.org/x/mod/semverfor the rock-solid semver primitivesgopkg.in/yaml.v3for YAML parsinggithub.com/spf13/cobrafor the CLI framework
If skillpack saves your team a single deployment headache, that's payment enough.