Keep .claude-plugin/marketplace.json and the README Available skills catalog
in sync with upstream external skills.
Arguments: $ARGUMENTS
<owner/repo> [path] [name]→ add mode (default).update(optionallyupdate <owner/repo>to scope to one repo) → update mode.
Granularity is the CLAUDE.md rule; what it does not say is where name comes
from — the skill folder's basename for a per-skill entry, the name in
plugin.json for a whole-plugin one.
Fetches
Both modes use these. HEAD resolves the repo's default branch, so main vs
master is never a guess.
Skill folders in a repo:
curl -fsSL "https://api.github.com/repos/<owner>/<repo>/git/trees/HEAD?recursive=1" \
| jq -r '.tree[] | select(.path | endswith("/SKILL.md")) | .path | sub("/SKILL.md$";"")'
Latest tags (does this repo publish semver tags?):
curl -fsSL "https://api.github.com/repos/<owner>/<repo>/tags?per_page=5" | jq -r '.[].name'
The SHA a ref resolves to — HEAD for a branch pin, the tag name for a tag pin:
curl -fsSL "https://api.github.com/repos/<owner>/<repo>/commits/<ref>" \
-H "Accept: application/vnd.github.sha"
A skill's upstream frontmatter description — the one-liner, usually its
first sentence:
curl -fsSL "https://raw.githubusercontent.com/<owner>/<repo>/<sha>/<path>/SKILL.md"
If curl is blocked/redirected here, fetch the same URLs via any HTTP tool — the
endpoints are identical.
Pinning the ref
Renovate updates an entry only when its ref has one of two shapes — one
customManager in renovate.json per shape:
- Tag —
vX.Y.Z, optionally prefixed (skill-v4.1.2). Tag andshabump together. The default when the repo publishes semver tags, and what most entries here use. The prefix names the tag series, so a repo publishing several (cli-v*,ext-v*,skill-v*) only ever bumps within the one you pinned. - Branch — literally
mainormaster; theshabumps to that branch's HEAD. For a repo publishing no usable tags.
A ref of any other value (1.2.0, release-2026, a bare SHA) matches neither
manager, and that entry is then silently never updated again.
Add mode
- Parse args. If
owner/repomissing, ask for it. - Decide granularity.
pathpoints at a plugin root (has.claude-plugin/plugin.json) holding artifacts a per-skill entry would drop — further skills, subagents, or commands → one whole-plugin entry at that root ("."when the plugin root is the repo root); skip per-skill discovery, takenamefrom theplugin.json. Otherwise discover skills:path= a skill folder → single entry; a parent folder → only folders under it (empty → stop, report); omitted → all (batch). - Pick the
ref(see above), then fetch the SHA it resolves to — one SHA shared by every entry from that repo. Confirm the skill'spathexists at that SHA. name= arg (single) or folder basename (batch). Confirm every name is free inmarketplace.json— report collisions and ask before proceeding.- For each skill, fetch its upstream
descriptionone-liner. Append each entry to thepluginsarray (match existing formatting exactly):
The{ "name": "<name>", "source": { "source": "git-subdir", "url": "https://github.com/<owner>/<repo>", "path": "<path>", "ref": "<tag or branch>", "sha": "<sha>" }, "description": "<one line saying what the skill does>" }descriptionsays what the skill does: upstream's one-liner, or a one-line summary written from theSKILL.mdbody when upstream's is empty or unusable. For a whole-plugin entry, list its bundled artifacts (agents + skills), e.g."…: bash-pro and posix-shell-pro agents plus the bash-defensive-patterns, bats-testing-patterns, and shellcheck-configuration skills.". - Regenerate the README (see below), then validate.
Done when every skill the discovery in step 2 turned up is either an entry in
marketplace.json or named as deliberately left out.
Update mode
For each git-subdir source repo in marketplace.json (or the one named):
- Discover the repo's current skill folders, and its latest tag or branch HEAD to match how its entries are pinned.
- Reconcile against the existing entries:
- New upstream folder (no entry) → add an entry (as in add mode).
- Removed upstream (entry whose
pathno longer hasSKILL.md) → list it, ask to confirm, then delete the entry. - Bump each surviving entry to the SHA its
refnow resolves to: a tag-pinned entry movesrefandshatogether to the latest tag, keeping its tag series; a branch-pinned entry takes that branch's HEAD. (Renovate also does this; harmless to set now.) - Refresh each entry's
descriptionfrom upstream.marketplace.jsonis the source of truth, so this is what the README shows.
- Regenerate the README, then validate.
Done when every git-subdir repo in scope is accounted for — reconciled, or
reported as already current. A repo silently skipped is one Renovate keeps
bumping while its entry list drifts.
Regenerate the README
The README Available skills catalog is a projection of
marketplace.json produced by scripts/gen-readme.js — never hand-edit it.
- If you referenced a skill from a new source repo, add a group to
scripts/catalog-meta.json(ordered list):
Use{ "repo": "<owner>/<repo>", "tagline": "<short label>", "kind": "skill" }"kind": "plugin"for a whole-plugin entry (renders a "What it bundles" column). The generator throws if any git-subdir entry's repo has no group (or is not omitted / a mode-router dependency), so this can't be silently missed. - Run the generator:
node scripts/gen-readme.js
Finish
node scripts/gen-readme.js --check # README catalog is in sync with marketplace.json
claude plugin validate .
Report what changed (added / removed / description updates). Do not commit — leave that to the user.