Releasing agent plugins
An agent plugin for Claude Code, Cursor, Codex, or Copilot CLI is installed from a
git repository or a marketplace catalog, not from npm. It carries its version in
plugin.json and the vendor manifests, not only in package.json. Changesets still
fits, but its defaults cover only package.json and npm:
changeset versionbumpspackage.jsonand nothing else, so the manifests drift.changeset publishpublishes to npm, and skips private packages.
Changesets has no plugin API for other version files. Both gaps close with its
existing configuration and with scripts around the CLI. The
init-changesets skill sets this up when
it detects a plugin repository.
How it works
Section titled “How it works”-
The plugin package is private. Its
package.jsonhas"private": trueand holds the version. A plugin without apackage.jsongets a minimal one. Changesets cannot run without one:changeset version,changeset publish,changesets/action, and changesets-gitlab all find packages by readingpackage.json, and changesets has no plugin API to change that. -
Changesets versions and tags private packages. In
.changeset/config.json:{ "privatePackages": { "version": true, "tag": true } } -
The
versionscript syncs the manifests. It chains a sync step afterchangeset version, so the Version Packages PR updatespackage.json,CHANGELOG.md, every plugin manifest, and the matching marketplace catalog entries together. -
The
releasescript tags instead of publishing.changeset publishruns nonpm publishfor a private package. It creates the<name>@<version>git tag (v<version>in a single-package repository). -
The release workflow pushes the tag and creates the GitHub release.
changesets/action@v2runs thereleasescript and reads the tags from the CLI’s output report. It then pushes each tag and creates a GitHub release. Users install the plugin from that tag.
Scripts
Section titled “Scripts”universal-plugin does the sync step:
| Repository | Sync step |
|---|---|
| universal-plugin 0.17 or later | universal-plugin plugin version --from-package |
| An older universal-plugin | universal-plugin publish sync-version && universal-plugin plugin build |
| Has its own version-sync command | That command |
{ "scripts": { "version": "changeset version && universal-plugin plugin version --from-package", "release": "changeset publish", "cs": "changeset" }}plugin version --from-package stamps the package.json version into plugin.json,
the vendor manifests, the repository’s marketplace catalogs, and the skill pins. Add
--root <dir> for a plugin in a subdirectory.
Release workflow
Section titled “Release workflow”- name: Create Release PR or Tag uses: changesets/action@v2 with: version-script: pnpm run version publish-script: pnpm run release github-token: ${{ secrets.GITHUB_TOKEN }}Keep publish-script even though nothing goes to npm. Without it the action does
nothing after the Version Packages PR merges, and no tag or release ever marks the
version. The job needs contents: write and pull-requests: write, but not
id-token: write.
Check the setup
Section titled “Check the setup”pnpm exec changeset publish-plan --output plan.json && cat plan.json && rm plan.jsonEvery entry must be "kind": "tag-only" and name a plugin package. "kind": "npm"
means a package is not private. An extra tag-only entry is another private workspace
project with a version, such as a docs app, that would be tagged on every release.
Remove its version field or list it in "ignore".
Marketplaces in other repositories
Section titled “Marketplaces in other repositories”A catalog in the same repository that points at the plugin by a relative path only
needs its version field updated, and the sync step does that. A marketplace in
another repository that pins the plugin by ref or sha needs its own update.
Trigger that update from the GitHub release. A release created with GITHUB_TOKEN does
not start other workflows, so this needs a PAT or a GitHub App token.
This repository releases its own plugin this way. See its release workflow.