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:

[defaults]
harnesses = ["claude", "pi", "hermes", "kimi", "grok", "codex"]
mcp_harnesses = ["claude", "pi", "hermes", "kimi", "grok", "codex"]

# 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"

# Many skills from one repo: `skills` is the plural of `name`, so one stanza
# carries one `source`. An entry declares `name` or `skills`, not both.
[[agent_skills]]
source = "mattpocock/skills"
skills = ["prototype", "research", "tdd", "wayfinder"]

# 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 skill upgrade                   # refresh Agent Skills from origin
zskills list                            # see what's installed (prints which harnesses can see each name)
zskills doctor                          # reconcile disk ↔ inventory ↔ settings

Commands

zskills list [-v]
zskills plugin install|remove|purge|enable|disable
zskills skill install|remove|upgrade
zskills mcp add|remove
zskills sync [--file f.toml] [--prune]
zskills doctor [--fix]
zskills scan|migrate|migrate-skill|migrate-all|search
zskills marketplace add|remove|list|update|add-recommended

Bare install/remove/purge/enable/disable/update/upgrade exit 2.

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

  • CLI group to manifest key: plugin → [[skills]]; skill → [[agent_skills]]; mcp → [[mcps]].
  • <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.
  • AGENTS_HOME overrides ~/.agents.
  • 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. Each plugin and Agent Skill line names which harnesses can see it: claude, pi, hermes, kimi, grok, codex. A Claude-only plugin is not a Pi install. Harnesses that have no cited skill root are listed as skipped (unsupported), not as present.

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 plugin install <name>                       # plugin from a registered marketplace
zskills plugin install <name>@<marketplace>         # qualified plugin
zskills plugin install <name> --harness pi,grok     # copy the plugin's skill trees into the shared Agent Skill hub
zskills skill install <owner>/<repo>                # Agent Skill(s) from a git repo
zskills skill install <git-url>                     # same, for https://, git@, file://
zskills skill install <owner>/<repo> --path REL     # Agent Skills under a non-conventional root
zskills plugin install -i                           # interactive picker over marketplace plugins

Plugin path (<name> / <name>@<marketplace>). Resolves against registered marketplaces. --harness is a one-shot override of [defaults].harnesses (comma-separated: claude, pi, hermes, kimi, grok, codex). Missing [defaults] and missing --harness keep 1.0 behaviour: Claude only. claude in the set flips enabledPlugins in ~/.claude/settings.json and materializes the marketplace plugin. pi / grok / codex / hermes copy each of the plugin’s skill trees into the shared hub ~/.agents/skills/<name>/ so SKILL.md sits at the scan root. Which trees ship is the plugin author’s call: when .claude-plugin/plugin.json carries a skills array, zskills resolves every entry against the plugin root and ships exactly those, the same array Claude Code reads — an empty array ships nothing. Without the array, zskills walks skills/ the way it surveys any Agent Skill root: a child holding a SKILL.md is a skill, a child without one is a category read one level deeper, so skills/<category>/<name>/SKILL.md works too. A declared entry that resolves outside the plugin root is refused and the whole plugin fails. Pi and Grok scan the hub itself; Codex and Hermes are symlinked from it into ~/.codex/skills/<name> and ~/.hermes/skills/<category>/<name>. Copies into the hub are real directories — a plugin cache path is version-stamped, so a link there would break on upgrade. kimi prints unsupported and does not invent a folder.

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 or .agents/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.
  • --path REL — walk REL inside the clone instead of those default roots. If REL/SKILL.md exists, that directory is the skill. Else every child with SKILL.md is a skill. --path is sparse-intent: a repo with .claude-plugin/marketplace.json still installs from that path instead of redirecting to marketplace add. After a successful install, zskills appends an [[agent_skills]] row: marketplace when the spec matches a registered marketplace (and the copy reuses that clone), else source.
  • Marketplace (.claude-plugin/marketplace.json at repo root) — without --path / --skill / -i, 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).
--skill <name>noneFor repo specs only: install exactly one skill by name (the manifest name field) — the non-interactive counterpart to -i for multi-skill repos. Bypasses the >5-skill size policy (the selection is explicit) and conflicts with --all.
--path <rel>noneFor repo specs only: relative path inside the clone to a directory of Agent Skills. Replaces the default walk of .agents/skills and skills/. Sparse-intent like --skill / -i: do not redirect a marketplace root. Writes [[agent_skills]] with marketplace or source. Ban .., absolute form, \, :.
--harness <list>[defaults].harnesses, or Claude onlyComma-separated harness names. One-shot override. Not --to.

Repo-path count behavior:

Skills in repoDefault (no flags)With -iWith --allWith --skill <name>
0errorerrorerrorerror
1install itinstall itinstall itinstall if it matches
2–5install allpickerinstall allinstall just that one
> 5abort + summary + next-step hintpickerinstall allinstall just that one

Sparse installs (root-level SKILL.md). A repo whose SKILL.md sits at the repo root inside a larger project (source code, lockfiles, websites…) is installed sparsely: only SKILL.md, the conventional skill dirs (references/, assets/, scripts/), and relative paths that SKILL.md links to are copied out — never the whole source tree, and never .git/. Installs from the skills/<name>/SKILL.md layout copy the skill subdirectory as before. Legacy full-repo installs are flagged by zskills doctor and slimmed on the next zskills skill upgrade (or doctor --fix).

remove / purge

remove is apt-style: disable in enabledPlugins, drop the inventory entry, and drop the matching [[skills]] row from the manifest so sync does not re-enable the plugin. Bytes stay on disk so re-enabling is instant. purge does the same plus deletes the bytes from ~/.claude/plugins/cache/.

zskills plugin remove <name>...
zskills plugin remove -i
zskills plugin 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 plugin enable  <name>...
zskills plugin disable <name>...

plugin enable fails if the plugin is not in enabledPlugins or the plugin inventory. plugin disable keeps bytes and inventory; use plugin purge to delete bytes.

skill

Agent Skills live in ~/.agents/skills/. The group writes [[agent_skills]], not [[skills]].

zskills skill install <owner/repo> [--path REL] [--skill NAME | --all | -i]
zskills skill remove <name> [--force] [--file path]
zskills skill upgrade [<name>...]

--path REL installs Agent Skills that do not live at .agents/skills or skills/. Example: zskills skill install nvk/llm-wiki --path plugins/llm-wiki-opencode/skills --skill wiki-manager. That command does not redirect to marketplace add. After install, the manifest row uses marketplace when the spec matches a registered marketplace, else source.

skill remove deletes bytes. It is not plugin remove. --force is required when inventory source starts with plugin:, or when a source-only [[agent_skills]] row owns the name. A marketplace: hub copy of the same name removes without --force and without disabling the plugin.

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 [[marketplaces]] entry with repo or url that is not yet registered: clone the source and write known_marketplaces.json plus extraKnownMarketplaces (same as marketplace add). This runs before plugin resolve, so a fresh machine can recreate the clone. Unqualified [[skills]] names (name only) are resolved against the map after that clone, so they can match a marketplace that did not exist at plan time.
  2. For each [[skills]] entry: resolve name@marketplace. If harnesses (or [defaults].harnesses) contains claude, write to enabledPlugins. Entries currently enabled but not in the manifest get flipped off. Hub-backed harnesses (pi, grok, codex) copy nested skills/<name>/ trees into ~/.agents/skills/ in the same pass. sync refuses to write an enabledPlugins key whose marketplace is not registered. It reports the unresolved plugins and exits non-zero instead of printing ✓ applied. Hub copies for those plugins are skipped too.
  3. For each [[agent_skills]] entry: classify the row by kind. source clones/pulls the Agent Skill cache. marketplace + path copies from the registered marketplace clone (hard error if the clone is missing — this is not a local-only row). If path is set, copy from that relative directory inside the clone instead of the default skill roots. If npm is present, run npm install -g --no-fund --no-audit <pkg> (or install_cmd), then claim all matching claims globs. If none of source / marketplace / npm is present (just name), register the existing on-disk skill in inventory without fetching anything. Nested plugin hub copies skip names claimed by [[agent_skills]]. Same-marketplace plugin: hub copies are taken over. A copy or resolve error increments a failure count: sync then skips ✓ applied. and exits non-zero.
  4. 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:

  • Registered marketplace not in manifest → new [[marketplaces]] row with name plus repo (owner/repo) or url (non-GitHub git source). Source comes from known_marketplaces.json / extraKnownMarketplaces. Remote-index entries are skipped. This is what makes a later sync on a fresh machine able to clone.
  • Registered marketplace already in the manifest with only name / pin → fill repo or url. Existing pin is left untouched. A row that already has a source is skipped.
  • Enabled plugin not in manifest → new [[skills]] row with name + marketplace.
  • Agent skill in inventory but not in manifest → new [[agent_skills]] row. The fields are reconstructed from the inventory tag: local becomes a name-only entry, npm:pkg becomes npm = "pkg", marketplace:<name>:<path> becomes marketplace plus path (rsplit on the last :), source:<git>:<path> becomes source plus path (rsplit on the last :, so a git URL that contains : stays intact), plugin:… is not adopted as [[agent_skills]], and any other string becomes source = "..." only.
  • 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 skill 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. Honour path when set.
