Dependency manager for the Claude ecosystem — rules, skills, agents, commands, hooks, MCP servers.
Project description
cleo
npm · pip · composer · cargo — for everything that goes in .claude/.
cleo is a dependency manager for the Claude ecosystem. One manifest pulls rules, skills, agents, commands, hooks, and MCP configs from dozens of authors — cleo install gives your whole team the same setup, cleo update keeps it current as each author ships fixes.
cleo require Surt/cleo-plan-then-doc # fetches from github.com/Surt/cleo-plan-then-doc
cleo install # install everything from cleo.json (lock-strict)
cleo update # update to latest matching versions
cleo remove Surt/cleo-plan-then-doc # uninstall + clean up
No registration. No central server. vendor/name resolves to github.com/vendor/name automatically.
Each install is validated (safe paths, git refs, manifest shape, hook size) and pinned by commit SHA in cleo.lock — reproducible across machines, auditable in review.
The README has two halves: Use cleo (consume packages in your project) and Publish a cleo package (author and share your own).
Install cleo
As a Claude Code plugin (gets you /cleo-install, /cleo-require, etc.):
/plugin marketplace add https://github.com/Surt/cleo
/plugin install cleo@cleo
Via pip (works anywhere, no Claude Code needed):
pip install ClaudeCleo
From source (if you prefer to clone):
git clone https://github.com/Surt/cleo
cd cleo && ln -s "$PWD/cleo" /usr/local/bin/cleo # or add to PATH
pip install pyyaml
Windows: add cleo.cmd to PATH instead.
Use cleo (install packages into your project)
Add your first package
The fastest path is to ask cleo to do it. From inside a Claude Code session:
/cleo-require Surt/cleo-plan-then-doc
Or from the terminal:
cleo require Surt/cleo-plan-then-doc # latest matching tag
cleo require Surt/cleo-plan-then-doc@^0.1 # with a version constraint
cleo require Surt/cleo-plan-then-doc --local # gitignored, this repo only
On first run, cleo scaffolds cleo.json for you if none exists, resolves the latest matching tag, fetches the package, copies its content into .claude/, and writes cleo.lock. After that:
cleo update # bump matching versions
cleo list # see what's installed
cleo remove Surt/cleo-plan-then-doc
Commands
| Command | Description |
|---|---|
cleo init |
Scaffold a starter cleo.json |
cleo install |
Install from cleo.json (lock-strict when cleo.lock exists) |
cleo require <vendor/pkg> [--repo <url>] |
Add a package and install it |
cleo remove <vendor/pkg> |
Uninstall — removes files, MCP entries, hooks, manifest entry |
cleo update [<vendor/pkg>] |
Re-resolve within constraints, update lock |
cleo list |
Show installed packages |
cleo check |
Validate manifest, report missing files, detect on-disk drift |
Claude Code slash commands — same ops, inside a session: /cleo-install · /cleo-require · /cleo-remove · /cleo-update · /cleo-list. /cleo-require accepts --repo <url>.
Where files land
Each package's content maps directly to Claude Code surfaces:
| In the package | Installed to | Claude Code concept |
|---|---|---|
rules/*.md |
.claude/rules/ |
Memory rules |
skills/*/SKILL.md |
.claude/skills/ |
Skills |
agents/*.md |
.claude/agents/ |
Subagents |
commands/*.md |
.claude/commands/ |
Slash commands (legacy form; merged into skills upstream) |
hooks/*.sh |
.claude/hooks/ + settings.json |
Tool-event hooks |
mcp.json |
settings.json → mcpServers |
MCP servers |
cleo fetches into ~/.claude/cleo/packages/<vendor>/<name>/<version>/ (version-pinned cache), then copies into your project. Installed files are prefixed cleo-<vendor>-<pkg>- so they never collide with hand-written ones.
Your cleo.json
cleo require writes one for you, but here's what it looks like (and what you'd edit by hand). Full schema: spec/cleo-json.md.
{
"name": "my-project",
"require": {
"acme/cleo-generic": "^1.0",
"acme/cleo-example": "^1.0",
"acme/cleo-mcp-example": "^2.0"
},
"require-local": {
"myhandle/personal-notes": "*"
},
"require-user": {
"myhandle/global-shortcuts": "^1.0"
}
}
Three buckets. All three are optional:
| Key | Where files land | Committed? | Scope |
|---|---|---|---|
require |
.claude/ |
yes | whole team |
require-local |
.claude/<type>/local/ |
no (gitignored) | you, this repo |
require-user |
~/.claude/ |
no (out-of-tree) | you, all repos on this machine |
Use repositories to override the GitHub convention for specific packages (GitLab, private hosts, local paths):
{
"repositories": [
{ "type": "git", "url": "https://gitlab.com/myorg/private-rules" }
],
"require": {
"myorg/private-rules": "*"
}
}
Version constraints
| Constraint | Meaning |
|---|---|
* |
any version |
1.2.3 |
exact |
^1.2.3 |
>=1.2.3 <2.0.0 |
~1.2.3 |
>=1.2.3 <1.3.0 |
>=1.0 |
at least 1.0 |
>=1.0 <2.0 |
range |
Transitive dependencies
Packages can declare their own dependencies in their cleo.json:
{
"name": "acme/full-stack",
"type": "bundle",
"version": "1.0.0",
"require": {
"acme/typescript-rules": "^1.0",
"acme/react-agent": "^2.0"
}
}
When you cleo require acme/full-stack, cleo resolves and installs all transitive dependencies automatically — in topological order so deps land before the packages that need them. Cycles are detected and reported as errors. Version conflicts (two packages requiring incompatible ranges of the same dep) are caught before any files are written.
cleo remove garbage-collects orphaned transitive deps: removing a top-level package also removes its deps if nothing else needs them.
Parallel fetching
Speed up installs with --jobs / -j:
cleo install --jobs 4 # fetch 4 packages at once
cleo require acme/pkg -j 4 # parallel fetch during require
Git clones/fetches run in parallel; file materialization remains sequential to avoid race conditions. Default is 1 (sequential).
URL resolution
cleo resolves vendor/name in this order:
--repo <url>flag (explicit override)- Matching entry in
repositories https://github.com/vendor/name(default convention)
Coming from vercel-labs/skills?
cleo is a drop-in replacement. Nothing you've installed gets thrown away, no commands need to be re-learned, and switching back is one git checkout away.
- Your existing skills stay put.
cleo update --adoptscans.claude/skills/(project) and~/.claude/skills/(global), finds the directories you installed withnpx skills, and registers them incleo.json+cleo.lock. Nothing is moved, copied, or rewritten. Add--scope project|global|bothto narrow it,--dry-runto preview. - Same source forms. Every URL or path you'd hand
npx skillsworks withcleo require(table below). - Same
--symlinkmode.cleo require <src> --symlinklinks from the package cache instead of copying — same dev-loop ergonomics. - Files land in the same place. Skills under
.claude/skills/, rules under.claude/rules/, etc. — cleo doesn't invent a new layout. The only difference is thecleo-<vendor>-<pkg>-prefix on installed names so cleo can tell its files apart from yours. - No lock-in.
cleo removeuninstalls cleanly. Deletecleo.json+cleo.lockand you're back to whatever you had before. - What you gain. Teammates run
cleo installand get the same state. Version constraints, lock file, and three scope buckets (project / gitignored / user-global) replace ad-hocgit clone+ manual copy.
cleo require accepts:
| Form | Example |
|---|---|
| GitHub shorthand | cleo require Surt/cleo-plan-then-doc |
| Full git URL | cleo require https://github.com/Surt/cleo-plan-then-doc |
| GitHub subdir (one folder of a repo) | cleo require https://github.com/vercel-labs/skills/tree/main/skills/playwright |
| GitLab URL | cleo require https://gitlab.com/org/repo |
| SSH URL | cleo require git@github.com:Surt/cleo-plan-then-doc.git |
| Local path | cleo require ./my-skills |
Symlink mode. cleo require <src> --symlink links from the package cache instead of copying — handy when authoring a skill in another working tree (mirrors npx skills --symlink).
Adopt skills already on disk. If .claude/skills/ has SKILL.md directories cleo doesn't yet track, cleo update --adopt registers them into cleo.json + cleo.lock so they survive a fresh clone. --scope project|global|both narrows the scan; --dry-run previews the diff.
Lock file (cleo.lock)
Generated automatically on cleo install / cleo require. Pins exact versions and commit SHAs so every developer and CI run gets identical output. Commit it. Full schema: spec/cleo-lock.md.
{
"version": 1,
"packages": {
"acme/cleo-example": {
"type": "bundle",
"url": "https://github.com/acme/cleo-example",
"version": "1.2.0",
"commit": "abc123def456",
"bucket": "project",
"items": [...]
}
}
}
cleo update is the only command that changes pinned versions. Everything else honors the lock.
Security
cleo fetches code from git and copies it into your project. Each install is validated for safe package names, symlinks, git refs, file types, hook size, and manifest shape. It does not sandbox installed artifacts, sign packages, or scan content — that's on you and on Claude Code's permission model.
Full threat model + what's out of scope: spec/security.md.
Publish a cleo package
A cleo package is just a git repo with a semver tag and one or more artifact directories. No registry signup; the URL is the identity.
Repo layout
my-pkg/ ← git repository root
├── cleo.json ← recommended: package metadata
├── rules/ ← whichever artifact dirs apply
│ └── my-rule.md
├── skills/
│ └── my-skill/SKILL.md
├── agents/my-agent.md
├── commands/my-command.md
├── hooks/PreToolUse.sh
├── mcp.json ← only for mcp-server / mixed packages
└── README.md
Only the directories you actually need. A package with just rules/ is valid.
Your package's cleo.json (different from a consumer's)
A consumer's cleo.json lists what packages to install. A package's cleo.json describes the package itself.
{
"name": "myorg/cleo-example",
"type": "bundle",
"version": "1.0.0",
"description": "Short description of what this package provides",
"homepage": "https://github.com/myorg/cleo-example"
}
| Field | Required | Notes |
|---|---|---|
name |
recommended | <vendor>/<name> |
type |
recommended | bundle | mcp-server | mixed. Defaults to bundle if absent. mcp-server / mixed require this field. |
version |
recommended | Informational — cleo uses git tags as the source of truth |
description, homepage |
optional | Surface in cleo list, helps discoverability |
If you omit cleo.json entirely, cleo treats the repo as type: bundle and installs whatever artifact dirs it finds. mcp.json is ignored without an explicit type.
Publish
- Create a git repo (GitHub default, GitLab/private fine — consumers point
--repoorrepositoriesat it) - Add artifact directories + (recommended) a
cleo.json - Tag a release:
git tag v1.0.0 && git push origin v1.0.0
Done. Anyone can install with cleo require myorg/cleo-example.
Full package spec: spec/package-format.md. What cleo refuses to install (hook size limits, symlinks, manifest shape): spec/security.md.
Versioning
Releases follow Semantic Versioning:
- MAJOR — breaking changes to
cleo.json/cleo.lockformat or CLI interface - MINOR — new commands or package-type support, backward-compatible
- PATCH — bug fixes, no interface changes
See CHANGELOG.md for the full history.
Requirements
- Python 3.9+
gitpip install pyyaml
License
Copyright © 2026 Erik Wiesenthal.
Released under GPL-3.0-or-later.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file claudecleo-0.4.1.tar.gz.
File metadata
- Download URL: claudecleo-0.4.1.tar.gz
- Upload date:
- Size: 99.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
46a6d0a257d1a2baadcb0e500fe438600fc147d3ba14f6bb58ae1d4ed95c7d3b
|
|
| MD5 |
37188138ce0f4cdfd5670bfbbebb9aa0
|
|
| BLAKE2b-256 |
0cbb82477ea1cfd102009c4fa478ae32e1b4f08c6759d74af6833cadbfb5eb2c
|
Provenance
The following attestation bundles were made for claudecleo-0.4.1.tar.gz:
Publisher:
release-please.yml on Surt/cleo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
claudecleo-0.4.1.tar.gz -
Subject digest:
46a6d0a257d1a2baadcb0e500fe438600fc147d3ba14f6bb58ae1d4ed95c7d3b - Sigstore transparency entry: 1625420708
- Sigstore integration time:
-
Permalink:
Surt/cleo@92a960b2673fccd61d8dc08413af75196e53d3f6 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Surt
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-please.yml@92a960b2673fccd61d8dc08413af75196e53d3f6 -
Trigger Event:
push
-
Statement type:
File details
Details for the file claudecleo-0.4.1-py3-none-any.whl.
File metadata
- Download URL: claudecleo-0.4.1-py3-none-any.whl
- Upload date:
- Size: 56.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dfff2e821808b98f45d0b123d5bcfae2e6f8efa93b1865a7d2807268db55bed4
|
|
| MD5 |
94ca667a229c1f5c8549c201e45dc12c
|
|
| BLAKE2b-256 |
59307c4528ddf68885d3d6d9a3af1fcb0c24e2e8309e7fcef9bf960062ee1d2c
|
Provenance
The following attestation bundles were made for claudecleo-0.4.1-py3-none-any.whl:
Publisher:
release-please.yml on Surt/cleo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
claudecleo-0.4.1-py3-none-any.whl -
Subject digest:
dfff2e821808b98f45d0b123d5bcfae2e6f8efa93b1865a7d2807268db55bed4 - Sigstore transparency entry: 1625420726
- Sigstore integration time:
-
Permalink:
Surt/cleo@92a960b2673fccd61d8dc08413af75196e53d3f6 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Surt
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-please.yml@92a960b2673fccd61d8dc08413af75196e53d3f6 -
Trigger Event:
push
-
Statement type: