Skills Manager
The user's skills live in a single hub (~/.skills-manager by default; override with --home <path> or SKILL_HOME). The hub is the only content authority: skills/<name>/ directories plus a registry.yaml of metadata. Skills flow in two directions — install/import into the hub, distribute out to agent runtime directories (~/.claude/skills, ~/.cursor/skills, …) as symlinks (user scope) or copies (project scope).
Key vocabulary you will need when reading output:
- Source: where a skill came from —
{type: git|local, url, subpath, ref, …}on the registry entry. A skill with aurl(+subpath) is **updatable`; without one it is a snapshot. - Update detection: the freshness engine behind
update --checkand the dashboard's 可更新 check. GitHub sources compare the skill sub-directory's tree SHA (source.upstream_tree) against the GitHub Trees API — one call per repo (shared by all its skills), TTL-cached, through the logged-inghCLI when present (5000 req/h) or anonymous HTTPS otherwise (60 req/h). A row reading 检测失败 / detection failed means the check did not complete — that is not "no update"; every failure appends a JSON line to<home>/.skills/dashboard.log. imported: true: how the skill entered the hub (viainit), orthogonal to whether it has a source.- Detected agents: the agent set
npx skillswould target on this machine, resolved locally from the bundled catalog snapshot. - Domain category / category set: a skill's free-form
categories: [](前端 / 金融 / backend — the user's own vocabulary, orthogonal to the frozen legacycategory); a category set is the per-runtime-path applied filter state thatcategories statusreports. - Resident cost / Cost ledger: the per-message token cost of a distributed skill's frontmatter
name + description(an undistributed skill costs nothing).costaccounts it per physical runtime path — approximately, always shown≈(method: "char-approx").
The CLI is non-interactive (bootstrap's agent picker is the one exception); every choice is a flag. Commands print JSON — except status (a human-readable summary), get (the skill file itself), cost (a human ledger view; --json prints the machine form), and --help. Parse stdout; don't guess the shape.
First run / empty hub
When the user's first message is a broad ask (“帮我看看我的 skills” / "what skills do I have"), or the hub turns out to be empty, do not assume any setup exists — orient first, then propose exactly one action:
skills-manager list— see what the hub holds. Empty output means a fresh bootstrap; say so plainly.- Based on what exists, offer one next step and wait:
- Skills already live in runtime dirs (
~/.claude/skills, …) →skills-manager init --dry-run, report the plan, import on the user's confirmation. - Nothing anywhere → offer to install from a source the user names (
skills-manager add owner/repo --listfirst).
- Skills already live in runtime dirs (
- This skill itself should be mounted wherever the user works: if
skills-manager doctorshows it missing for an agent they use, offerskills-manager distribute --to user --skill skills-manager --agent <id>.
Keep the first turn short: report state, propose one action, wait. Deeper workflows (like provenance backfill below) come when the user asks for them.
Commands by task
Inspect the library
bashskills-manager list [--category <c>] [--include-archived] skills-manager list --brief # compact rows — use this in conversation first; full rows are tens of KB skills-manager get <name> # zero-retention read: full SKILL.md (frontmatter + body) on stdout skills-manager get <name> --path # absolute hub dir, for read-only borrowing of sibling files skills-manager status # distribution health: managed/outdated/foreign skills-manager doctor # warnings incl. imported-without-source queue skills-manager cost [--top <n>] [--json] # resident-cost ledger: per-path cost + report-only recall suggestions skills-manager catalog info # snapshot stamp + detected agents (each with its runtime dir) skills-manager catalog refresh # re-pull the upstream agent table skills-manager backup list
get is the reference layer — the zero-cost alternative to distributing. When you need a skill once (this conversation only), get <name> prints its complete SKILL.md to stdout — raw text, not JSON — and the frontmatter carries the provenance mirror (where the skill came from), so the read doubles as a trust check. Use it, then move on: nothing stays loaded. --path prints the hub directory instead — first stdout line, followed by a read-only notice (an archived skill resolves to its .skills/archive/… directory) — for reading sibling files (scripts/, references). Read-only: the hub is canonical, changes go through skills-manager commands. No distribution state is required; archived skills stay readable (in body mode an archived notice rides stderr, stdout stays the file body), and a miss suggests near names.
Install from a source (source-first)
bashskills-manager add <owner/repo> --list # discover first, install nothing skills-manager add <owner/repo> --skill <name> # install by discovered name or subpath skills-manager add /local/path --all -y # local sources work too; -y overwrites
<source> accepts GitHub owner/repo, a Git URL, a GitHub tree URL, or a local path. Always --list first when unsure what a source contains.
Import skills already living in runtime dirs
bashskills-manager init --dry-run # plan only — start here skills-manager init # import + replace origins with symlinks skills-manager init --prefer claude-code ~/.agents/skills hub # conflict priority this run skills-manager init --resolve my-skill=hub # per-skill override skills-manager backup restore <skill> # undo one import
Import never guesses provenance, but it adopts evidence: entries in the npx skills lockfile (~/.agents/.skill-lock.json) with matching names become real sources automatically (ADR-0011).
When the dry-run reports conflicts (kind: "multi-runtime"), walk the user through the decision rather than quoting flag docs: each location already carries its agent ids, runtime dir, and its own description — present the sides, ask which one to trust, then map the answer to --prefer <runtime-dir> (priority for the whole run) or --resolve <skill>=<choice> (one skill). A dry-run's plannedImports names what would import.
Distribute / undistribute
bashskills-manager distribute --to user --skill <name> --agent claude-code --agent zed skills-manager distribute --to project --project ./repo --skill <name> --mode copy skills-manager undistribute --to user --skill <name> --agent claude-code skills-manager redistribute --refresh # re-sync stale copy targets skills-manager distribute rollback --to user
Omitting --agent targets the detected set — which can be dozens of agents on a busy machine. Check catalog info first and name agents explicitly unless the user truly means "everywhere". User scope defaults to symlink, project scope to copy.
Domain categories: tag skills, apply a category set
Every skill can carry free-form domain categories (前端 / 金融 / backend — no controlled vocabulary; categories list keeps the wording consistent). Tagging only writes the registry — what loads changes only on an explicit apply.
bashskills-manager categories set <skill> 前端 后端 # replace the whole list (no values = clear) skills-manager categories add <skill> 金融 # incremental, no full restatement skills-manager categories remove <skill> 金融 skills-manager categories list # every tag in the hub + per-tag skill count skills-manager edit <skill> --categories 前端 后端 # same replace semantics inside edit
Map conversational tagging asks ("给 X 归到前端类" / "tag this as frontend") straight onto these commands — there are no flags for the user to remember.
categories apply rewrites the selected agents' runtime dirs to exactly the skills in the given categories: distributes what's missing, removes managed skills outside the set.
bashskills-manager categories apply 前端 金融 -a claude-code skills-manager categories apply --all # dissolve the filter, restore every managed skill skills-manager categories status # per-path applied set + drift; never writes
Agent selection reuses distribute's mental model, not a new one: -a repeatable, default = detected set. The CLI itself never prompts — when the user doesn't name agents, you pick in conversation, the way distribute's dashboard picker behaves: detected agents prechecked when no apply has been confirmed yet, and the last confirmed apply selection — remembered per scope, within this conversation — offered as the default next time. Agents sharing one physical runtime dir (an agent family) necessarily share one category set; selection is always by agent id.
State the strict semantics plainly whenever proposing an apply:
- Only the selected set — managed skills outside the categories, including uncategorized ones, are removed from the runtime dir. It is reversible:
apply --allrestores everything, anddistribute rollback --to userrestores the runtime content and the category-set record together. - Exemptions — this manager skill is always exempt (switching categories never cuts off this conversation); foreign (unmanaged) entries are never touched.
- Idempotent — re-running the same apply changes nothing; recovering from an interruption is just "run it again".
- No auto-push — apply is an explicit snapshot; later tag edits or updates never mutate the runtime.
categories statusreports the drift ("N skills now match the set but are undistributed") and names the re-run command that converges.
Presets (预设 / 档位) give a category list a name, so switching is one command instead of restating categories (ADR-0019):
bashskills-manager preset set frontend 前端 ui # save / replace a named preset (categories may not exist yet — build first, tag later) skills-manager preset list # members + mount footprint (paths carrying it; drift reported, never implied healthy) skills-manager preset remove frontend # delete + cascade-clear the name off every mounted path (runtime untouched) skills-manager preset apply frontend -a claude-code
Map 档位-speak straight onto these — there are no flags for the user to remember: "换个档位" / "只留前端技能" / "切到 X 组合" → preset apply; "记住这个组合" → preset set. When a name already holds the list, apply the name — never restate the categories.
preset apply carries exactly the strict semantics above (uncategorized removed, manager skill exempt, foreign untouched, reversible via distribute rollback --to user) plus two hard errors that protect the saved object: a preset resolving to zero managed skills, or one referencing a category that no longer exists, both refuse at apply time naming the preset and its categories — relay the error verbatim; it points at the fix (tag skills into the categories, or re-set the preset's list). The success output ends with the preset's resident-cost line (≈ tokens, char-approx): state the strict semantics and the cost line whenever proposing an apply — the same warnings categories apply gets, so switching 档位 never sounds cheaper or safer than it is.
Update from sources
bashskills-manager update --check # freshness detection: stale / upToDate / failed / skipped skills-manager update --plan # candidates only (url + subpath) — no freshness check skills-manager update --skill <name> skills-manager update --source <key> # one repo group from the plan
Only skills with source.url and source.subpath are candidates — which is why backfill (below) always writes both.
--check answers "is anything actually new": it compares each candidate's anchored tree SHA against the GitHub Trees API (one call per repo, through the logged-in gh when present), so present its stale list as the real update set. failed rows mean the check did not complete — each row carries the log path (<home>/.skills/dashboard.log) with the timestamped cause; usual suspects are network flaps and, without a logged-in gh, the 60 req/h anonymous API limit. --plan lists candidates without checking — don't present a plan entry as "has an update".
Git installs and updates are shallow (--depth 1); a network-class clone failure (HTTP/2 stream reset and friends) is retried once over HTTP/1.1 automatically. Detection anchors on the skill sub-directory's tree SHA, so an unrelated upstream commit (a README bump) does not flag an update. When the dashboard shows 检测失败 (detection failed), read <home>/.skills/dashboard.log for the timestamped cause — usual suspects are network flaps and, without a logged-in gh, the 60 req/h anonymous API limit.
Supply or fix one skill's source
bashskills-manager edit <skill> --source-git <owner/repo> --subpath <path> [--source-ref <ref>] skills-manager edit <skill> --title "New title" --description "…" --category <c> --tags a b
--source-git normalizes owner/repo (or a full GitHub URL) to the canonical repo URL, so the entry lands in exactly the shape add writes and immediately qualifies for update.
Maintenance
bashskills-manager provenance list [--json] # the backfill queue (see workflow) skills-manager provenance adopt [--dry-run] [--skill <name>] skills-manager archive <skill> # keep content, hide from lists skills-manager rebuild-collections skills-manager migrate-consumers # one-shot legacy-tag migration skills-manager migrate-views # leftover hub views → runtimes (legacy) skills-manager bootstrap [--agent <id...>] [--force] # (re)mount this skill onto agents skills-manager web [-p 4777] # local dashboard
Workflow: find and install a skill (查找与发现技能)
Run this when the user asks "帮我找个处理 PDF 的 skill" / "find a skill for X" — discovering a new skill from the ecosystem is a first-class flow, not a backfill side effect. The sequence:
-
Distill English keywords from the ask. The channels are English-keyword search engines: a Chinese ask becomes 2–3 English words ("处理 PDF 的" →
pdf), never a translated sentence. -
Search the shared channel table (below) — one query; the first channel that answers serves the result.
-
Verify before presenting — the same bar as backfill: for each top candidate, fetch the upstream
SKILL.mdand compare itsname/descriptionagainst what the user asked for. Discard mismatches — junk candidates cost more trust than fewer, better ones. -
Present with evidence:
name,owner/repo,installs(an adoption signal, never a quality verdict — say so when showing the number), and the skills.sh link (https://skills.sh/<owner>/<repo>/<skillId>). Name the channel that served the result. -
Install only on the user's pick, through the unchanged source-first machinery. The API's
sourcefield is alreadyowner/repoandskillIdis the skill's directory inside that repo:bashskills-manager add <source> --list # confirm the skill is there and its exact name/subpath skills-manager add <source> --skill <skillId> # the selector matches a discovered name or subpathDistribution stays a separate, explicit step — offer
distributeafterwards if the user wants the skill live for an agent.
Search channel table (shared — ADR-0021)
Both consumers use this one table: the find workflow above and provenance backfill Step 3. Defined once here, referenced there.
| # | Channel | Command |
|---|---|---|
| ① | skills.sh direct API (primary) | curl -s --max-time 30 "https://skills.sh/api/search?q=<kw>" |
| ② | npx skills find (second — buffer against API drift) | npx -y skills find "<kw>" |
| ③ | GitHub code search (last resort) | `gh api -X GET search/code -f q='filename:SKILL.md "" in:file' -f per_page=10 --jq '.items[] |
Channel discipline:
- English keywords only; one query, no retries — no re-querying, no guessed or undocumented API parameters. The single exception is channel ③'s rate-limit handling, spelled out in its own row of discipline below.
- Any failure = channel unavailable, degrade silently: a timeout (cap ~30s), a non-200, JSON that fails to parse, or a missing/empty
skillsarray all mean "try the next channel". The user sees one result set, not the failed attempts — but the presented result names the channel that produced it. - Channel ① returns structured JSON —
skills: [{id, skillId, name, installs, source}]— withsource=owner/repoandid=source/skillId. - Channel ② output lines look like
owner/repo@skillname <installs>with ahttps://skills.sh/...link; on some npm setupsnpxchokes on this package withUnknown command— that is the channel being unavailable, not your query being wrong. - Channel ③ returns noise on generic names — prefer a distinctive phrase as the quoted term. Rate limits (429/403) are common: wait ~20s, retry once (the one sanctioned retry), then switch the phrase before giving up on the channel.
Workflow: backfill sources for the whole library
Run this when the user asks to "补齐来源 / fix sources / find out where these skills came from / make them updatable", or when doctor reports imported-without-source skills. The rule that governs everything: evidence is adopted automatically; guesses are never written without the user picking them, one skill at a time (ADR-0012).
Step 1 — Adopt deterministic evidence (no questions asked)
bashskills-manager provenance adopt --dry-run # preview, then: skills-manager provenance adopt
This re-runs the lockfile-evidence adoption over legacy imports. Report how many were adopted and how many remain.
Step 2 — Get the remaining queue
bashskills-manager provenance list --json
Two buckets: importedWithoutSource (came from runtime dirs, evidence missing) and locallyAuthored (the user wrote them). Work through both — locally-authored skills usually just need a "confirm" (Step 4), not a source.
Step 3 — Search and verify, one skill at a time
When the queue is more than a handful (5+), fan the search out to parallel subagents — each takes a slice of skills through the steps below and returns structured candidates — then come back and run Step 4 yourself. Searching parallelizes; approving does not.
For each pending skill:
-
Read its
SKILL.mdfrontmatter (name,description) from the hub — that's the query material. Distill it to English keywords per the channel table's discipline. -
Search the shared channel table — the ordered three channels and their discipline live in one place: the "Search channel table" section under Workflow: find and install a skill (above). This step is the table's other consumer; it never redefines it.
-
Verify before presenting. For each top candidate, fetch the upstream
SKILL.mdand compare itsname/descriptionwith the local copy:bashcurl -sL https://raw.githubusercontent.com/<owner>/<repo>/HEAD/<path-without-SKILL.md>/SKILL.mdA matching name plus a matching (or clearly evolved) description is a verified candidate. Discard mismatches — showing the user junk candidates costs more trust than showing fewer, better ones.
Step 4 — The user decides, one skill at a time
Present exactly one question per skill (AskUserQuestion or equivalent single choice), batched at the platform's per-prompt cap — 4 questions per round on Claude Code, so a 17-skill queue is ~5 rounds, not 17 interruptions. Each question shows the verified candidates (labelled with owner/repo, install count, and how well it matched), plus:
- "Locally authored — keep as-is": the user wrote it; write nothing. An honest local snapshot is the correct end state — never invent an upstream.
- "Skip for now": leave it in the queue.
When the user picks a candidate, write it immediately:
bashskills-manager edit <skill> --source-git <owner/repo> --subpath <dir-inside-repo>
If the search or verification produced nothing trustworthy, say so plainly and offer only locally-authored / skip — do not pad the list with unverified guesses.
Step 5 — Report
bashskills-manager doctor
Summarize: adopted-from-lockfile count, sources written by approval, confirmed locally-authored, skipped, and the before/after of the doctor queue.
Workflow: audit resident context cost
Every distributed skill charges every message the tokens of its frontmatter name + description — its resident cost. That is the account skills-manager cost keeps: totals grouped by physical runtime path (a shared path counted once, its agent family noted), per-skill lines under each path, and report-only recall suggestions. Consult it unprompted when the user wonders what their agents pay per message (常驻成本 / context cost) — and when it shows expensive or zero-controversy distributions, propose the recall yourself instead of waiting to be asked.
bashskills-manager cost --json # the full three-layer ledger — parse this in conversation skills-manager cost # human view: path groups → per-skill lines → total + unmanaged line → suggestions skills-manager cost --top 10 # widen the expensive-descriptions list (default 5)
The JSON carries paths[] (each with runtimeDir, kind, agents, per-skill lines), the top-level totalTokens and method, a three-layer suggestions object, an unmanaged count for foreign entries the ledger honestly omits, and an errors list for unreadable entries (counted 0; entries missing a description are flagged incomplete).
- Approximations, always.
methodis"char-approx"(CJK characters ×1, everything else ÷4) and every displayed number is≈-prefixed — never relay a ledger number to the user as an exact token count. - Suggest recalls, never execute them. Every suggestion carries a verbatim runnable
undistributecommand (including the required--to, and--projectwhere the path is a project target). Acting on it is the user's decision. Onlyundistributeis ever suggested —get(the reference layer) andarchiveare user-side follow-ups you may narrate but not run. - Suggestion layers, in order: archived-but-distributed skills (zero-controversy recalls — the impossible-to-miss head section), top-N most expensive descriptions, and scattered distributions (one skill across several paths — consolidate to one).
Workflow: sync the hub across machines (多机同步)
Run this when the user asks "我想在另一台机器上用这些 skills" / "sync my skills" / "多台电脑怎么同步". The hub itself becomes a git repository; push/pull over the user's own remote is the whole sync mechanism (ADR-0020) — no device codes, no managed tokens, git stays the merge tool. Three moments, three commands:
First machine — set up once:
bashskills-manager sync init --remote <url> # git init (or adopt), canonical .gitignore, baseline commit, attach origin
init adopts an existing .git/ (a manually git-ified hub is a step ahead, not an error — history kept), appends only the missing canonical .gitignore lines, makes one baseline commit when the tree has anything to commit, and reports exactly what it did, zero omission. .backups/ and .skills/ (distribution index, rollback snapshots, activity log) are machine-local and never sync — distribution is per machine, on purpose. A local bare repo works as the remote too. The output ends with a reminder to confirm the hub holds no secrets before it is pushed — relay it, don't bury it.
After changes — the routine, on every machine:
bashskills-manager sync push # add -A + one skill-level summary commit + push skills-manager sync status # read-only probe: git-ified?, remote, dirty count, ahead/behind, last sync commit
push is snapshot-at-push-time — no write point ever auto-commits. With a clean tree and an up-to-date remote it reports "already in sync" and exits 0, never an empty commit. status is zero-network: ahead/behind reads the remote-tracking ref as of the last fetch and says so. sync status --json is the machine-readable form when you need to report sync posture in conversation.
New machine — get the content, then distribute yourself:
bashskills-manager sync pull # fetch + merge; ends with skill-level stats and a distribute reminder
pull ends with what the merge brought in (added / updated / removed skills, registry flag); the not yet distributed reminder prints only when skills actually arrived — the follow-up is the user's call. Offer distribute as the explicit next step when they want pulled skills live for an agent; never run it unprompted after a pull.
Discipline — matching the CLI's hard gates. When the user hits one, the nonzero exit already carries this guidance; relay it, don't soften it:
- pull never auto-distributes, and never runs over a dirty tree — it refuses and points at
sync pushor manual handling. It never stashes. - Conflicts are the user's: pull exits nonzero and points at
git -C <hub> status— skills-manager never makes merge decisions over user data. The single exception: whenregistry.yamlis the only conflicted path, the local content is the empty placeholder (skills: {}), and local history is nothing but the init baseline commit, pull keeps the remote's real registry and says so — in that state no local user data exists. Merge commits are allowed; there is no rebase and no ff-only. - A missing git identity, no remote, or an un-gitified hub are hard errors on push/pull (with guidance) — the tool never configures git or picks a remote on the user's behalf.
sync statuson an un-gitified hub is the exception: a friendly hint plus exit 0. - Bootstrap never git-initializes the hub — sync is the user's explicit opt-in.
Autonomy rules
- Act on your own: previews, evidence adoption, listing, searching, verifying candidates.
- Ask the user: every guessed-source write, one skill per question. Batch the questions, never the writes.
- Never: write a source the user didn't pick, fabricate an upstream for a locally-authored skill, or touch the
npx skillslockfile (read-only evidence).
Notes
- Command surface changes faster than this file: when a command errors as unknown,
skills-manager --helpanddocs/CLI.mdin the skills-manager repo are the authority. npx skillsinteroperability after import is not promised; advise the user to manage skills through skills-manager once imported.


