DEV Community

Cover image for Stop zipping your Claude Code skills: build a plugin marketplace in 15 minutes
Fairen
Fairen

Posted on

Stop zipping your Claude Code skills: build a plugin marketplace in 15 minutes

agkit

You wrote a Claude Code skill. A good one — it turns your staged diff into a proper conventional commit message, and you use it a dozen times a day.

A teammate watches you work and asks the inevitable question: "can you send me that?"

So you zip up a folder. Then someone else asks. Then you improve the skill — and now there are three copies of it in the wild, two of them stale, and nobody knows which one they have.

So you do the obvious thing: you make a repo. team-skills, one folder per skill, everyone clones it and copies what they need into their ~/.claude/. Push a fix and it's available to everybody.

This is better than the zip. It is not a solution — it's the same problem with a git remote in front of it.

The junk drawer problem

A shared repo of skills is a junk drawer, and it fails in four specific ways.

It's all-or-nothing. Clone the repo and you get all fourteen skills, including the eleven that have nothing to do with your work. And skills aren't free: each one's description competes for the agent's attention every session. Your teammate wanted one skill. Their alternative is to hand-pick files out of the clone — which is copying, which is where we came in.

Copying is still copying. The moment those files land in someone's ~/.claude/, they're a fork. No link back to the origin, no update path except "clone again and re-copy, carefully", and no way to answer the question "which version do you have?" — because there are no versions.

There's no unit. team-skills@a3f9c2 tells you nothing about the commit skill specifically. You can't say "I'm on commit-crafter 0.2.0" because commit-crafter isn't a thing that has versions; it's a folder in a repo whose history is fourteen unrelated stories braided together. Every skill's changelog is every other skill's noise.

There's no boundary of ownership. Another team publishes a genuinely good skill. Your drawer's options: copy it in and own the drift forever, or tell everyone to clone a second drawer.

What's missing are two things — and here's the part worth being clear about, because it's the actual point of this article: neither of them is an agkit invention.

The first is the plugin: the unit. A directory with a manifest that gives it an identity — a name, a version, a description, an author. Something that can be installed, updated, and uninstalled as one thing, and versioned independently of its neighbours. This is how Claude Code and GitHub Copilot already model extensions. The separation isn't tooling; it's the shape of the thing.

The second is the marketplace: the catalog. One file listing your plugins, so an agent can show them, install one by name, and know where each of them comes from — including from a repo that isn't yours.

Put those together and "can you send me that?" has a different answer:

/plugin install commit-crafter@dev-toolkit
Enter fullscreen mode Exit fullscreen mode

Your teammate gets one plugin, not fourteen. It has a version. /plugin update brings them your fix. They can uninstall it cleanly. Nothing was copied by hand, and nothing drifted.

A repo full of skills versus a marketplace of plugins: cloning a junk drawer copies everything with no versions and no update path, while a marketplace installs one plugin by name, at a version, with updates and clean uninstall

So why does anyone still keep a junk drawer? Because writing that catalog by hand — plus a manifest per plugin, plus the versioning, plus the changelogs, plus keeping the docs in step — is exactly the kind of chore that makes zipping a folder look reasonable at 6pm on a Friday.

That's the gap agkit fills. It doesn't reinvent plugins or marketplaces — it removes the reason you didn't bother. Think ng for Angular, but for agent plugin marketplaces: one command scaffolds a push-ready repository, and a handful more cover the rest of the project's life.

In this walkthrough we'll take that commit skill from "a folder on your machine" to "installable by anyone with one line". We'll scaffold a marketplace, add a plugin from a template and make it ours, catalog a second plugin that lives in someone else's repo without ever cloning it, publish to GitHub, then ship an improvement with a real version bump.

Everything below is a real terminal session against agkit 0.9.2.

The one idea to hold onto

agkit is built around a single principle: one canonical catalog.

Your repository has exactly one source of truth, .claude-plugin/marketplace.json, plus your plugins under plugins/<name>/. Claude Code and GitHub Copilot read that file as-is — no build step, no compilation. Push the repo, and it installs.

One canonical catalog, every agent: Claude Code and Copilot read marketplace.json as-is, while agkit build generates registries for Codex and Cursor

Everything else — the README plugin table, the AGENTS.md list, the registries for Codex and Cursor — is derived from that catalog. Which means it can't drift, because you never maintain it by hand.

Keep that in mind and the rest of the commands stop looking like a list of tools and start looking like one workflow.

Step 1: scaffold the marketplace

You need Node.js 22+. No install required:

npx agkit init dev-toolkit
Enter fullscreen mode Exit fullscreen mode

It asks a few questions (name, owner, description, git remote, CI), or you can pass them as flags and skip the prompts entirely:

npx agkit init dev-toolkit -y \
  --name dev-toolkit \
  --owner "Jane Doe" \
  --email jane@example.com \
  --description "My personal Claude Code + Copilot plugins" \
  --repo https://github.com/jane/dev-toolkit.git
Enter fullscreen mode Exit fullscreen mode
┌  agkit init _
│
◇  Marketplace scaffolded
│
◇  Next steps ─────────────────────────────────────────────────────╮
│                                                                   │
│  cd dev-toolkit                                                   │
│  git remote add origin https://github.com/jane/dev-toolkit.git    │
│  agkit add skill my-first-skill                                   │
│  agkit validate                                                   │
│  git add -A && git commit -m 'feat: initial marketplace'          │
│  git push -u origin main                                          │
│                                                                   │
│  Target agents: Claude Code, GitHub Copilot CLI                   │
│  Native install (no build): Claude Code, GitHub Copilot CLI       │
│                                                                   │
├───────────────────────────────────────────────────────────────────╯
│
└  Done ✔
Enter fullscreen mode Exit fullscreen mode

Here's what landed:

dev-toolkit/
├── .claude-plugin/marketplace.json   # the catalog
├── plugins/                          # your plugins live here
├── AGENTS.md                         # repo context, read natively by most agents
├── README.md                         # with an install section, already filled in
├── examples/team-settings.json       # drop-in config so teammates get it automatically
├── .github/workflows/validate.yml    # CI gate
└── .gitignore                        # (git init already ran)
Enter fullscreen mode Exit fullscreen mode

The catalog itself is deliberately boring:

{
  "$schema": "https://json.schemastore.org/claude-code-marketplace.json",
  "name": "dev-toolkit",
  "owner": { "name": "Jane Doe", "email": "jane@example.com" },
  "metadata": {
    "description": "My personal Claude Code + Copilot plugins",
    "version": "1.0.0",
    "pluginRoot": "./plugins",
    "targets": ["claude-code", "copilot"]
  },
  "plugins": []
}
Enter fullscreen mode Exit fullscreen mode

Two things worth noticing. targets records which agents you serve — the default is Claude Code and Copilot, both native. And it's already a git repo with a $schema, meaning your editor autocompletes the catalog and CI can validate it from the first commit.

Serving Codex or Cursor too? Pass --agents claude-code,copilot,codex,cursor at init. Those two install from their own registry, which agkit build generates from this same catalog. One repo, four agents, no duplicated plugin content. We'll stay native in this article.

Step 2: add the skill, then make it yours

agkit add takes a template and a name. Five built-in templates ship with the CLI — skill, command, agent, hook, mcp:

agkit add skill commit-crafter -d "Write conventional commit messages from staged changes"
Enter fullscreen mode Exit fullscreen mode
◆  Created plugins/commit-crafter (built-in:skill template) and registered it in marketplace.json
│
●  Edit the generated files, then test locally:
│    claude
│    /plugin marketplace add /path/to/dev-toolkit
│    /plugin install commit-crafter@dev-toolkit
Enter fullscreen mode Exit fullscreen mode

Two files appear, and the catalog gains an entry pointing at them:

{
  "name": "commit-crafter",
  "source": "./plugins/commit-crafter",
  "description": "Write conventional commit messages from staged changes",
  "version": "0.1.0",
  "author": { "name": "Jane Doe" }
}
Enter fullscreen mode Exit fullscreen mode

Note that last hint in the output: /plugin marketplace add /path/to/dev-toolkit. A marketplace is just a directory with a catalog, so you can install from a local path and test your plugin before anything is pushed anywhere. That tightens the loop considerably.

Now the part that matters — the template is a skeleton, not a skill. Open plugins/commit-crafter/skills/commit-crafter/SKILL.md and you get scaffolding with instructions in it:

---
name: commit-crafter
description: "Write conventional commit messages from staged changes ..."
---

# Commit Crafter

Explain the knowledge or procedure this skill provides. Claude loads this
file on demand, so write it as instructions to a competent colleague.

## When to use
- Situation 1
- Situation 2
Enter fullscreen mode Exit fullscreen mode

Replace it with the real thing. The description in the front matter is what Claude reads to decide whether to load the skill at all, so it's worth writing carefully:

---
name: commit-crafter
description: "Writes conventional commit messages from staged changes. Use when the user asks to commit, write a commit message, or says 'commit this'."
---

# Commit Crafter

Turn a staged diff into a Conventional Commits message.

## Instructions

1. Run `git diff --staged` to read what is actually being committed.
2. Pick the type from the change itself: `feat` for new behaviour, `fix` for
   a bug, `refactor` when behaviour is unchanged, `docs`, `test`, `chore`.
3. Scope it with the package or directory touched: `feat(auth): …`
4. Subject line: imperative, lowercase, no trailing period, ≤ 72 chars.
5. If the change breaks a public contract, add a `BREAKING CHANGE:` footer.

## Examples

`feat(parser): support scoped package names`
`fix(auth): refresh the token before expiry, not after`
Enter fullscreen mode Exit fullscreen mode

That's the whole shape of it: agkit gives you a valid, registered, correctly wired plugin; you supply the substance.

If you later hand-edit a manifest — a description, keywords, a homepage — remember that plugins/<name>/.claude-plugin/plugin.json is the source of truth, not the catalog. Edit it, then run agkit sync and the catalog, README table and AGENTS.md all catch up.

Step 3: catalog a plugin that lives in someone else's repo

Here's where a marketplace beats a folder of copies.

There's a public plugin you like — say a changelog generator, published in its own repository. You want it in your toolkit, but you don't want to own its code, copy it in, or track its upstream changes by hand.

So don't. Point at it:

agkit add octo-org/changelog-skill changelog \
  --ref v1.2.0 \
  -d "Generate a release changelog from git history"
Enter fullscreen mode Exit fullscreen mode
◆  Registered changelog as a remote plugin (octo-org/changelog-skill) in marketplace.json — nothing was cloned.
│
●  Claude Code fetches it from the source at install time:
│    /plugin install changelog@dev-toolkit
│  (pass --vendor to clone it into ./plugins instead)
Enter fullscreen mode Exit fullscreen mode

Nothing was cloned. Your repo gained exactly one thing — an entry in the catalog:

{
  "name": "changelog",
  "source": {
    "source": "github",
    "repo": "octo-org/changelog-skill",
    "ref": "v1.2.0"
  },
  "description": "Generate a release changelog from git history"
}
Enter fullscreen mode Exit fullscreen mode

The agent resolves that source when a user installs the plugin. No vendored code, no version to keep in sync, no fork to maintain. The --ref v1.2.0 pins it to a tag so upstream can't move under your users' feet — use --sha <commit> if you want to pin harder.

This is the default for any remote git source, and the spec grammar is flexible: bare owner/repo, gh:/gl: shorthands, or a full git URL for any forge. If the plugin sits in a subdirectory of a bigger repo, point at the folder with //:

agkit add "https://gitlab.com/team/marketplace.git//my-plugin" my-plugin
Enter fullscreen mode Exit fullscreen mode

And when you do want the files copied in — seeding a plugin from a shared template you intend to modify — that's what --vendor is for. It clones the repo, renders it, and drops the result into plugins/<name>/ as your code. The rule of thumb: remote sources are referenced by default; --vendor when you want to own the copy.

Three ways to add a plugin: scaffold from a template copies files into your repo, reference points at someone else's repo without cloning, and vendor clones the files in

Step 4: validate and publish

agkit validate runs local checks — JSON validity, kebab-case names, reserved names, source resolution, manifest presence, version drift — and delegates to claude plugin validate when the Claude Code CLI is installed:

agkit validate
Enter fullscreen mode Exit fullscreen mode
●  Note: relative plugin sources resolve only when the marketplace is added via
   Git (any forge), not via a direct URL to marketplace.json.
◆  claude plugin validate: passed
◆  .claude-plugin/marketplace.json is valid
Enter fullscreen mode Exit fullscreen mode

It exits non-zero on error, which is why the CI workflow that init generated already calls it.

Publishing is the anticlimax — it's just git:

git remote add origin https://github.com/jane/dev-toolkit.git
git add -A
git commit -m "feat: initial marketplace"
git push -u origin main
Enter fullscreen mode Exit fullscreen mode

That's it. That's the distribution step. No registry, no account, no publish token. Your teammate now runs:

/plugin marketplace add jane/dev-toolkit
/plugin install commit-crafter@dev-toolkit
Enter fullscreen mode Exit fullscreen mode

Copilot users get the same repo through copilot plugin marketplace add jane/dev-toolkit. And because distribution is just git push, none of this is GitHub-specific — GitLab, Bitbucket, Gitea and self-hosted forges all work, agkit just emits the git URL form instead of the owner/repo shorthand.

Better still, init wrote an examples/team-settings.json with an extraKnownMarketplaces block. Drop it into .claude/settings.json in your team's repo and teammates get the marketplace automatically when they trust the repository — nobody has to add anything by hand.

Step 5: ship an improvement

This is the step neither the zip nor the drawer could ever do.

You improve the skill — it should now recognise breaking changes and add the footer. Commit it with a conventional message, scoped to the plugin:

git add plugins/commit-crafter
git commit -m "feat(commit-crafter): detect breaking changes and add a BREAKING CHANGE footer"
Enter fullscreen mode Exit fullscreen mode

Preview the release before committing to it:

agkit bump commit-crafter --dry-run
Enter fullscreen mode Exit fullscreen mode
●  2 commit(s) (no previous release tag):
│    minor  feat(commit-crafter): detect breaking changes and add a BREAKING CHANGE footer
│    minor  feat: initial marketplace
│
●  [dry-run] commit-crafter: 0.1.0 -> 0.2.0 (minor) + CHANGELOG.md entry
Enter fullscreen mode Exit fullscreen mode

agkit read the commits that touch that plugin's directory since its last commit-crafter@x.y.z tag — there isn't one yet, hence "no previous release tag" and the initial commit being counted too — and applied conventional-commit rules: a feat means minor, so 0.1.0 → 0.2.0. A fix would be a patch; a feat! or BREAKING CHANGE: would be major. You can always force it: agkit bump commit-crafter major.

Happy with it? Do it for real, and let agkit commit and tag the release:

agkit bump commit-crafter --tag
Enter fullscreen mode Exit fullscreen mode
◆  commit-crafter: 0.1.0 -> 0.2.0 (minor)
◆  Committed and tagged commit-crafter@0.2.0 — push with: git push --follow-tags
Enter fullscreen mode Exit fullscreen mode

In one command it wrote the new version into the plugin manifest, generated a changelog entry from your commit subjects, and ran sync internally so the version flowed everywhere it needs to be:

# Changelog

## 0.2.0 — 2026-07-16

- feat(commit-crafter): detect breaking changes and add a BREAKING CHANGE footer
- feat: initial marketplace
Enter fullscreen mode Exit fullscreen mode

The catalog now says 0.2.0. So does the README table. So does the AGENTS.md list. You updated none of them.

The release loop: edit, commit with a conventional message, agkit bump --tag, git push --follow-tags — one command writes the manifest version, the changelog, the catalog, the README and AGENTS.md tables, and the release tag

git push --follow-tags
Enter fullscreen mode Exit fullscreen mode

The --follow-tags matters: the tag is the boundary the next bump measures from, so it has to reach the forge. Your teammate runs /plugin update and has your fix. Versions are per plugin, by the way — each one carries its own manifest version, changelog and tags, so a busy plugin never drags a quiet one into a release.

One caveat worth knowing: you can't bump a referenced remote plugin like changelog. Its version is owned upstream and you only hold a reference. To move it forward, re-pin it: agkit add octo-org/changelog-skill changelog --ref v1.3.0.

The loop, condensed

npx agkit init dev-toolkit                    # scaffold a push-ready repo
agkit add skill commit-crafter                # scaffold a plugin, then edit it
agkit add octo-org/changelog-skill changelog --ref v1.2.0   # reference, don't clone
agkit validate && git push -u origin main     # publish

# later:
git commit -m "feat(commit-crafter): …"
agkit bump commit-crafter --tag               # version + changelog + sync + tag
git push --follow-tags
Enter fullscreen mode Exit fullscreen mode

Five commands to go from a folder on your laptop to something a team installs by name and updates on its own.

The genuine shift here isn't the scaffolding — it's that your skills stop being files you hand people and become software you ship: each one a plugin with a name, a version, a changelog and a release history, listed in a catalog, validated in CI. The same repo can serve Claude Code, Copilot, Codex and Cursor, from any git forge, because there's one catalog and everything else derives from it.

And notice how little of that is about agkit. The plugin and the marketplace are the platform's model — they're what let your teammate install one thing instead of fourteen, at a version, and update it without asking you. agkit's contribution is narrower and, honestly, more useful: it makes producing that structure cheap enough that you'll actually do it, instead of pushing another folder into the drawer.

Which, it turns out, was all the zip was ever missing.


agkit is MIT-licensed and unaffiliated with Anthropic, GitHub, OpenAI or Cursor.

If you build something with it, I'd love to see the catalog.

Top comments (0)