Marketplace Agent Skills (marketplace + path)Re-copy from the marketplace clone after marketplace refresh. Does not clone into the Agent Skill cache. Filter matches the marketplace name. Unnamed rows refresh names already tagged marketplace:<name>:<path>.
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, name, or marketplace 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 these 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"

# Nested tree: Agent Skills that are not at `.agents/skills` or `skills/`
[[agent_skills]]
source = "owner/odd-layout"
path = "packages/foo/skills"
name = "foo"

# Reuse a registered marketplace clone (one clone, several packaging trees).
# Pair with [[skills]] harnesses = ["claude"] — see llm-wiki example below.
[[agent_skills]]
marketplace = "llm-wiki"
path = "plugins/llm-wiki-opencode/skills"
name = "wiki-manager"
harnesses = ["pi", "grok"]

[[agent_skills]]
marketplace = "llm-wiki"
path = "plugins/llm-wiki-opencode/skills"
name = "wiki-query"
harnesses = ["pi", "grok"]

# 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"

# Many skills from one source, one stanza
[[agent_skills]]
source = "mattpocock/skills"
skills = ["prototype", "research", "tdd", "wayfinder"]
FieldPurpose
sourceGit source: owner/repo (GitHub) or full git URL. sync/upgrade clone/pull and copy. Mutex with npm and marketplace.
marketplaceName of a registered marketplace. Reuse that clone instead of cloning source into the Agent Skill cache. Requires the clone to exist (hard error if missing). Mutex with source and npm. Inventory tag is marketplace:<name>:<path>.
pathRelative directory inside the cloned source or marketplace. Replaces the default walk of .agents/skills and skills/. If path/SKILL.md exists, that directory is the skill (name = last segment). Else every child with SKILL.md is a skill. Requires source or marketplace. Must be relative: no .., no absolute form, no \, no :. Inventory tag is source:<source>:<path> or marketplace:<name>:<path>.
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.
skillsOptional list. The plural form of name: one stanza picks many skills out of the same source, marketplace or npm package, and writes source once instead of once per name. Every other key on the stanza (path, harnesses, claims, install_cmd) applies to every name in the list. An entry declares name or skills, never both — a stanza carrying both is refused when the manifest loads. An empty skills = [] means what an absent key means: the row keeps whatever it meant before.
harnessesOptional list. Names which harnesses can see this Agent Skill. Empty inherits [defaults].harnesses, then every harness whose home exists. Set it on marketplace+path rows. The llm-wiki recipe uses ["pi", "grok"] so Claude does not also get the OpenCode tree.
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.

llm-wiki for Claude, Pi, and Grok

nvk/llm-wiki is one source repo. It ships a Claude plugin and OpenCode Agent Skills. A plugin and an Agent Skill are different things. Declare both.

Requires zskills 1.3.0 or later. 1.3.0 is the series after 1.2.0 that copies Agent Skills from a marketplace clone via marketplace and path. A 1.2 binary ignores unknown keys. It treats marketplace and path as absent. The [[agent_skills]] rows then become local-only. sync prints ✓ applied. and copies nothing from the OpenCode tree.

[[marketplaces]]
name = "llm-wiki"
repo = "nvk/llm-wiki"

[[skills]]
name = "wiki"
marketplace = "llm-wiki"
harnesses = ["claude"]

[[agent_skills]]
marketplace = "llm-wiki"
path = "plugins/llm-wiki-opencode/skills"
name = "wiki-manager"
harnesses = ["pi", "grok"]

[[agent_skills]]
marketplace = "llm-wiki"
path = "plugins/llm-wiki-opencode/skills"
name = "wiki-query"
harnesses = ["pi", "grok"]

