Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

RuntimeStatusWhat’s managed
Claude Code✅ supportedplugins (via marketplaces), Agent Skills (~/.claude/skills/), MCP servers (all five known scopes)
Grok-based CLIs (e.g. grok-cli)plannedskills (~/.agents/skills/), MCP servers
Codexplannedskills, MCP servers
xAI’s official CLIplannedonce 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, or name@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=1 to disable, or pipe through cat if you need plain text.
  • CLAUDE_HOME=/custom/path overrides ~/.claude for testing.
  • XDG_CONFIG_HOME and XDG_CACHE_HOME are 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]
FlagDefaultDescription
--jsonoffEmit a machine-readable JSON document for scripting
-v, --verboseoffExpand grouped agent skills (show every skill name in each source group)
--pathsoffShow 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.json and ~/.claude/settings.json — user scope
  • <cwd>/.mcp.json and <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.json and 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.json at repo root) — prints a redirect: use zskills marketplace add <owner/repo> and installs nothing from this repo.
  • MCP servers (.mcp.json or mcpServers in plugin.json) — surfaced as a hint; not auto-installed (use [[mcps]] in skills.toml + zskills sync).
FlagDefaultDescription
-i, --interactiveoffFor 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.
--alloffFor 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 repoDefault (no flags)With -iWith --all
0errorerrorerror
1install itinstall itinstall it
2–5install allpickerinstall all
> 5abort + summary + next-step hintpickerinstall 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>...
FlagDefaultDescription
-i, --interactiveoff(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]
FlagDefaultDescription
--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-runoffPrint the plan; do not write
--pruneoffAllow 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/.
--adoptoffInverse 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:

  1. For each [[skills]] entry: resolve name@marketplace, write to enabledPlugins. Entries currently enabled but not in the manifest get flipped off.
  2. For each [[agent_skills]] entry: if source is present, clone/pull and copy skills/<name>/ to ~/.agents/skills/. If npm is present, run npm install -g --no-fund --no-audit <pkg> (or install_cmd), then claim all matching claims globs. If neither is present (just name), register the existing on-disk skill in inventory without fetching anything.
  3. Agent skills tracked in inventory but missing from the manifest are reported. With --prune they’re deleted; with --adopt they’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 with name + marketplace.
  • Agent skill in inventory but not in manifest → new [[agent_skills]] row. The source / npm / name fields are reconstructed from the inventory tag (local becomes a name-only entry, npm:pkg becomes npm = "pkg", anything else becomes source = "...").
  • MCP server configured but not in manifest → new [[mcps]] row with full transport details (command/args/env for stdio, url/headers for http/sse), scope preserved. 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 kindWhat 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"
FieldPurpose
name (required)MCP server name. Becomes the key under mcpServers.
commandStdio: the executable to run. Required for stdio transports.
argsStdio: list of args. ${VAR} refs are allowed and recommended for credentials.
envStdio: env-var map. Values should use ${VAR} refs.
urlHTTP/SSE: server URL.
headersHTTP/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:

ScopeFileNotes
user~/.claude.jsonThe file claude mcp add --scope user writes to. Wrapped JSON ({"mcpServers": {...}}).
project<cwd>/.mcp.jsonSpec-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.jsonGitignored personal+creds file. Wrapped.

Sync semantics.

  • sync installs 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 as skip — 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/doctor but sync ignores 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"
FieldPurpose
sourceGit source: owner/repo (GitHub) or full git URL. sync/upgrade clone/pull and copy.
npmnpm package name. sync/upgrade runs npm install -g <pkg>.
install_cmdCustom installer command — overrides the default npm install -g. Used for packages with their own setup CLI.
nameOptional. 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.
claimsGlob 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:

  1. Plugins enabled in enabledPlugins but not present in installed_plugins.json (broken references — Claude Code will fetch on next start).
  2. Plugins in inventory whose marketplace tap is no longer registered.
  3. Agent skills tracked in inventory but missing from ~/.agents/skills/ on disk.
  4. MCP server issues — static checks against every server returned by list:
    • stdio: command must resolve on $PATH (uses which-style lookup).
    • any transport: every ${VAR} reference in env (stdio) or headers (http/sse) must be set in the user’s environment.
    • sse transport: flagged as deprecated; the spec recommends migrating to http.
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]
FlagDefaultDescription
<path>.Tree to walk
--depth N6Maximum directory recursion depth
--jsonoffMachine-readable output

Detects two patterns:

  • .claude/settings.json or .claude/settings.local.json with enabledPlugins / 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]
FlagDefaultDescription
--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-alloffDelete the skill’s .claude/skills/<name>/ from every matched project
--dry-runoffPrint 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]
FlagDefaultDescription
<dir>requiredTree to walk
--threshold N2Only consider skills appearing in ≥N projects
--yes, -yoffSkip prompts; accept defaults (no source, keep project copies)
--dry-runoffPrint planned action per skill; do not write

For each duplicated skill above the threshold, prompts:

  1. “Promote ‘’ to user scope? [Y/n]”
  2. “Upstream source [owner/repo, URL, or blank for local-only]:”
  3. “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.

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]
FlagDefaultDescription
--limit <n>25Maximum results per marketplace
--jsonoffEmit results as a JSON array for scripting
-i, --interactiveoffAfter 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.

