zskills
A declarative package manager for agentic coding CLIs — skills, plugins, and MCP servers from a single TOML manifest. Written in Rust.
Think brew bundle for your AI coding setup: skills.toml declares intent, the runtime’s on-disk config (e.g. Claude Code’s ~/.claude/settings.json, installed_plugins.json, and MCP server entries) gets reconciled atomically. Works with any marketplace tap, any GitHub repo that exposes a skill under skills/<name>/SKILL.md, and npm-distributed skill bundles via npm = "<pkg>".
Supported runtimes
| Runtime | Status | What’s managed |
|---|---|---|
| Claude Code | ✅ supported | plugins (via marketplaces), Agent Skills (~/.claude/skills/), MCP servers (all five known scopes) |
Grok-based CLIs (e.g. grok-cli) | planned | skills (~/.agents/skills/), MCP servers |
| Codex | planned | skills, MCP servers |
| xAI’s official CLI | planned | once it ships |
The data model is runtime-agnostic; new runtimes are new loaders, not a new tool.
Install
cargo install --git https://github.com/zot24/zskills
Requires git and (for npm-sourced skills) npm on $PATH.
Quick start
Create ~/.config/zskills/skills.toml:
# Claude Code plugins (marketplace-based)
[[skills]]
name = "umbrel-app"
marketplace = "zot24-skills"
[[skills]]
name = "cloudflare"
marketplace = "cloudflare"
# Agent Skills from a GitHub repo
[[agent_skills]]
source = "jakubkrehel/make-interfaces-feel-better"
# Agent Skills from an npm package (with glob ownership)
[[agent_skills]]
npm = "get-shit-done-cc"
claims = ["gsd-*"]
# MCP servers (v0.7+)
[[mcps]]
name = "github"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
env = { GITHUB_TOKEN = "${GITHUB_TOKEN}" }
scope = "user"
[[mcps]]
name = "linear"
url = "https://mcp.linear.app/mcp"
scope = "user"
Then:
zskills marketplace add-recommended # seed trusted defaults (Anthropic-official marketplace)
zskills marketplace add zot24/skills # register additional taps as needed
zskills search <query> # find skills across registered marketplaces
zskills sync # apply the manifest
zskills upgrade # refresh everything from origin
zskills list # see what's installed
zskills doctor # reconcile disk ↔ inventory ↔ settings
Commands
zskills list [-v] # what's installed; agent skills grouped by source
zskills install <name> # add a plugin to enabledPlugins
zskills remove <name> # disable + drop inventory (keep bytes)
zskills purge <name> # also delete bytes
zskills enable <name> / disable <name> # flip the flag only
zskills sync [--file f.toml] [--prune] # apply declarative manifest
zskills upgrade [<name>...] # ONE command: refresh everything
zskills update [<name>...] # refresh marketplace caches (plugins only)
zskills doctor [--fix] # reconcile disk ↔ inventory ↔ settings
zskills scan [path] # find project-scope skills across a tree
zskills migrate <project> # promote project skills to user scope
zskills migrate-skill <name> # promote ONE skill across every project
zskills migrate-all <dir> # interactive sweep
zskills search <query> # keyword search across registered marketplaces
zskills marketplace add|remove|list|update
zskills marketplace add-recommended # seed trusted defaults (anthropics/claude-plugins-official)
Optional capabilities live behind cargo features so the default binary stays minimal — see Commands → Optional features for the skills-sh remote-index driver.
Full reference: Commands. Workflows and recipes: Use cases. How it works internally: Architecture. Stuck? Troubleshooting.
Why
Existing tooling is fragmented across runtimes, primitives, and languages: a JS shim for Claude skills, a separate flow for MCP servers, no shared manifest, no atomic write semantics, no way to take ownership of bundles installed via other tooling. zskills is a single static binary that:
- Manages skills, plugins, and MCP servers from one declarative manifest.
- Preserves every unknown field in your settings JSON (hooks, permissions, env, anything the runtime adds later) — atomic round-trips, never clobbers.
- Tracks ownership via inventory tags + glob claims so you can take over skill bundles installed by other tools.
- Reconciles intent ↔ inventory ↔ activation in one pass via
sync. - Treats secrets carefully: only
${VAR}references and key names ever land in zskills’s data structures, never values. - Is built for multiple runtimes — Claude Code today, more planned as their primitives stabilize.
Source
github.com/zot24/zskills · MIT license · v0.6+
Commands reference
Full reference for every zskills subcommand. Flags are shown with their defaults. Run zskills <cmd> --help for the up-to-date help.
Conventions
<name>accepts the unqualified skill name (e.g.servarr) when unambiguous, orname@marketplace(e.g.servarr@zot24-skills) when multiple marketplaces declare the same skill. This matches Claude Code’s own syntax.- Most commands print colored output. Set
NO_COLOR=1to disable, or pipe throughcatif you need plain text. CLAUDE_HOME=/custom/pathoverrides~/.claudefor testing.XDG_CONFIG_HOMEandXDG_CACHE_HOMEare respected for manifest/cache locations.
list
What’s currently installed, with each item’s enabled/disabled/orphaned status. Covers both Claude Code plugins and Agent Skills.
zskills list [--json] [-v] [--paths]
| Flag | Default | Description |
|---|---|---|
--json | off | Emit a machine-readable JSON document for scripting |
-v, --verbose | off | Expand grouped agent skills (show every skill name in each source group) |
--paths | off | Show the on-disk location of each entry: plugin install path under ~/.claude/plugins/cache/..., agent skill directory under ~/.agents/skills/..., or the settings file an MCP server is declared in |
The non-JSON output groups results into four plugin buckets (active, installed-but-disabled, enabled-but-not-installed, installed-from-missing-marketplace) plus two Agent Skill buckets (managed by zskills, on-disk-but-untracked), plus a final MCP Servers section that aggregates every server visible to Claude Code from all of:
~/.claude.jsonand~/.claude/settings.json— user scope<cwd>/.mcp.jsonand<cwd>/.claude/settings.json— project scope<cwd>/.claude.local/settings.json— local (gitignored) scope/Library/Application Support/ClaudeCode/managed-settings.json(macOS) //etc/claude-code/managed-settings.json(Linux) — managed (org-deployed) scope- An attribution column reads each enabled plugin’s
plugin.json/ sibling.mcp.jsonand marks entries with★ plugin:<name>when the server is bundled by a plugin.
Servers are grouped by scope (managed → local → project → user) and only the keys of env / headers are surfaced (no values), so the output never leaks secrets even when ${VAR} refs aren’t used. Set ZSKILLS_MANAGED_SETTINGS=<path> to override the managed-settings probe (useful in CI).
install
Three accepted spec shapes:
zskills install <name> # plugin from a registered marketplace
zskills install <name>@<marketplace> # qualified plugin
zskills install <owner>/<repo> # NEW: Agent Skill(s) directly from a git repo
zskills install <git-url> # NEW: same, for arbitrary git URLs (https://, git@, file://)
zskills install -i # interactive picker over marketplace plugins
Plugin path (<name> / <name>@<marketplace>). Resolves against registered marketplaces, flips enabledPlugins in ~/.claude/settings.json. Claude Code materializes bytes on next launch.
Repo path (<owner>/<repo> or git URL). Clones the repo via git clone --depth 1 into ~/.cache/zskills/agent-skills/<owner>-<repo>/, surveys the tree, and:
- Agent Skills (any directory under
skills/<name>/SKILL.md) — installed to~/.agents/skills/<name>/(the cross-client convention; visible to Claude Code, Grok CLI, and any other compliant client). Inventory tagged with the source. - Marketplace (
.claude-plugin/marketplace.jsonat repo root) — prints a redirect:use zskills marketplace add <owner/repo>and installs nothing from this repo. - MCP servers (
.mcp.jsonormcpServersinplugin.json) — surfaced as a hint; not auto-installed (use[[mcps]]inskills.toml+zskills sync).
| Flag | Default | Description |
|---|---|---|
-i, --interactive | off | For plugin specs: without any <name>, browse all marketplace plugins with a fuzzy picker. For repo specs: always opens a multi-select picker over the Agent Skills found in the repo. |
--all | off | For repo specs only: when the repo contains more than 5 Agent Skills, confirm “yes, install every one.” Without --all, large collections abort with a sample summary so they don’t silently flood ~/.agents/skills/. Ignored for repos with ≤5 skills (those install everything by default). |
Repo-path count behavior:
| Skills in repo | Default (no flags) | With -i | With --all |
|---|---|---|---|
| 0 | error | error | error |
| 1 | install it | install it | install it |
| 2–5 | install all | picker | install all |
| > 5 | abort + summary + next-step hint | picker | install all |
remove / purge
remove is apt-style: disable in enabledPlugins and drop the inventory entry, but leave bytes on disk so re-enabling is instant. purge does the same plus deletes the bytes from ~/.claude/plugins/cache/.
zskills remove <name>...
zskills remove -i
zskills purge <name>...
| Flag | Default | Description |
|---|---|---|
-i, --interactive | off | (remove only) When passed without any <name>, browse enabled plugins with a multi-select picker and remove the selection. |
enable / disable
Flip a plugin’s enabledPlugins flag without (un)installing.
zskills enable <name>...
zskills disable <name>...
enable is a no-op if the plugin isn’t installed (Claude Code will install it on next start). disable keeps bytes and inventory; use purge to wipe completely.
sync (headline command)
Apply a declarative skills.toml manifest. Diffs intent against current state, then atomically writes the necessary settings.json and inventory changes.
zskills sync [--file <path>] [--dry-run] [--prune | --adopt]
| Flag | Default | Description |
|---|---|---|
--file <path> | $XDG_CONFIG_HOME/zskills/skills.toml (then ~/.config/zskills/skills.toml) | Path to skills.toml. ./skills.toml is NOT auto-loaded — pass --file ./skills.toml to use a project-local manifest. (This caused destructive surprises in v0.5; the v0.5.1 default is safer.) |
--dry-run | off | Print the plan; do not write |
--prune | off | Allow destructive removals. Without --prune, agent skills present on disk but absent from the manifest are reported as skip and left untouched. With --prune, their bytes are deleted from ~/.agents/skills/. |
--adopt | off | Inverse of --prune. Append every orphan (installed agent skill, enabled plugin, configured MCP that isn’t yet in the manifest) to skills.toml and exit. Useful for capturing a hand-curated environment into your manifest in one shot. Mutually exclusive with --prune. |
What sync does:
- For each
[[skills]]entry: resolvename@marketplace, write toenabledPlugins. Entries currently enabled but not in the manifest get flipped off. - For each
[[agent_skills]]entry: ifsourceis present, clone/pull and copyskills/<name>/to~/.agents/skills/. Ifnpmis present, runnpm install -g --no-fund --no-audit <pkg>(orinstall_cmd), then claim all matchingclaimsglobs. If neither is present (justname), register the existing on-disk skill in inventory without fetching anything. - Agent skills tracked in inventory but missing from the manifest are reported. With
--prunethey’re deleted; with--adoptthey’re appended to the manifest; otherwise they’re skipped.
--adopt details
When you pass --adopt, sync writes new entries to skills.toml instead of removing anything:
- Enabled plugin not in manifest → new
[[skills]]row withname+marketplace. - Agent skill in inventory but not in manifest → new
[[agent_skills]]row. Thesource/npm/namefields are reconstructed from the inventory tag (localbecomes a name-only entry,npm:pkgbecomesnpm = "pkg", anything else becomessource = "..."). - MCP server configured but not in manifest → new
[[mcps]]row with full transport details (command/args/envfor stdio,url/headersfor http/sse),scopepreserved. Env and header values are copied verbatim — if any contain literal secrets, eyeball the resulting manifest and replace them with${VAR}references before committing.
Adoption is idempotent and de-duplicates against existing entries, so re-running sync --adopt after editing the manifest is safe.
Sync is idempotent. Run it on every fresh machine to reproduce your global state from a single file.
If a ./skills.toml exists in CWD when you run sync without --file, zskills prints a yellow warning telling you it’s being ignored — pass --file ./skills.toml if that’s actually what you wanted.
upgrade
The one command for refreshing everything zskills manages — marketplaces, git agent skills, and npm agent skills.
zskills upgrade [<name>...]
| Source kind | What upgrade does |
|---|---|
| Plugins (marketplace-based) | For each registered marketplace tap, git pull --ff-only if it’s a git working tree; otherwise fetch the GitHub archive tarball from the source recorded in known_marketplaces.json and atomically swap the tree. Claude Code picks up new plugin versions on next start. |
Git agent skills (source = "owner/repo") | git pull the cached source clone + re-copy bytes |
npm agent skills (npm = "pkg") | Run npm install -g <pkg> (or install_cmd), then re-apply the claims glob to retag inventory |
Pass specific names to upgrade just those; empty = upgrade everything. The name filter matches against the manifest’s npm, source, or name fields.
MCP servers manifest schema
Add [[mcps]] tables to skills.toml to declaratively manage MCP servers. sync writes them to the appropriate runtime config file based on scope:
# Stdio: a process the runtime spawns directly.
[[mcps]]
name = "github"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
env = { GITHUB_TOKEN = "${GITHUB_TOKEN}" }
scope = "user" # default; also "project" or "local"
# HTTP: a remote server reachable over HTTP.
[[mcps]]
name = "linear"
url = "https://mcp.linear.app/mcp"
transport = "http" # optional; inferred from `url`
scope = "user"
# Stdio + mcp-remote proxy pattern (real-world): args reference ${VAR} too.
[[mcps]]
name = "honcho"
command = "npx"
args = [
"mcp-remote",
"https://mcp.honcho.dev",
"--header", "Authorization:${HONCHO_AUTH}",
"--header", "X-Honcho-User-Name:${USER_NAME}",
]
env = { HONCHO_AUTH = "${HONCHO_AUTH}", USER_NAME = "${USER}" }
scope = "user"
| Field | Purpose |
|---|---|
name (required) | MCP server name. Becomes the key under mcpServers. |
command | Stdio: the executable to run. Required for stdio transports. |
args | Stdio: list of args. ${VAR} refs are allowed and recommended for credentials. |
env | Stdio: env-var map. Values should use ${VAR} refs. |
url | HTTP/SSE: server URL. |
headers | HTTP/SSE: header map. Same ${VAR} policy as env. |
transport | "stdio" | "http" | "sse". Optional; inferred from command or url. SSE is the deprecated spec form — prefer http. |
scope | "user" (default) | "project" | "local". "managed" is not accepted (read-only). |
Write targets per scope:
| Scope | File | Notes |
|---|---|---|
user | ~/.claude.json | The file claude mcp add --scope user writes to. Wrapped JSON ({"mcpServers": {...}}). |
project | <cwd>/.mcp.json | Spec-recommended team-shared file. Created wrapped; if it already exists with the legacy flat schema, the existing shape is preserved. |
local | <cwd>/.claude.local/settings.json | Gitignored personal+creds file. Wrapped. |
Sync semantics.
syncinstalls every MCP from the manifest and overwrites overlapping entries (manifest is source of truth).- Without
--prune, MCP entries currently in the runtime config but absent from the manifest are reported asskip— same safety pattern as[[agent_skills]]. - With
--prune, those entries are removed from their settings file. - Plugin-injected MCPs are never pruned. If a server name matches one declared by an enabled plugin, it’s owned by that plugin and zskills won’t touch it.
- Managed scope is never written. Entries appear in
list/doctorbutsyncignores them.
Secret handling. Values in env/headers should be ${VAR} references; the actual secret stays in your shell. Literal values are not blocked but they land in the JSON verbatim — which means they’d go straight into git-committed files at project scope. The recommendation is documentation, not enforcement.
Agent skills manifest schema
The [[agent_skills]] table supports four orthogonal fields, used in combination:
# Git-sourced single skill
[[agent_skills]]
source = "jakubkrehel/make-interfaces-feel-better"
# Git-sourced multi-skill repo, install just one
[[agent_skills]]
source = "owner/multi-skill-repo"
name = "specific-skill"
# npm-distributed package
[[agent_skills]]
npm = "get-shit-done-cc"
claims = ["gsd-*"] # glob patterns; every match in ~/.agents/skills/ is owned by this entry
# npm with custom installer command
[[agent_skills]]
npm = "some-tool"
install_cmd = "npx some-tool setup"
# Local-only (just tracked, never refreshed from a remote)
[[agent_skills]]
name = "my-internal-tool"
| Field | Purpose |
|---|---|
source | Git source: owner/repo (GitHub) or full git URL. sync/upgrade clone/pull and copy. |
npm | npm package name. sync/upgrade runs npm install -g <pkg>. |
install_cmd | Custom installer command — overrides the default npm install -g. Used for packages with their own setup CLI. |
name | Optional. For source entries, pick a single skill out of a multi-skill repo. For local-only entries, required — names the on-disk skill to track. |
claims | Glob patterns (e.g., ["gsd-*"]) matched against ~/.agents/skills/. After install, every match is tagged with this entry’s source. Used for npm packages whose installer touches pre-existing directories — so the diff-after-install discovers nothing, but claims retroactively claims ownership. |
update
Refresh every registered marketplace’s git cache (or just the named one).
zskills update [<marketplace-name>]
Runs git pull --ff-only against each marketplace clone. Claude Code reads the cache on next start to detect new versions.
doctor
Reconcile disk ↔ inventory ↔ settings, and statically validate every configured MCP server. Reports drift in four categories:
- Plugins enabled in
enabledPluginsbut not present ininstalled_plugins.json(broken references — Claude Code will fetch on next start). - Plugins in inventory whose marketplace tap is no longer registered.
- Agent skills tracked in inventory but missing from
~/.agents/skills/on disk. - MCP server issues — static checks against every server returned by
list:- stdio:
commandmust resolve on$PATH(useswhich-style lookup). - any transport: every
${VAR}reference inenv(stdio) orheaders(http/sse) must be set in the user’s environment. - sse transport: flagged as deprecated; the spec recommends migrating to
http.
- stdio:
zskills doctor [--fix]
With --fix, dangling plugin/inventory references are removed. --fix is a no-op for MCP issues — none of them are auto-fixable (we won’t install a missing binary or invent an env var), so doctor’s job there is purely to surface what’s broken. --fix never deletes installed bytes — that’s what purge is for.
Doctor never spawns or talks to an MCP server. Running-state diagnosis (whether a server is actually connected, last error, latency) is Claude Code’s job — replicating it here would risk divergent diagnoses. If you need that, run the server directly or check Claude Code’s own logs.
scan
Walk a directory tree looking for project-scope skills.
zskills scan [<path>] [--depth N] [--json]
| Flag | Default | Description |
|---|---|---|
<path> | . | Tree to walk |
--depth N | 6 | Maximum directory recursion depth |
--json | off | Machine-readable output |
Detects two patterns:
.claude/settings.jsonor.claude/settings.local.jsonwithenabledPlugins/extraKnownMarketplaces(project-scope plugin enables).claude/skills/<name>/SKILL.md(project-scope Agent Skills)
The default depth of 6 catches both patterns from a ~/Desktop/code-style parent directory (Agent Skills are at depth 5 from the project root).
migrate
Promote ONE project’s enabled plugins and project-scope Agent Skills to user scope.
zskills migrate <project> [--remove-from-project] [--dry-run]
Reads <project>/.claude/settings.json (or settings.local.json) and <project>/.claude/skills/<name>/. Writes promoted plugin enables into ~/.claude/settings.json’s enabledPlugins and copies Agent Skill directories into ~/.agents/skills/.
--remove-from-project clears enabledPlugins, extraKnownMarketplaces, and .claude/skills/ from the project after a successful promote.
migrate-skill
Promote ONE Agent Skill that appears in many projects across the tree, in a single operation.
zskills migrate-skill <name> [--root <dir>] [--source <ref>]
[--remove-from-all] [--dry-run]
| Flag | Default | Description |
|---|---|---|
--root <dir> | . | Tree to search |
--source <ref> | none (local-only) | If set, install from upstream (owner/repo or git URL) instead of copying canonical |
--remove-from-all | off | Delete the skill’s .claude/skills/<name>/ from every matched project |
--dry-run | off | Print the plan; do not write |
For each matched project, the skill’s directory is hashed and compared. If content diverges, the first project (alphabetical) wins as canonical and a warning is printed showing which projects have which hash. The promoted skill gets a [[agent_skills]] entry appended to your skills.toml so the migration is reproducible.
migrate-all
Interactive sweep across a tree.
zskills migrate-all <dir> [--threshold N] [--yes] [--dry-run]
| Flag | Default | Description |
|---|---|---|
<dir> | required | Tree to walk |
--threshold N | 2 | Only consider skills appearing in ≥N projects |
--yes, -y | off | Skip prompts; accept defaults (no source, keep project copies) |
--dry-run | off | Print planned action per skill; do not write |
For each duplicated skill above the threshold, prompts:
- “Promote ‘
’ to user scope? [Y/n]” - “Upstream source [owner/repo, URL, or blank for local-only]:”
- “Remove project copies from N project(s)? [y/N]”
Calls migrate-skill under the hood for each accepted prompt.
marketplace
Tap management — register, list, refresh, and remove Claude Code marketplaces.
zskills marketplace add <owner/repo | git-url>
zskills marketplace add-recommended
zskills marketplace remove <name>
zskills marketplace list [--json]
zskills marketplace update [<name>]
add clones the marketplace repo into ~/.claude/plugins/marketplaces/<name>/ and writes both known_marketplaces.json and settings.json’s extraKnownMarketplaces. Mirrors what /plugin marketplace add does inside Claude Code.
add-recommended seeds the trusted defaults (currently just anthropics/claude-plugins-official). Idempotent — safe to re-run; existing marketplaces are left as-is.
add skills.sh is recognized only when zskills was built with --features skills-sh. It registers skills.sh as a remote-index source type (no git clone) and is dispatched by search and install via the HTTP API. See Optional features below.
search
Keyword search across every registered marketplace. Substring-matches <query> against name + description in each marketplace’s cached marketplace.json. Purely local — no network calls.
zskills search <query> [--limit <n>] [--json] [-i]
| Flag | Default | Description |
|---|---|---|
--limit <n> | 25 | Maximum results per marketplace |
--json | off | Emit results as a JSON array for scripting |
-i, --interactive | off | After printing results, open a picker; selecting one installs it. |
With the skills-sh cargo feature compiled in AND ZSKILLS_SKILLS_SH_API_KEY set, search also federates to the skills.sh remote index and tags those results [skill]. Without the env var, the registered remote-index is skipped with a one-line hint and local search continues uninterrupted.
Optional features
zskills ships vanilla by default. Optional capabilities are gated behind cargo features so they aren’t even compiled into the binary unless you ask for them.
| Feature | What it adds | How to enable |
|---|---|---|
skills-sh | Federated search + install against the skills.sh remote index. Registers a new remote-index source type. Runtime activation requires ZSKILLS_SKILLS_SH_API_KEY. | cargo install --git https://github.com/zot24/zskills --features skills-sh |
Without the feature, zskills marketplace add skills.sh returns “unrecognized marketplace source” — there’s no dormant code, no env-var detection, nothing. The compiled binary is byte-identical to a feature-free build except for what you explicitly asked for.
fzf integration (auto-detected)
install -i, search -i, and remove -i automatically use fzf when it’s on $PATH, which gets you full fuzzy filtering and the familiar fzf keybindings. When fzf is not installed, zskills falls back to the built-in dialoguer picker (FuzzySelect for single-select, MultiSelect for multi). Set ZSKILLS_NO_FZF=1 to force the dialoguer path even when fzf is installed.
install fallback (skills.sh feature only)
When skills-sh is built in and a remote index is registered with a valid key, install <name> will fall through to skills.sh if the spec doesn’t resolve in any local plugin marketplace. It performs an exact-slug match against the skills.sh search API and, on hit, routes through the existing Agent Skill install path (git clone source/repo → drop SKILL.md into ~/.agents/skills/<name>/). No enabledPlugins flip — agent skills don’t use that gate.
Use cases
Pragmatic recipes for common workflows. Each section assumes zskills is installed and on PATH.
1. Bootstrap a fresh machine
cargo install --git https://github.com/zot24/zskills
# Copy your manifest from the old machine (or your dotfiles repo)
mkdir -p ~/.config/zskills
scp old-machine:~/.config/zskills/skills.toml ~/.config/zskills/
# Seed the trusted defaults, then add any additional marketplaces named in the manifest
zskills marketplace add-recommended # anthropics/claude-plugins-official
zskills marketplace add zot24/skills
zskills marketplace add cloudflare/skills
# Apply
zskills sync
After sync, restart Claude Code so it fetches the bytes for the flagged plugins. zskills doctor should report clean.
2. Add a new skill globally
Two options. Use whichever matches how you think.
Imperative — flip on now, document later:
zskills install firecrawl@zot24-skills
Declarative — edit the manifest, run sync:
# ~/.config/zskills/skills.toml
[[skills]]
name = "firecrawl"
marketplace = "zot24-skills"
zskills sync
The declarative path is reproducible across machines; the imperative path is what you reach for in a one-off shell.
3. Mirror an Agent Skill from a GitHub repo
Two paths — pick whichever fits.
Imperative, one-shot:
zskills install jakubkrehel/make-interfaces-feel-better
zskills clones the repo, surveys it, and installs every skills/<name>/SKILL.md it finds. For repos with ≤5 skills, all install by default. For one skill, it installs silently. For multi-skill repos (e.g. a collection), see “large collections” below.
The same command accepts full git URLs too:
zskills install https://github.com/jakubkrehel/make-interfaces-feel-better.git
zskills install [email protected]:jakubkrehel/make-interfaces-feel-better.git
Declarative, reproducible across machines:
[[agent_skills]]
source = "jakubkrehel/make-interfaces-feel-better"
zskills sync
sync clones (or pulls) the repo into $XDG_CACHE_HOME/zskills/agent-skills/, then copies every directory under skills/<name>/SKILL.md into ~/.claude/skills/<name>/. To pin just one skill out of a multi-skill repo, add name = "specific-skill".
Large collections from a repo
When a repo exposes more than 5 skills (a curated collection, say), the imperative install path won’t silently flood ~/.claude/skills/:
$ zskills install owner/big-skill-collection
owner/big-skill-collection contains 47 Agent Skills — zskills won't install all of them by default.
Options:
zskills install owner/big-skill-collection -i interactive picker
zskills install owner/big-skill-collection --all install all 47 skills
Sample (5 of 47): skill-a, skill-b, skill-c, skill-d, skill-e, …
-i opens a multi-select picker (fzf when on $PATH, else dialoguer’s MultiSelect); --all is the explicit-consent escape hatch.
Marketplace repos
If the repo is actually a Claude Code marketplace (has .claude-plugin/marketplace.json), zskills redirects:
$ zskills install anthropics/claude-plugins-official
This repo is a plugin marketplace. To register and install plugins from it:
zskills marketplace add anthropics/claude-plugins-official
zskills install <plugin>@<marketplace>
That’s the canonical path for plugins — they go through marketplace registration, not direct install from the marketplace’s repo.
4. Centralize duplicate skills scattered across projects
You have the same performance-tracking-skill directory under .claude/skills/ in 8 projects, and you want it at user scope so every project gets it for free.
Inspect first:
zskills scan ~/Desktop/code | grep -A1 'Skill → projects'
Promote one specifically (preview, then apply):
zskills migrate-skill performance-tracking-skill --root ~/Desktop/code --dry-run
zskills migrate-skill performance-tracking-skill --root ~/Desktop/code --remove-from-all
migrate-skill hashes each project’s copy; if content has diverged it warns and picks the first as canonical so you can stop and inspect manually. The skill ends up at ~/.claude/skills/performance-tracking-skill/, an entry is appended to your skills.toml, and (with --remove-from-all) every project’s copy is deleted.
5. Interactive sweep across many projects
For a one-shot cleanup of dozens of duplicated skills:
zskills migrate-all ~/Desktop/code --threshold 3
Walks the tree, groups by skill name, only considers skills in ≥3 projects, then prompts per skill. For each accepted prompt it asks for an upstream source (or blank for local-only) and whether to clean project copies.
For batch-promotion without interactivity (accepts defaults: no source, keep project copies):
zskills migrate-all ~/Desktop/code --threshold 5 -y
6. Track a project-scope skill you can’t move yet
Sometimes a skill is genuinely project-specific (e.g., the project’s own ops runbook). You don’t want it at user scope, but you do want it tracked. Currently the manifest is user-scope-only. Two reasonable patterns:
Pattern A: Keep the project copy in version control. Check .claude/skills/<name>/ into the project’s git repo. Don’t add it to ~/.config/zskills/skills.toml. The project carries its own skill; teammates get it on git clone.
Pattern B: Put a project-scope skills.toml in the project root. zskills sync (run from inside the project) auto-discovers ./skills.toml before falling back to ~/.config/zskills/. So a project can carry its own intent for what should be globally enabled when working on it.
7. Adopt a multi-skill npm package (e.g., get-shit-done-cc)
Some skill bundles ship as npm packages whose post-install hook writes many skill directories under ~/.claude/skills/. zskills owns them via a npm + claims declaration:
[[agent_skills]]
npm = "get-shit-done-cc"
claims = ["gsd-*"] # any name matching this glob is owned by this entry
After zskills sync, all matching ~/.claude/skills/gsd-*/ directories are tagged source: "npm:get-shit-done-cc" in inventory. zskills list groups them under one line:
✓ get-shit-done-cc (66 skills) ← npm
gsd-add-tests, gsd-ai-integration-phase, … [-v to list all 66]
zskills upgrade will run npm update -g get-shit-done-cc and re-claim, keeping the bundle current.
If the npm package needs a custom setup command (some packages have a separate CLI to actually place files), use install_cmd:
[[agent_skills]]
npm = "some-tool"
install_cmd = "npx some-tool install"
claims = ["sometool-*"]
8. One command, refresh everything
zskills upgrade
That single command:
git pull(or tarball fetch) every marketplace tap, so Claude Code sees the latest plugin versionsgit pullevery git-sourced agent skill and re-copy bytesnpm update -gevery npm-sourced agent skill (and re-claim viaclaimsglobs)
Pass names to limit scope: zskills upgrade get-shit-done-cc zot24-skills.
9. Diagnose drift
zskills doctor
If something’s amiss:
- “enabled but NOT installed (broken)” — Claude Code knows about the plugin via
enabledPlugins, but the bytes aren’t on disk. Restart Claude Code (it’ll install on startup), or run/plugin install <name>@<mp>inside Claude Code. If you don’t want it anymore,zskills doctor --fixremoves the flag. - “installed from a marketplace that’s no longer registered” — you
marketplace remove-d a tap but plugins from it are still in the inventory. Runzskills purge <name>to clean. - “agent skill tracked in inventory but missing on disk” — someone deleted
~/.claude/skills/<name>/manually.zskills doctor --fixremoves the stale inventory entry, or re-runsyncto reinstall.
10. Reproduce someone else’s setup
Ask them for their skills.toml. Drop it in ~/.config/zskills/skills.toml. Register any marketplaces they use (zskills marketplace add owner/repo). Run zskills sync. Done.
11. Promote project skills + remove the project’s .claude/skills/
zskills migrate ~/Desktop/code/some-project --remove-from-project
Moves both enabledPlugins entries from the project’s .claude/settings.json (or settings.local.json) AND every directory under .claude/skills/ into user scope, then clears the project copies. Useful when you’ve decided “these are clearly global tools — they shouldn’t be vendored per-project.”
12. Vendor a global skill INTO a project (rare; manual)
zskills doesn’t push this direction yet (user-scope → project-scope). If you need a particular project to pin an exact version of a skill that diverges from the global one, copy ~/.claude/skills/<name>/ into the project’s .claude/skills/<name>/ and commit it. Claude Code resolves project scope before user scope, so the project’s pinned copy wins.
13. See every MCP server configured on this machine
Claude Code can read MCP servers from up to six files (per scope). zskills list aggregates the lot and attributes plugin-injected entries:
zskills list # everything; MCPs are the last section
zskills list --paths # also show which file each entry was loaded from
zskills list --json | jq '.mcp_servers'
Output looks like:
MCP Servers
user (3)
github stdio npx -y @modelcontextprotocol/server-github ★ plugin:github (1 env)
honcho http https://mcp.honcho.dev (3 headers)
linear-server http https://mcp.linear.app/mcp
project (1)
postgres stdio docker run --rm postgres-mcp (2 envs)
Only env / header keys are surfaced — values are never read into memory, so the output is safe even if a secret got pasted in literally instead of as a ${VAR} ref.
14. Declare MCP servers in the manifest
[[mcps]]
name = "github"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
env = { GITHUB_TOKEN = "${GITHUB_TOKEN}" }
scope = "user"
[[mcps]]
name = "linear"
url = "https://mcp.linear.app/mcp"
scope = "user"
zskills sync # writes both into ~/.claude.json atomically
Restart Claude Code (or use its /mcp prompt) so it picks up the new servers.
15. Centralize scattered MCP configs
You’ve added MCP servers ad-hoc with claude mcp add from different projects; now they’re spread across ~/.claude.json, project .mcp.json files, and one rogue .claude.local/settings.json. To consolidate at user scope:
-
See what’s where:
zskills list --paths -
Manually transcribe each into
~/.config/zskills/skills.tomlas[[mcps]]entries. Use${VAR}refs for any credentials — never paste literal tokens. -
Sync with prune to write at user scope AND delete the duplicates from other scopes:
zskills sync --prune
--prune removes MCP entries currently in settings files but absent from the manifest. Plugin-injected MCPs are never pruned — zskills detects them by name match against every enabled plugin’s plugin.json / sibling .mcp.json and leaves them alone. Managed scope is never written — IT-deployed entries are read-only.
A dump-mcps helper to skip the manual transcription step is on the roadmap (see issue #14).
16. Validate MCPs before launching Claude Code
zskills doctor
Three static checks per MCP server, no process spawning:
- stdio:
commandmust resolve on$PATH(e.g.npxis installed, the package would be reachable). - any transport: every
${VAR}referenced inenv/headers/argsmust be defined in your shell environment. - sse transport: flagged as deprecated; switch to
transport = "http".
--fix is a no-op for MCP findings — none of them are auto-fixable (zskills won’t install a missing binary or invent an env var). The contribution is surfacing the problem so you know before Claude Code complains.
Doctor never spawns or talks to a server. Runtime health (connection, latency, last error) is Claude Code’s job; replicating it here would risk divergent diagnoses.
17. Find a skill before installing it
You remember there’s a Stripe integration somewhere but you don’t know which marketplace ships it:
zskills search stripe # substring-matches name + description across taps
zskills search stripe --limit 5 # tighter output
zskills search "data analytics" # quoted multi-word queries work
zskills search stripe --json | jq # JSON for scripting
Search reads each marketplace’s cached marketplace.json — purely local, no network. If you also have the skills-sh cargo feature compiled in and ZSKILLS_SKILLS_SH_API_KEY set, results from the skills.sh remote index are tagged [skill] and merged in:
cargo install --git https://github.com/zot24/zskills --features skills-sh --force
export ZSKILLS_SKILLS_SH_API_KEY=sk_live_...
zskills marketplace add skills.sh
zskills search next-js # now federates to skills.sh
Once you’ve found the name, zskills install <name> flips it on (or appends [[skills]] to skills.toml for the declarative path).
Architecture
How zskills models the three sources of truth in your Claude Code install — across three primitives — and reconciles between them.
The three states
| State | Lives at | Authoritative for |
|---|---|---|
| Intent | skills.toml | What you want installed and enabled |
| Inventory | ~/.claude/plugins/installed_plugins.json + ~/.agents/skills/.zskills.json | What exists on disk |
| Activation | ~/.claude/settings.json → enabledPlugins; ~/.claude.json + per-scope MCP files → mcpServers | What’s currently running in a Claude Code session |
The first is what zskills writes from. The second and third are what Claude Code reads. zskills’ job is to keep all three consistent across all three primitives.
Three primitives
zskills models a single manifest over three first-class types of artifact, each with its own activation surface:
Claude Code plugins
- Distributed via marketplaces (Git repos with a
.claude-plugin/marketplace.json) - Installed under
~/.claude/plugins/cache/<marketplace>/<name>/<version>/ - Activation toggle in
settings.json→enabledPlugins - Inventory:
~/.claude/plugins/installed_plugins.json - Qualified name:
<plugin>@<marketplace>(matches Claude Code’s syntax)
Agent Skills (raw SKILL.md format)
- No marketplace — direct from any Git repo with
skills/<name>/SKILL.md - Installed under
~/.agents/skills/<name>/— the cross-client convention from agentskills.io, visible to Claude Code, Grok CLI, and any other compliant client. Override withAGENTS_HOMEfor tests. - No “enabled” flag — files-on-disk is the activation
- Inventory:
~/.agents/skills/.zskills.json(we own this; clients don’t write it)
MCP servers
-
No “installed bytes” of zskills’s own — the MCP server is just a process to spawn (stdio) or a URL to call (http/sse). Inventory and activation collapse into the same record.
-
Activation lives in
mcpServerskeys across multiple files, by scope:Scope File Notes managed/Library/Application Support/ClaudeCode/managed-settings.json(macOS) //etc/claude-code/managed-settings.json(Linux)IT-deployed. Read-only — syncnever writes here.local<cwd>/.claude.local/settings.jsonGitignored, personal+creds. project<cwd>/.mcp.json(recommended) OR<cwd>/.claude/settings.json(legacy)Team-shared via git. user~/.claude.json(whereclaude mcp add --scope userwrites) AND~/.claude/settings.jsonBoth are loaded; sync writes to ~/.claude.json.(attribution) Each enabled plugin’s plugin.json+ sibling.mcp.jsonPlugin-bundled entries — surfaced in listas★ plugin:<name>, never pruned by sync. -
The data model in
src/mcp.rs(McpServer { name, scope, transport, source, source_file }) is intentionally runtime-agnostic — when grok-cli / Codex adapters arrive, only the loader paths change; the struct stays.
All three are declared in skills.toml:
[[skills]] # plugin
name = "umbrel-app"
marketplace = "zot24-skills"
[[agent_skills]] # agent skill from a repo
source = "owner/repo"
[[agent_skills]] # local-only agent skill
name = "my-internal-tool" # name required; source omitted = no remote refresh
[[mcps]] # MCP server, stdio transport
name = "github"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
env = { GITHUB_TOKEN = "${GITHUB_TOKEN}" }
scope = "user"
[[mcps]] # MCP server, http transport
name = "linear"
url = "https://mcp.linear.app/mcp"
scope = "user"
Atomic JSON writes
Claude Code’s settings.json and installed_plugins.json carry more than the keys zskills cares about: hooks, permissions, env vars, MCP servers, etc. Losing those on round-trip would be catastrophic. So every write goes through:
- Read full document as
serde_json::Map<String, Value>(preserves unknown keys). - Mutate only the keys zskills owns (
enabledPlugins,extraKnownMarketplaces,plugins.*). - Serialize the entire map back to a temp file in the same directory.
std::fs::renameto the target path (atomic on Unix).
Same idea for skills.toml writes: we use toml_edit::DocumentMut so existing comments, blank lines, and table ordering survive. An append-only writer (manifest::append_agent_skill) checks for duplicates by exact (source, name) match before inserting.
How sync reconciles
┌─────────────────┐
│ skills.toml │ ← you edit this
└────────┬────────┘
│
▼
┌─────────────────┐
│ Manifest │ parsed model
└────────┬────────┘
│
┌────────────┴────────────┐
│ resolve name@marketplace │
│ via known_marketplaces │
└────────┬────────────────┘
▼
┌────────────────┐ ┌─────────────────────┐
│ desired_plugins│ │ desired_agent_skills│
└───────┬────────┘ └──────────┬──────────┘
│ │
▼ ▼
┌────────────────┐ ┌────────────────┐
│ current_plugins│ │ current_agent │ (from inventory + disk)
│ from settings │ │ skills │
└────────┬───────┘ └────────┬───────┘
│ │
▼ ▼
DIFF DIFF
│ │
┌────────┴───────┐ ┌────────┴────────┐
│ enable / disable│ │ install / remove│
│ via settings.json│ │ via git + copy │
└─────────────────┘ └─────────────────┘
sync is a single atomic apply: it computes the full plan first, prints it, and (unless --dry-run) applies the diff. The settings.json write happens once at the end, not per-skill.
Doctor’s three reconciliations
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ settings │◀──▶│ inventory │◀──▶│ disk │
│ enabledPlugins│ │ json files │ │ ~/.claude/ │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
└────────────────────┴───────────────────┘
│
doctor
Failure modes covered by doctor:
- Settings says enabled, inventory says nothing — broken plugin reference. Claude Code’s startup install will fix this on next launch, OR
doctor --fixremoves the flag. - Inventory says installed, marketplace gone — orphan from a
marketplace remove.doctorreports;purgecleans. - Agent skill inventory entry, no bytes on disk — someone
rm -rf’d the skill manually.doctor --fixdrops the inventory entry;syncwould reinstall from manifest. - MCP stdio command not found on
$PATH— flagged per-server;--fixis a no-op (we won’t install missing binaries). - MCP
${VAR}reference but the env var is unset — flagged per-server;--fixis a no-op (we won’t invent env vars). - MCP uses deprecated
ssetransport — flagged; the spec recommends migrating tohttp.
Doctor never deletes plugin bytes — that’s purge’s job. Doctor also never spawns or contacts an MCP server: runtime state (connection, latency, last error) is Claude Code’s domain. Replicating it here would risk divergent diagnoses (“zskills says fine, Claude says auth failed”). Static checks only.
Marketplace update strategies
Marketplaces installed via Claude Code can be either git working trees or unpacked tarballs (the latter is how claude-plugins-official ships, with a .gcs-sha cache marker file). zskills upgrade handles both:
┌─ is .git/ present?
│
┌─────┴──────┐
yes no
│ │
git pull resolve source from known_marketplaces.json
│
┌───────┴────────┐
github source other
│ │
fetch archive tarball error / skip
from GitHub HEAD branch
(falls back to main, master)
│
extract to sibling staging dir
│
atomic-ish rename swap (backup → rename → cleanup)
The tarball path uses reqwest blocking + flate2 + tar. Result: every marketplace recorded in known_marketplaces.json is updatable from one command regardless of how Claude Code originally installed it.
Remote-index marketplaces (cargo-feature-gated)
A third marketplace shape lives behind cargo features: remote indexes. Their known_marketplaces.json entry has source.source = "remote-index" and a source.url, but no installLocation — there’s no git clone. search and install dispatch to driver code that talks to the index’s HTTP API.
known_marketplaces.json:
{
"skills.sh": {
"source": { "source": "remote-index", "url": "https://skills.sh" },
"autoUpdate": false
}
}
Each driver lives behind its own cargo feature (today: skills-sh). Default builds don’t compile the driver in — marketplace add skills.sh errors with “unrecognized marketplace source”. With the feature, add accepts the special name, search federates to the API when ZSKILLS_SKILLS_SH_API_KEY is set, and install falls through to the index when local plugin resolution misses (routing through the existing agent-skill install path).
The non-feature build still tolerates remote-index entries that might be in known_marketplaces.json from a feature-built version: list shows them with a [remote-index] tag, update skips them, remove works as expected. This is a forward-compatibility hedge, not a runtime dispatch path.
See README → Roadmap: third-party marketplace drivers for when the cargo-feature pattern is the wrong shape and a subprocess plugin protocol takes over.
Ownership tracking for agent skills
Agent skill inventory entries carry a source field that’s typed by prefix:
| Inventory source | Means | Refresh via |
|---|---|---|
owner/repo (or git URL) | Git-cloned source | git pull cached clone + re-copy |
npm:<pkg> | npm-installed | npm install -g <pkg> |
local | Local-only, never refreshed | (manual) |
The manifest entry’s claims glob list bridges the gap when an npm package overwrites files in-place: after the install command runs, every ~/.agents/skills/<name>/ directory matching any glob in claims is tagged with the entry’s source. This is the only way to claim “I own these 66 pre-existing directories” without re-installing fresh.
sync uses the same ownership signals (source match + npm tag + claims glob) when deciding whether a skill not in the desired set should be removed — preventing accidental deletion of skills owned by a [[agent_skills]] entry.
Why git is shelled out
zskills runs git clone --depth 1 and git pull --ff-only via std::process::Command instead of linking libgit2. Reasons:
- Reuses your existing credential helpers (SSH keys, gh CLI, OS keychain) for free.
- Smaller binary (no libgit2 dependency).
- Sparse-checkout and partial-clone work correctly without bespoke handling.
- Errors come back as plain git output, which is what users already know how to read.
The trade-off is one process spawn per fetch, which is fine: marketplace updates and agent-skill installs are rare events.
Path resolution
~/.claude.json MCP servers (user scope, where `claude mcp` writes)
~/.claude/ ($CLAUDE_HOME)
├── settings.json activation; may also carry mcpServers
├── plugins/
│ ├── installed_plugins.json plugin inventory
│ ├── known_marketplaces.json tap registry
│ ├── marketplaces/<name>/ tap clones
│ └── cache/<mp>/<name>/<v>/ plugin bytes
│ └── .claude-plugin/plugin.json plugin manifest (may declare mcpServers)
│ └── .mcp.json plugin-bundled MCP servers (sibling shape)
└── skills/
├── .zskills.json agent skill inventory
└── <name>/SKILL.md agent skill bytes
<cwd>/.mcp.json project-scope MCPs (team-shared, git-committed)
<cwd>/.claude/settings.json legacy project-scope MCPs + enabledPlugins
<cwd>/.claude.local/settings.json local-scope MCPs (gitignored, personal+creds)
/Library/Application Support/ClaudeCode/managed-settings.json managed MCPs (macOS, IT-deployed, read-only)
/etc/claude-code/managed-settings.json managed MCPs (Linux)
Override with $ZSKILLS_MANAGED_SETTINGS.
$XDG_CACHE_HOME/zskills/agent-skills/<owner>-<repo>/ source clones
$XDG_CONFIG_HOME/zskills/skills.toml manifest (fallback)
./skills.toml manifest (project-scope wins, NOT auto-loaded since v0.5.1)
CLAUDE_HOME env var overrides ~/.claude. XDG_CACHE_HOME and XDG_CONFIG_HOME are respected. On macOS, zskills uses ~/.config/zskills/ for the manifest by default (matching cargo/starship/atuin) rather than the platform’s ~/Library/Application Support/ location.
Troubleshooting
“no skills.toml found”
Error: no skills.toml found (looked in ./ and ~/.config/zskills/)
zskills looks for the manifest in this order:
$PWD/skills.toml(if you’re inside a project that vendors a manifest)$XDG_CONFIG_HOME/zskills/skills.toml~/.config/zskills/skills.toml- The platform default from
dirs::config_dir()(~/Library/Application Support/zskills/skills.tomlon macOS)
Fix: create the file at ~/.config/zskills/skills.toml, or pass --file <path> explicitly.
“skill X is ambiguous — qualify with @marketplace”
Two registered marketplaces both expose a skill with the same name. zskills can’t pick one. Qualify it:
zskills install firecrawl@zot24-skills # instead of just firecrawl
“enabled but NOT installed (broken)”
doctor is flagging an enabledPlugins entry that has no corresponding inventory record. Three legitimate causes:
- You just ran
syncand haven’t restarted Claude Code yet. This is the normal post-sync state. Restart Claude Code; it’ll fetch the bytes on startup. Doctor will go clean. - The plugin was removed from its marketplace upstream. Pick a replacement or
zskills disable <name>to silence the warning. - The marketplace tap was unregistered (
marketplace remove).zskills doctor --fixwill drop the orphan reference fromenabledPlugins.
Sync deleted agent skills I didn’t expect to lose
A v0.5 incident: running zskills sync inside a repo that ships its own skills.toml (like the zot24/skills marketplace) destructively re-applied that manifest against your user-scope state. v0.5.1+ defaults prevent this:
./skills.tomlno longer auto-loads. Sync without--fileuses only~/.config/zskills/skills.toml. If./skills.tomlexists, sync prints a yellow warning pointing at it.syncnever deletes bytes by default. Removal of agent skills no longer in the manifest requires--prune. Without it, they’re reported asskipand left intact.
If you lost a skill before these defaults, check whether the skill was committed in any project under your tree:
find ~/Desktop/code -path '*/.claude/skills/<name>/SKILL.md' 2>/dev/null
# Or via git history (any project that had it)
for p in ~/Desktop/code/*; do
[ -d "$p/.git" ] || continue
sha=$(git -C "$p" log --all --oneline -- ".claude/skills/<name>/SKILL.md" | head -1 | awk '{print $1}')
[ -n "$sha" ] && echo "$p: $sha"
done
Then restore via git checkout <sha> -- .claude/skills/<name>/ in the relevant project and cp -R to ~/.agents/skills/ (the cross-client user-scope location).
Sync wants to disable plugins I want to keep
If your skills.toml doesn’t list a plugin, sync will flip it off because the manifest is declarative — it represents your complete intent. Two ways to handle:
A) Add the plugin to your manifest so sync stops touching it:
[[skills]]
name = "rust-analyzer-lsp"
marketplace = "claude-plugins-official"
B) Edit ~/.claude/settings.json to remove the unwanted enabledPlugins entry, so it no longer needs an enable=false flip on every sync.
(A sync --no-prune flag is on the roadmap for users who prefer additive-only behavior; for now, declare everything you want.)
migrate-skill says “content differs across projects”
Different projects have edited their copy of the same-named skill, so the bytes are no longer identical. zskills hashes each project’s SKILL.md tree and groups by hash:
! content differs across projects — using the first as canonical:
[4e483861] 7 project(s)
/path/to/project-a
/path/to/project-b
...
[5f9d37fb] 1 project(s)
/path/to/project-z
The first project (alphabetical) wins as canonical. If you want a different project’s version to win, either:
- Re-run
migrate-skillfrom inside that project first, OR - Manually copy the desired version to
~/.agents/skills/<name>/beforemigrate-skill(which will detect it as “already at user scope” and overwrite with canonical only if you proceed — so cancel first).
A --canonical <project-path> flag is reasonable for v0.4 if this becomes common pain.
npm agent skill says “no new skills discovered”
Some npm packages place their skill files via a separate setup CLI (e.g., npx <pkg> install), not via npm’s own postinstall hook. If npm install -g <pkg> alone doesn’t write to ~/.agents/skills/, the diff-before-after returns empty and zskills sees nothing to claim.
Two fixes:
- Add a
claimsglob so zskills retroactively claims pre-existing directories that match:[[agent_skills]] npm = "get-shit-done-cc" claims = ["gsd-*"] - Set
install_cmdto whatever the package’s actual installer is:[[agent_skills]] npm = "some-tool" install_cmd = "npx some-tool install"
If you’re not sure where a package writes its skills, run it once manually, then check ~/.agents/skills/ (or ~/.claude/skills/ if the package targets the legacy Claude-specific path) and pick a claims pattern that covers them.
sync clones repeatedly / is slow on Agent Skills
The first sync clones every [[agent_skills]] source repo into ~/.cache/zskills/agent-skills/. Subsequent syncs do git pull --ff-only against the cache — fast. If you’re seeing repeated full clones, check that the cache directory exists and is writable:
ls -la ~/.cache/zskills/agent-skills/
You can wipe and rebuild the cache safely; it’s reproducible:
rm -rf ~/.cache/zskills
zskills sync
Plugin bytes seem stale / not reflecting upstream
Marketplace caches need an explicit refresh:
zskills marketplace update # all marketplaces
zskills marketplace update zot24-skills # one
Restart Claude Code afterward so it picks up the new versions.
Manifest entries vanished after I edited skills.toml manually
zskills writes via toml_edit and never deletes user content. If entries are gone, check:
- Did you save the file?
- Are you editing the right one?
zskills sync --dry-runprints the manifest path at the top. - Did
git checkoutor another tool overwrite it?
“agent skill in inventory, missing on disk”
You deleted ~/.agents/skills/<name>/ manually. Two ways to recover:
# Re-fetch from upstream (if there's a source in the manifest)
zskills sync
# Or just drop the inventory entry
zskills doctor --fix
“unrecognized marketplace source: skills.sh”
You ran zskills marketplace add skills.sh against a default build. The skills.sh driver is gated behind the skills-sh cargo feature and not compiled into vanilla binaries. Reinstall with the feature on:
cargo install --git https://github.com/zot24/zskills --features skills-sh --force
Then set ZSKILLS_SKILLS_SH_API_KEY (get one from skills.sh/account) and retry the marketplace add. Without the env var, search will skip skills.sh with a one-line hint and install will not fall through to it.
“skills.sh rejected the API key in ZSKILLS_SKILLS_SH_API_KEY (HTTP 401)”
The key is wrong, revoked, or expired. Generate a fresh one at skills.sh/account, update your shell rc, and re-source. The whole skills.sh API is gated — there’s no unauthenticated fallback today.
Cargo install fails with “edition2024 not stabilized”
You’re on Rust < 1.85. zskills requires Rust 1.85 or newer (transitive idna_adapter dep). Update:
rustup update stable
cargo install --git https://github.com/zot24/zskills --force
doctor flags command not found on $PATH for an MCP
The MCP server’s command (stdio transport) doesn’t resolve via which-style lookup. Two fixes:
- Install the binary. For npx-launched servers,
npxitself is on PATH but a global likenodeis required first. - If the config has a non-absolute name pointing at something only available in a Node/Python project shell, switch to an absolute path or wrap with
npx -y <package>.
zskills never spawns the server itself; the check is purely “is the file findable.” So this catches the common “I’m not in the right shell environment” case before Claude Code does.
doctor says env var X is referenced but not set
An MCP entry references ${X} in env, headers, or args, but X isn’t defined in your shell. Export it (and put it in your shell rc for persistence):
export GITHUB_TOKEN=ghp_...
zskills extracts ${VAR} references from values without storing the values themselves — only the variable names land in our data structures. So this check is safe even if you paste a literal secret elsewhere in the same entry.
doctor says transport sse is deprecated
The MCP spec marks sse as legacy in favor of http. To migrate an existing entry, edit its config:
-{ "type": "sse", "url": "https://x.example/sse" }
+{ "type": "http", "url": "https://x.example/http" }
(Check the server’s docs for the correct HTTP endpoint — it usually differs from the SSE endpoint.) If you manage MCPs declaratively, change transport = "http" (or remove transport to let zskills infer it from url) and run zskills sync.
sync overwrote my hand-edited MCP entry
sync treats skills.toml as the source of truth: any MCP in the manifest is rewritten to match the manifest’s values on every run. If you customized an entry in ~/.claude.json directly, those changes are lost on the next sync.
Two options:
- Move the customization into the manifest so it’s tracked and reproducible.
- Remove the entry from
skills.toml— then the manifest doesn’t claim ownership and sync leaves it alone (and without--prune, never removes it).
MCP entry didn’t appear after zskills sync — where did it go?
Check the scope you targeted. zskills writes per-scope:
scope = "user"→~/.claude.json(NOT~/.claude/settings.json— that file isn’t whereclaude mcpwrites today)scope = "project"→<cwd>/.mcp.jsonscope = "local"→<cwd>/.claude.local/settings.json
Verify with zskills list --paths. If the entry shows up there but Claude Code doesn’t see it, restart Claude Code (or use its /mcp flow) so it re-reads the settings file.
zskills list doesn’t show an MCP that exists in my managed-settings file
It should, with scope = managed. If it doesn’t:
- Confirm the file exists at
/Library/Application Support/ClaudeCode/managed-settings.json(macOS) or/etc/claude-code/managed-settings.json(Linux). - Confirm
mcpServersis a top-level key in that file. - Set
ZSKILLS_MANAGED_SETTINGS=<absolute-path>to override the auto-discovered path (useful when corp IT puts it elsewhere, or for CI where you want to skip the probe).
Managed scope is read-only by design — zskills sync never writes to it, even with --prune.
I want to remove an MCP from one scope without touching others
sync --prune removes everything not in the manifest, across every writable scope. For a one-MCP one-scope removal today, edit the relevant settings file directly (or use claude mcp remove if it targets the scope you want). A finer-grained removal API is on the roadmap.
How do I uninstall zskills entirely?
cargo uninstall zskills
rm -rf ~/.cache/zskills
# Manifest stays — it's just a config file. Delete if you don't want it:
rm ~/.config/zskills/skills.toml
# Agent skill inventory stays too — Claude Code itself doesn't read it, but if
# you reinstall zskills later it'll resume from this state. Delete if you want
# a clean slate:
rm ~/.agents/skills/.zskills.json
Plugins remain in ~/.claude/plugins/ and Agent Skills remain in ~/.agents/skills/ — they’re managed by Claude Code / the agent runtime, not zskills. Uninstall plugins via Claude Code’s /plugin uninstall or by deleting the relevant directories under ~/.claude/plugins/; Agent Skills are just directories — rm -rf removes them.
Changelog
All notable changes to this project are documented here. The format is based on Keep a Changelog, and this project adheres to Semantic Versioning. Releases from this point forward are managed by release-please based on Conventional Commits.
0.7.0 (2026-05-16)
Features
- [[mcps]] manifest support + sync reconciliation (#13) (882bfe5)
- add -i interactive mode to install, search, remove (#8) (33ae160)
- doctor statically validates MCP servers (#11) (8295670)
- prefer fzf for interactive pickers, fall back to dialoguer (#9) (1e13340)
- zskills list –paths shows on-disk location for each entry (#15) (d1dcc04)
- zskills list aggregates MCP servers across all scopes (#10) (b13ca45)
Documentation
- reposition as multi-runtime, not Claude-only (#12) (b8429ac)
- site: add OG/Twitter card image for social previews (#6) (631a426)
0.6.0 (2026-05-13)
Features
Bug Fixes
- list: cleaner group header — bare name + arrow source kind (ea68a63)
- sync: honor npm/claims ownership; skip already-present source entries (be3e187)
- sync: prevent data loss via safer defaults (b97c721)
Documentation
- add mdBook static site + GitHub Pages deploy (4a12893)
- cover v0.5/v0.5.1 features in depth (cd802ef)
- document v0.6 search command and skills-sh optional feature (#5) (793ec61)
- site: add CNAME for zskills.zot24.com (a82cf7e)
- site: mirror .md files + add llms.txt and llms-full.txt (4e0be13)
0.5.0 (2026-05-13)
Features
- tarball update for non-git marketplaces (db6e370)
- v0.5 — upgrade command, npm sources, grouped list (fb37468)
Bug Fixes
- claims field + quiet git output + skip non-git marketplaces (b424711)
0.4.0 (2026-05-12)
Features
- initial v0.1 — package manager for Claude Code skills (c03fcea)
- v0.2 — Agent Skills support (~/.claude/skills/) (fcd7773)
- v0.3 — migrate-skill, migrate-all, optional source (d4144d9)
Bug Fixes
- manifest: use XDG ~/.config across platforms, not platform default (25e9b10)
Documentation
- release-please + CHANGELOG + docs/ folder (bdb002c)
0.3.0 - 2026-05-12
Features
- migrate-skill: promote ONE agent skill across every project under a tree. Hashes each project’s copy to detect content divergence, picks the first as canonical, copies to user scope, optionally removes from all projects, appends a
[[agent_skills]]entry to the manifest. - migrate-all: interactive sweep over a tree. Groups by skill name, sorts by occurrence count, prompts per skill (promote? source? remove from projects?).
--threshold Nfilters;-y/--yesaccepts defaults. - Optional source on
[[agent_skills]]entries. Aname-only entry declares a local-only skill: tracked in inventory but not refreshed from a remote bysync. - Manifest writes preserve formatting: append uses
toml_edit::DocumentMutso existing comments/structure inskills.tomlsurvive round-trip.
Internal
- Added
dialoguerfor interactive prompts. - 13/13 integration tests passing, including new coverage for
migrate-skill.
0.2.0 - 2026-05-12
Features
- Agent Skills support (raw
SKILL.mdformat under~/.claude/skills/). New[[agent_skills]]manifest section withsource(owner/repo or git URL) and optionalname. - Source repos cached at
$XDG_CACHE_HOME/zskills/agent-skills/<owner>-<repo>/. - Own inventory at
~/.claude/skills/.zskills.json(since Claude Code’sinstalled_plugins.jsondoesn’t cover Agent Skills). syncapplies both[[skills]]and[[agent_skills]]in a single pass.listshows plugins AND agent skills; flags untracked agent skills.doctordetects orphans across all three states (settings, inventory, disk).scanwalks.claude/skills/<name>/SKILL.mddirectories at project scope (default depth bumped 4 → 6).migratealso promotes.claude/skills/directories to user scope.
0.1.0 - 2026-05-12
Initial release — package manager for Claude Code plugins.
Features
- Commands:
list,install,remove,purge,enable,disable,sync,update,doctor,scan,migrate,marketplace add|remove|list|update. - Atomic JSON round-trip preserves all unknown fields in
~/.claude/settings.json(hooks, permissions, env, etc.). - Multi-marketplace support with
name@marketplacequalification matching Claude Code’s syntax. - Declarative
skills.tomlmanifest auto-discovered from CWD or~/.config/zskills/. - Scan + migrate for promoting project-scope skills to user scope.
- Git shelled out (no
libgit2bundling); rustls TLS; single static binary. - 8 integration tests using
assert_cmd+tempfile-isolatedCLAUDE_HOME.