Set harnesses on both rows. Do not omit them. A plugin row that omits harnesses inherits [defaults].harnesses. If that default includes pi or grok, sync copies the Claude nested skill onto the hub. An Agent Skill row that omits harnesses inherits every harness whose home exists. That can symlink the OpenCode tree into ~/.claude/skills/.

repo lets sync clone the marketplace on a fresh machine. The Agent Skill rows reuse that clone. They do not clone nvk/llm-wiki a second time.

Do not run zskills skill install nvk/llm-wiki. That command sees .claude-plugin/marketplace.json and redirects to marketplace add. Write the [[agent_skills]] rows.

What each harness consumes:

HarnessWhat it consumes
Claude CodePlugin wiki@llm-wiki. Slash commands /wiki:*. Nested Claude wiki-manager. bin/llm-wiki in the plugin cache.
PiHub Agent Skills wiki-manager and wiki-query at ~/.agents/skills/. Pi lists them under [Skills]. wiki-query is the read-only lookup skill.
GrokThe same hub Agent Skills. Grok scans ~/.agents/skills/. Slash /wiki-manager and /wiki-query.

What this recipe does not provide:

  • zskills does not install scripts/pi-wiki-query. That is a launcher. It is not an Agent Skill.
  • zskills does not write ~/.pi/agent/prompts/wiki-query.md. That path is a Pi prompt file. Official lookup is the hub Agent Skill wiki-query.
  • zskills does not provide Grok slash /wiki:*. zskills does not write ~/.grok/config.toml. It does not install a Grok plugin.
  • zskills copies SKILL.md as-is. Pi warns when a skill description exceeds 1024 characters. OpenCode wiki-manager is over that limit in nvk/llm-wiki. Truncate it upstream. Do not edit the hub copy. sync overwrites local edits.

Apply with zskills sync. Workflow: Use cases → Declare llm-wiki.

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] [--mcp]
FlagDefaultDescription
<path>.Tree to walk
--depth N6Maximum directory recursion depth
--jsonoffMachine-readable output
--mcpoffAlso report each project’s MCP servers (name and kind only; never env or header values). Without this flag, MCP-only projects are not listed and the JSON has no mcps key.

Detects:

  • .claude/settings.json or .claude/settings.local.json with enabledPlugins / extraKnownMarketplaces (project-scope plugin enables)
  • .claude/skills/<name>/SKILL.md (project-scope Agent Skills)
  • with --mcp: <project>/.mcp.json (wrapped {"mcpServers": {…}} or a flat top-level map) and mcpServers in .claude/settings.json / settings.local.json

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, project-scope Agent Skills, and MCP servers 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/.

MCP servers are promoted too. Sources are <project>/.mcp.json (both schemas), <project>/.claude.local/settings.json, and the .claude/settings*.json files above. Each raw JSON entry is copied verbatim into ~/.claude.json, env and header values included. Plugin-provided servers are skipped. A name already present at user scope is left alone.

--remove-from-project clears enabledPlugins, extraKnownMarketplaces, .claude/skills/, and promoted MCP keys 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. <name> is the name field in the clone’s .claude-plugin/marketplace.json. If that file is missing, unparseable, or its name is not a single safe path segment, add falls back to the repo basename. Two repos that declare the same name collide: the second add is refused and the first tap is left as-is. Mirrors what /plugin marketplace add does inside Claude Code.

Adding a marketplace does not install plugins and does not change the manifest. After a successful add, zskills reads .claude-plugin/marketplace.json and prints the plugins the marketplace offers, plus the next command.