FeatureWhat it addsHow to enable
skills-shFederated 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 versions
  • git pull every git-sourced agent skill and re-copy bytes
  • npm update -g every npm-sourced agent skill (and re-claim via claims globs)

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 --fix removes 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. Run zskills purge <name> to clean.
  • “agent skill tracked in inventory but missing on disk” — someone deleted ~/.claude/skills/<name>/ manually. zskills doctor --fix removes the stale inventory entry, or re-run sync to 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:

  1. See what’s where:

    zskills list --paths
    
  2. Manually transcribe each into ~/.config/zskills/skills.toml as [[mcps]] entries. Use ${VAR} refs for any credentials — never paste literal tokens.

  3. 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: command must resolve on $PATH (e.g. npx is installed, the package would be reachable).
  • any transport: every ${VAR} referenced in env / headers / args must 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

StateLives atAuthoritative for
Intentskills.tomlWhat you want installed and enabled
Inventory~/.claude/plugins/installed_plugins.json + ~/.agents/skills/.zskills.jsonWhat exists on disk
Activation~/.claude/settings.jsonenabledPlugins; ~/.claude.json + per-scope MCP files → mcpServersWhat’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.jsonenabledPlugins
  • 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 with AGENTS_HOME for 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 mcpServers keys across multiple files, by scope:

    ScopeFileNotes
    managed/Library/Application Support/ClaudeCode/managed-settings.json (macOS) / /etc/claude-code/managed-settings.json (Linux)IT-deployed. Read-only — sync never 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 (where claude mcp add --scope user writes) 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 list as ★ 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:

  1. Read full document as serde_json::Map<String, Value> (preserves unknown keys).
  2. Mutate only the keys zskills owns (enabledPlugins, extraKnownMarketplaces, plugins.*).
  3. Serialize the entire map back to a temp file in the same directory.
  4. std::fs::rename to 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:

  1. Settings says enabled, inventory says nothing — broken plugin reference. Claude Code’s startup install will fix this on next launch, OR doctor --fix removes the flag.
  2. Inventory says installed, marketplace gone — orphan from a marketplace remove. doctor reports; purge cleans.
  3. Agent skill inventory entry, no bytes on disk — someone rm -rf’d the skill manually. doctor --fix drops the inventory entry; sync would reinstall from manifest.
  4. MCP stdio command not found on $PATH — flagged per-server; --fix is a no-op (we won’t install missing binaries).
  5. MCP ${VAR} reference but the env var is unset — flagged per-server; --fix is a no-op (we won’t invent env vars).
  6. MCP uses deprecated sse transport — flagged; the spec recommends migrating to http.

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 sourceMeansRefresh via
owner/repo (or git URL)Git-cloned sourcegit pull cached clone + re-copy
npm:<pkg>npm-installednpm install -g <pkg>
localLocal-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:

  1. $PWD/skills.toml (if you’re inside a project that vendors a manifest)
  2. $XDG_CONFIG_HOME/zskills/skills.toml
  3. ~/.config/zskills/skills.toml
  4. The platform default from dirs::config_dir() (~/Library/Application Support/zskills/skills.toml on 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:

  1. You just ran sync and 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.
  2. The plugin was removed from its marketplace upstream. Pick a replacement or zskills disable <name> to silence the warning.
  3. The marketplace tap was unregistered (marketplace remove). zskills doctor --fix will drop the orphan reference from enabledPlugins.

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:

  1. ./skills.toml no longer auto-loads. Sync without --file uses only ~/.config/zskills/skills.toml. If ./skills.toml exists, sync prints a yellow warning pointing at it.
  2. sync never deletes bytes by default. Removal of agent skills no longer in the manifest requires --prune. Without it, they’re reported as skip and 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-skill from inside that project first, OR
  • Manually copy the desired version to ~/.agents/skills/<name>/ before migrate-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:

  1. Add a claims glob so zskills retroactively claims pre-existing directories that match:
    [[agent_skills]]
    npm = "get-shit-done-cc"
    claims = ["gsd-*"]
    
  2. Set install_cmd to 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-run prints the manifest path at the top.
  • Did git checkout or 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, npx itself is on PATH but a global like node is 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 where claude mcp writes today)
  • scope = "project"<cwd>/.mcp.json
  • scope = "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 mcpServers is 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

  • add search command and optional skills.sh driver (#4) (f65573c)

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 N filters; -y/--yes accepts defaults.
  • Optional source on [[agent_skills]] entries. A name-only entry declares a local-only skill: tracked in inventory but not refreshed from a remote by sync.
  • Manifest writes preserve formatting: append uses toml_edit::DocumentMut so existing comments/structure in skills.toml survive round-trip.

Internal

  • Added dialoguer for 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.md format under ~/.claude/skills/). New [[agent_skills]] manifest section with source (owner/repo or git URL) and optional name.
  • Source repos cached at $XDG_CACHE_HOME/zskills/agent-skills/<owner>-<repo>/.
  • Own inventory at ~/.claude/skills/.zskills.json (since Claude Code’s installed_plugins.json doesn’t cover Agent Skills).
  • sync applies both [[skills]] and [[agent_skills]] in a single pass.
  • list shows plugins AND agent skills; flags untracked agent skills.
  • doctor detects orphans across all three states (settings, inventory, disk).
  • scan walks .claude/skills/<name>/SKILL.md directories at project scope (default depth bumped 4 → 6).
  • migrate also 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@marketplace qualification matching Claude Code’s syntax.
  • Declarative skills.toml manifest auto-discovered from CWD or ~/.config/zskills/.
  • Scan + migrate for promoting project-scope skills to user scope.
  • Git shelled out (no libgit2 bundling); rustls TLS; single static binary.
  • 8 integration tests using assert_cmd + tempfile-isolated CLAUDE_HOME.