If the clone also has Agent Skill trees at plugins/*/skills/ that are not inside a plugin source from marketplace.json, add prints those Agent Skill names and a [[agent_skills]] stanza. It does not write the stanza to the manifest.

✓ added marketplace llm-wiki
  1 plugin: wiki
  Next: zskills plugin install wiki@llm-wiki
  Agent Skills under plugins/llm-wiki-opencode/skills: wiki-manager, wiki-query
    [[agent_skills]]
    marketplace = "llm-wiki"
    path = "plugins/llm-wiki-opencode/skills"
    name = "wiki-manager"
    [[agent_skills]]
    marketplace = "llm-wiki"
    path = "plugins/llm-wiki-opencode/skills"
    name = "wiki-query"

If marketplace.json is missing, add still registers the clone and warns. list also prints a dimmed hint when no plugins are active and a registered marketplace offers at least one.

add-recommended seeds the trusted defaults (currently just anthropics/claude-plugins-official). Idempotent — safe to re-run; existing marketplaces are left as-is.

Pinning a marketplace

By default marketplace update, update and upgrade all git pull every registered marketplace, so a marketplace tracks whatever its default branch moves to. Declare a pin in skills.toml to hold one at a tag, branch, or full sha:

[[marketplaces]]
name = "llm-wiki"
repo = "nvk/llm-wiki"   # or url = "https://…" — required so sync can clone on a fresh machine
pin  = "v0.23.0"        # tag, branch, or full sha

repo is GitHub owner/repo. url is any git URL. When both are set, repo wins. sync registers any declared marketplace that is not yet in known_marketplaces.json (clone + extraKnownMarketplaces) before it resolves [[skills]]. A [[marketplaces]] row with only name and pin still pins an already-registered marketplace; it cannot recreate the clone.

A pinned marketplace is checked out at that ref and never pulled. marketplace list marks it [pinned v0.23.0]. If the clone has drifted, the next update puts it back:

$ zskills update
Updating llm-wiki ... pinned @ d02cbcb (restored)

Three details worth knowing:

  • The ref is resolved from what the clone already has. Only if that fails does zskills git fetch, which cannot move HEAD.
  • A pin that cannot be resolved is an error, not a fallback. zskills will not pull a marketplace it was told to hold, so a typo in a pin freezes the marketplace rather than floating it.
  • A pinned clone sits on a detached HEAD on purpose. Leaving it on a branch invites the next pull to move it.

Remove the pin line (or blank it) to let the marketplace float again.

The pin lives in skills.toml, not in known_marketplaces.json. That file belongs to Claude Code, which validates it and rejects the whole file when it disagrees — one entry missing lastUpdated is enough to break every claude plugin install. skills.toml is also the half of the configuration that is meant to be shared between machines; known_marketplaces.json holds machine-local absolute paths.

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 plugin 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 skill 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 skill install https://github.com/jakubkrehel/make-interfaces-feel-better.git
zskills skill 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 skill install owner/big-skill-collection
owner/big-skill-collection contains 47 Agent Skills — zskills won't install all of them by default.

Options:
  zskills skill install owner/big-skill-collection -i      interactive picker
  zskills skill 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 skill 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 plugin 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 skill 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 skill 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 plugin 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. Adopt every configured-but-unlisted MCP into the manifest:

    zskills sync --adopt
    

    Each server becomes a [[mcps]] row with its transport details intact. Env and header values are copied verbatim — eyeball skills.toml and replace any literal secrets with ${VAR} references before you commit it.

  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.

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 plugin install <name> flips it on (or appends [[skills]] to skills.toml for the declarative path).

18. Declare llm-wiki for Claude Code, Pi, and Grok

nvk/llm-wiki is one source repo. It ships a Claude plugin and OpenCode Agent Skills. A plugin and an Agent Skill are different things. Declare both.

Requires zskills 1.3.0 or later. 1.3.0 is the series after 1.2.0 that copies Agent Skills from a marketplace clone via marketplace and path. A 1.2 binary ignores unknown keys. It treats marketplace and path as absent. The [[agent_skills]] rows then become local-only. sync prints ✓ applied. and copies nothing from the OpenCode tree.

# ~/.config/zskills/skills.toml
[[marketplaces]]
name = "llm-wiki"
repo = "nvk/llm-wiki"

[[skills]]
name = "wiki"
marketplace = "llm-wiki"
harnesses = ["claude"]

[[agent_skills]]
marketplace = "llm-wiki"
path = "plugins/llm-wiki-opencode/skills"
name = "wiki-manager"
harnesses = ["pi", "grok"]

[[agent_skills]]
marketplace = "llm-wiki"
path = "plugins/llm-wiki-opencode/skills"
name = "wiki-query"
harnesses = ["pi", "grok"]

Set harnesses on both rows. Do not omit them. A plugin row that omits harnesses inherits [defaults].harnesses. If that default includes pi or grok, sync copies the Claude nested skill onto the hub. An Agent Skill row that omits harnesses inherits every harness whose home exists. That can symlink the OpenCode tree into ~/.claude/skills/.

zskills sync

repo lets sync clone the marketplace on a fresh machine. The Agent Skill rows reuse that clone. They do not clone nvk/llm-wiki a second time.

Do not run zskills skill install nvk/llm-wiki. That command sees .claude-plugin/marketplace.json and redirects to marketplace add. Write the [[agent_skills]] rows.

What each harness consumes:

HarnessWhat it consumes
Claude CodePlugin wiki@llm-wiki. Slash commands /wiki:*. Nested Claude wiki-manager. bin/llm-wiki in the plugin cache.
PiHub Agent Skills wiki-manager and wiki-query at ~/.agents/skills/. Pi lists them under [Skills]. wiki-query is the read-only lookup skill.
GrokThe same hub Agent Skills. Grok scans ~/.agents/skills/. Slash /wiki-manager and /wiki-query.

What this recipe does not provide:

  • zskills does not install scripts/pi-wiki-query. That is a launcher. It is not an Agent Skill.
  • zskills does not write ~/.pi/agent/prompts/wiki-query.md. Official lookup is the hub Agent Skill wiki-query.
  • zskills does not provide Grok slash /wiki:*. zskills does not write ~/.grok/config.toml. It does not install a Grok plugin.

Schema: Commands → llm-wiki for Claude, Pi, and Grok.

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.json → enabledPlugins; ~/.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.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 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 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 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 plugin install firecrawl@zot24-skills        # instead of just firecrawl

marketplace add succeeded but list shows no plugins

marketplace add registers a catalog. It does not install plugins and it does not add a [[skills]] entry to the manifest. sync then reports no changes. list reports plugins that are enabled and installed, so it stays empty.

Install the plugin the marketplace offers:

zskills marketplace list              # how many plugins the clone lists
zskills plugin install wiki@llm-wiki  # nvk/llm-wiki offers `wiki`
# or
zskills plugin install -i             # browse every registered marketplace

To keep the plugin across later sync runs, also add it to skills.toml:

[[skills]]
name = "wiki"
marketplace = "llm-wiki"

marketplace add now prints the plugins and the plugin install command. list prints a dimmed hint when a registered marketplace offers a plugin and none are active.

“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 plugin 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

doctor says hub Agent Skill is Claude-flavored

Pi or Grok can see ~/.agents/skills/wiki-manager/SKILL.md, and that file matches Claude flavour:

  • YAML frontmatter contains Activates for /wiki commands, or
  • a tools: list with Claude Code names (Read, Write, Edit), or
  • the body contains Claude Code is both the compiler

That happens when a plugin nested skill was copied onto the hub. Pi and Grok do not load /wiki:* slash commands, so they need the OpenCode tree.

OpenCode wiki-manager still contains /wiki:* as documented shorthand. That string alone is not this finding. doctor does not substring /wiki:.

Fix: point [[agent_skills]] at the OpenCode tree and sync:

[[agent_skills]]
marketplace = "llm-wiki"
path = "plugins/llm-wiki-opencode/skills"
name = "wiki-manager"
harnesses = ["pi", "grok"]

--fix does not rewrite SKILL.md. sync takes over a same-marketplace plugin: hub copy.

Pi says skill description exceeds 1024 characters

Pi prints [Skill conflicts] when SKILL.md frontmatter description is longer than 1024 characters. OpenCode wiki-manager from nvk/llm-wiki is over that limit (~1157). zskills copied the file as-is.

This is not a zskills bug. Do not shorten the hub copy. zskills sync and zskills skill upgrade overwrite it from the marketplace clone.

Fix the description in nvk/llm-wiki plugins/llm-wiki-opencode/skills/wiki-manager/SKILL.md. Then zskills marketplace update llm-wiki and zskills sync.

wiki-query is under the limit. It is the read-only lookup Agent Skill. It is not a Pi prompt under ~/.pi/agent/prompts/.

doctor says [[agent_skills]] path is not on disk

The path in the manifest does not exist inside the clone. Check the relative path (plugins/llm-wiki-opencode/skills for llm-wiki). Then zskills marketplace update or fix the selector.

--fix does not create the directory.

doctor says marketplace is not in known_marketplaces.json

The marketplace name on an [[agent_skills]] row is not a key in ~/.claude/plugins/known_marketplaces.json. Register it:

zskills marketplace add nvk/llm-wiki

Or add repo = "nvk/llm-wiki" on [[marketplaces]] and run sync. --fix does not register a marketplace.

“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 one MCP server at one scope, run zskills mcp remove <name> [--scope user|project|local]. That command writes the matching [[mcps]] row and the runtime key. It does not run a full sync.

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.

PR Description Standard (ASD-STE100)

One card. Read it before you write a pull request description.

The house standard is ASD-STE100 (Simplified Technical English). It is a hard rule, not a suggestion. The rule covers the description text and the prose inside the mermaid diagram in that description.

This card is the zskills copy of the house standard. The writing rules and the forked-chat rule are identical across repositories. Only the test commands and the diagram storage rule differ, because zskills is a Rust CLI.

The portable version of this standard is the pr-standard Agent Skill in zot24/skills. That repository is the skill home. An agent that needs the standard in another repository installs it from there:

zskills skill install zot24/skills --skill pr-standard

Do not add a copy of the skill to this repository.

Related: ../../CLAUDE.md · ../../.github/pull_request_template.md


1. The four labels

Every PR carries four labels. A PR without all four is not ready for review.

GroupValues
Typebug, enhancement, documentation
Prioritypriority:critical, priority:high, priority:medium
Sizet-shirt:small, t-shirt:medium, t-shirt:big
Areaarea:cli, area:dx, area:docs

More than one area label is allowed. Set the labels with gh pr edit:

gh pr edit 26 --add-label "bug,priority:high,t-shirt:big,area:cli"

area:cli covers command behaviour and the state zskills writes. area:dx covers the build, the tests, and the tooling. area:docs covers this card, the template, and docs/.


2. The six writing rules

These six rules are the whole standard. Do not add more.

RuleDo thisNot this
One idea per sentence“doctor reads known_marketplaces.json. It reports an entry without lastUpdated.”“doctor reads known_marketplaces.json and reports an entry without lastUpdated, which also covers the empty-string case.”
Same word for the same thingmarketplace everywheremarketplace, tap, registry in one PR
Active voice“install writes enabledPlugins.”“enabledPlugins is written by install.”
Imperative in steps“Run cargo test.”“You will want to run cargo test.”
No filler“The entry needs a string timestamp.”“Basically the entry just needs a string timestamp in order to work.”
Exact technical namesknown_marketplaces.json, enabledPlugins“the marketplace file”, “the enabled map”

Banned filler words: basically, simply, just, actually, in order to, obviously, of course.

Agreed nouns — use these, and no synonym for them: plugin, marketplace, Agent Skill, MCP server, manifest, inventory, scope.

plugin and Agent Skill are different things. A plugin comes from a marketplace and lives in ~/.claude/plugins/. An Agent Skill is a SKILL.md directory and lives in ~/.agents/skills/. Do not call one the other.


3. Write the description in a forked chat

Do not draft the description in the same conversation that wrote the code. That conversation already understands the change. It writes for itself, and a reviewer cannot follow the result.

  1. Finish the code on the main chat.
  2. Fork the conversation. Give the fork three things: the diff summary, the mermaid text, and the linked issues. Tell it to apply this card.
  3. The fork writes the description so that a common reviewer understands the change, then puts it on the PR (gh pr create or gh pr edit).
  4. Drop the fork.
  5. Continue the main chat.

A fork that inherits the full coding conversation does not satisfy this rule. The writer must start from the diff, not from the memory of writing it.

The description does not have to be short. Give the reviewer the context, the mechanism, and the risk. STE100 makes the text clear. It does not cap the length. Cut filler, not information.

This applies to humans and to agents. Any in-repo agent or skill that opens a PR follows the same five steps.


4. Which diagram the PR needs

Every PR carries a mermaid diagram of how the change works.

Change typeRequired mermaid
Command flow, or a call into another processsequenceDiagram
Branching logic, or a classificationflowchart

Scope the diagram to the pieces the PR touches. Do not redraw the whole binary.

zskills has no docs/diagrams/ directory today, so the mermaid block in the PR body is the whole requirement. If the repository later adds docs/diagrams/, commit each diagram as a .mmd source there as well and reference it under the block.


5. Three mermaid patterns that break GitHub

GitHub renders unparseable mermaid as a plain code block. It prints no error, so nobody notices.

  1. ; inside note or message text. Mermaid reads ; as a statement terminator, so stamps the field; keeps the tap ends the statement early.
  2. HTML in a participant alias. participant Doctor as doctor<br/>(--fix) does not render. Write the alias on one line with no tag.
  3. A bare %% line. The comment stripper needs one character after %%, so a bare %% glues onto the next line. Use %% --- as a separator. This breaks flowcharts, not sequence diagrams.

No CI job reads the PR body. The description is machine-unchecked. Open the PR page and look at the diagram.


6. Testing evidence

Paste real output. These are the commands for this repository:

cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test

zskills writes to the user’s real ~/.claude and ~/.agents. When a PR changes a command that writes state, exercise the built binary against a temporary home and paste the before/after:

export CLAUDE_HOME=/tmp/zs-sandbox/.claude AGENTS_HOME=/tmp/zs-sandbox/.agents
./target/release/zskills doctor

7. Scope

  • New PRs follow this card. Old open PRs are not rewritten for the standard alone.
  • If you edit an old PR’s body for another reason, bring it up to the standard while you are there.

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.

1.4.0 (2026-09-01)

Features

  • one [[agent_skills]] stanza names many skills from one source (#79) (cfe7d88)
  • scan –mcp and migrate project MCP servers (#76) (11f72be)

Bug Fixes

  • doctor walks harness skill roots (#74) (a7d624b)
  • drop [[skills]] row on plugin remove and purge (#72) (31dc6ef)
  • name marketplaces from the manifest, not the repo basename (#78) (58a920d)
  • plan npm agent-skill rows so sync dry-run matches apply (#75) (5fe0b5f)
  • show source kind on every list Agent Skill line (#73) (99f6182)

Documentation

  • record Pi skill listing and upstream wiki-manager description limit (#70) (2a82ed8)

1.3.0 (2026-08-30)

Features

  • copy Agent Skills from a marketplace clone via marketplace and path (d9c065e)
  • doctor warns when hub wiki-manager is Claude-flavored for Pi or Grok (8baa6d8)
  • hint Agent Skill trees under plugins/*/skills after marketplace add (41e83c7)
  • select an Agent Skill tree with path inside a source clone (c48f505)
  • skill install –path for non-conventional Agent Skill roots (b743ac8)

Bug Fixes

  • declare marketplace source in skills.toml so sync can clone on a fresh machine (675ad99)
  • doctor: guard the shipped SKILL.md against verbs removed in 1.0 (#52) (10528b8)
  • print plugins and next install command after marketplace add (#56) (2906957)

Documentation

  • declare llm-wiki plugin and OpenCode Agent Skills for Claude, Pi, and Grok (ed4197f)
  • update command surface and harness rules for 1.2.0 (#48) (470abd2)

1.2.0 (2026-08-25)

Features

  • register the Agent Skill hub in Pi settings (#45) (b99398a)
  • symlink hub skills into per-harness roots (#46) (e92139e)

1.1.0 (2026-08-24)

Features

  • name which harnesses can see a plugin or Agent Skill (#35) (daad031), closes #34

1.0.0 (2026-08-24)

⚠ BREAKING CHANGES

  • typed plugin, skill, and mcp command groups (#32)

Features

  • typed plugin, skill, and mcp command groups (#32) (5562c42)

0.9.0 (2026-08-22)

Features

  • pin a marketplace so update and upgrade cannot float it (#28) (b604aca)

Bug Fixes

  • discover Agent Skills under .agents/skills/, not only skills/ (#31) (37334fb)
  • stop reporting owned skills as unmanaged, and stop faking inventory (#30) (bfdf5ca)

0.8.0 (2026-08-20)

Features

  • install from repo + cross-client skill path + sync –adopt (#21) (64274b2)
  • sparse Agent Skill installs from git repos + install –skill flag (#24) (9623646)
  • zskills install <owner/repo> — direct install from a git repo (#18) (2756dc0)

Bug Fixes

  • sparse installs work on marketplace repos and nested skill layouts (#25) (7dcd9a5)

Documentation

  • adopt the house PR standard — template, STE100 card, labels (#27) (80a6b59)
  • ship Agent Skill + MCP-aware docs for v0.7.0 (#16) (66ad32f)
  • theme: terminal aesthetic + readability tune + ← zot24.com link (#23) (a34aeae)
  • update for ~/.agents/skills/ path + sync –adopt + quieter npm (#22) (38a5c58)

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.