VAT Distribution: Build, Publish & Install
Scope
This skill covers the file-based install method for Claude Code CLI (~/.claude/plugins/).
This is the only install method VAT currently supports.
For the full install landscape — Claude Desktop paths, enterprise CI deployment,
Anthropic Cloud org management, MDM integration, and the vat claude plugin uninstall
design — see the contributor reference at docs/contributing/vat-install-architecture.md
in the vibe-agent-toolkit repo (contributor material, not bundled with this skill).
Overview
VAT distributes skills as Claude plugins via npm packages. The pipeline:
vat buildcompiles SKILL.md sources into plugin artifactsnpm publishpushes the package to a registrynpm installtriggers a postinstall hook that registers the plugin in Claude Code's plugin system
Skills installed this way appear in Claude Code as /plugin-name:skill-name.
Project Structure
my-project/
├── package.json ← vat.skills + postinstall hook + publishConfig
├── vibe-agent-toolkit.config.yaml ← skills: + claude: config
├── resources/
│ └── skills/
│ └── SKILL.md
└── dist/ ← generated by vat build
├── skills/my-skill/ ← packaged skill
└── .claude/plugins/marketplaces/
└── my-marketplace/plugins/my-plugin/
├── .claude-plugin/plugin.json
└── skills/my-skill/SKILL.md
Step 1: package.json Configuration
{
"name": "@myorg/my-skills",
"version": "1.0.0",
"vat": {
"version": "1.0",
"skills": ["my-skill"]
},
"dependencies": {
"vibe-agent-toolkit": "latest"
},
"scripts": {
"build:vat": "vat build",
"postinstall": "vat claude plugin install --npm-postinstall 2>/dev/null || exit 0"
},
"files": ["dist", "README.md"],
"publishConfig": {
"registry": "https://registry.npmjs.org"
}
}
vibe-agent-toolkit must be in dependencies (not devDependencies). npm adds all bin entries from runtime dependencies to ./node_modules/.bin/ and puts that on PATH when running lifecycle scripts. This is how vat is available during your postinstall without being globally installed. If it is in devDependencies, npm will not install it on the user's machine and the postinstall will fail with "command not found".
The vat.skills array contains skill name strings for npm discoverability. Skill source paths and packaging config live in vibe-agent-toolkit.config.yaml (see Step 2).
For private GitHub Packages registry:
"publishConfig": {
"registry": "https://npm.pkg.github.com",
"access": "restricted"
}
Users (or IT) installing from GitHub Packages need .npmrc configured with the scope registry and a read-only token:
@myorg:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
IT deploying to managed machines should pre-configure .npmrc at the system or user level before running install commands. End users do not need to understand npm or the registry — IT handles it once.
Handling Plugin Renames: vat.replaces
Use this only when renaming a plugin, merging plugins, or cleaning up legacy flat-skill installs. Normal upgrades (same plugin name, same skills) do NOT need
vat.replaces— the installer already overwrites the plugin directory on every install.
The problem: silent stale skills
When you rename a plugin or reorganize skills across packages, the old registration remains in Claude Code. Claude Code does not warn you when two plugins provide conflicting skill names — the first-registered (stale) skill silently wins. Users continue loading old content from the renamed plugin unless the old registration is explicitly removed.
This is the scenario where vat.replaces is needed:
- Plugin renamed:
old-plugin-name→new-plugin-name - Two old plugins merged into one new plugin
- Skills previously installed to
~/.claude/skills/<name>(legacy pre-0.1.20 flat install) now delivered via the plugin tree
How it works
When a VAT package is installed (via postinstall hook or --dev), the installer reads vat.replaces from package.json and — before installing the new plugin:
- For each name in
replaces.plugins: uninstalls<name>@<marketplace>— removes plugin directory, cache entry, registry entry, andsettings.jsonentry - For each name in
replaces.flatSkills: deletes~/.claude/skills/<name>— removes legacy pre-0.1.20 flat installs
Both operations are idempotent — "not found" is handled gracefully.
Schema
"vat": {
"version": "1.0",
"skills": ["authoring", "audit"],
"replaces": {
"plugins": ["my-old-plugin-name"],
"flatSkills": ["my-old-skill", "another-old-skill"]
}
}
Both plugins and flatSkills are optional arrays. The entire replaces key is optional — omit it when there is nothing to clean up.
Real example: vat-development-agents 0.1.21
This package (vat-development-agents) renamed its plugin from vat-development-agents to vibe-agent-toolkit in v0.1.21. Without vat.replaces, users who had already installed v0.1.20 would have both vat-development-agents@vat-skills and vibe-agent-toolkit@vat-skills registered — Claude Code would serve stale skill content from the old plugin.
The fix in package.json:
"vat": {
"version": "1.0",
"skills": ["vibe-agent-toolkit", "resources", "distribution", "authoring", "audit", "debugging", "install"],
"replaces": {
"plugins": ["vat-development-agents"],
"flatSkills": ["vibe-agent-toolkit", "resources"]
}
}
plugins: removes the old plugin registration (same marketplace, old plugin name)flatSkills: removes legacy~/.claude/skills/vibe-agent-toolkitand~/.claude/skills/resourcesentries from users who installed before 0.1.20 switched to the plugin tree
Non-obvious gotchas
1. Plugin names in replaces.plugins have NO @marketplace
The format is just the plugin name — e.g. "vat-development-agents" — NOT "vat-development-agents@vat-skills". The installer infers the marketplace from the current package's dist tree. Using @marketplace syntax here would be wrong.
2. replaces.plugins is for old plugin registrations, not for skills that moved between plugins
If a skill moved from plugin-a to plugin-b within the same marketplace, list "plugin-a" in replaces.plugins to clean up the entire old plugin. You do not manage individual skills — you manage plugins.
3. replaces.flatSkills is ONLY for the legacy ~/.claude/skills/ location
This is specifically for skills that were previously installed as flat files to ~/.claude/skills/<name> (pre-plugin-tree, before v0.1.20). Skills within the plugin tree (under ~/.claude/plugins/) are handled via replaces.plugins. Do not mix them up.
4. Normal upgrades need nothing
If you publish a new version of the same package with the same plugin name, the installer overwrites the plugin directory automatically. vat.replaces is only for the case where the old name is different from the new name.
5. The symptom is subtle and delayed
You will not see an error. Claude Code simply loads the first-registered skill with a given name. If the stale plugin is registered first (alphabetically or by install order), your new content is invisible until you remove the old registration.
Step 2: vibe-agent-toolkit.config.yaml
version: 1
skills:
include:
- "resources/skills/**/SKILL.md"
claude:
marketplaces:
my-marketplace: # org/publisher identity
owner:
name: My Organization
plugins:
- name: my-plugin # installable unit
description: My plugin description
The top-level skills: section drives standalone skill builds (output: dist/skills/). The claude: section defines plugins, which are assembled from their own plugins/<name>/ directories (plugin-local skills under plugins/<name>/skills/**/SKILL.md). Each marketplace has owner and plugins fields (strict schema — no extra fields).
Naming convention: marketplace = org identity (e.g. acme), plugin = this package
(e.g. acme-tools). Registers as my-plugin@my-marketplace in Claude's plugin registry.
Multiple skills in one plugin
List all skills in vat.skills for npm discoverability:
"vat": {
"version": "1.0",
"skills": ["my-linting", "my-testing"]
}
Each skill lives as a subdirectory of the plugin under plugins/<name>/skills/<skill>/SKILL.md:
plugins/my-plugin/
skills/
my-linting/SKILL.md
my-testing/SKILL.md
All plugin-local skills found under plugins/<name>/skills/ are packaged into the plugin automatically — no per-plugin selector is needed or supported. Skill names must be globally unique across all plugins.
Step 3: Build
vat build # skills phase then claude phase
vat verify # validates resources + skills + claude artifacts
What vat build does
Two phases, run in dependency order:
vat skills build— readsvibe-agent-toolkit.config.yaml skills:section, discovers SKILL.md files via include/exclude globs, compiles each intodist/skills/<name>/vat claude plugin build— readsvibe-agent-toolkit.config.yaml claude:section, wraps built skills intodist/.claude/plugins/marketplaces/<mp>/plugins/<plugin>/structure with.claude-plugin/plugin.json. Cleans stale output before each build.
Individual commands still work:
vat skills build # skills phase only
vat claude plugin build # claude plugin phase only (requires skills already built)
Step 4: Publish
npm publish --tag next # RC/pre-release
npm publish # stable release
Marketplace Distribution
Marketplace distribution publishes a dedicated branch to GitHub that Claude Code users can install directly — no npm account or registry required.
How it works
vat buildcompiles skills and plugin artifacts intodist/vat claude marketplace publishpushes thedist/.claude/tree to a dedicated branch (e.g.claude-marketplace) in your GitHub repo- Users install via the slash command:
/plugin marketplace add owner/repo#claude-marketplace
Configuration
Add a publish section under your marketplace in vibe-agent-toolkit.config.yaml:
claude:
marketplaces:
my-marketplace:
owner:
name: My Organization
plugins:
- name: my-plugin
description: My plugin description
publish:
github:
repo: owner/repo # GitHub repo to publish to
branch: claude-marketplace # branch that stores the installable artifacts
Publish workflow
vat build # build all artifacts first
vat claude marketplace publish # push dist/.claude/ to the publish branch
vat claude marketplace publish --dry-run # preview what would be published (no push)
Consumer install
Once published, users install with:
/plugin marketplace add owner/repo#claude-marketplace
No npm, no registry, no token required. Claude Code fetches the branch directly from GitHub.
Testing locally
After publishing, verify the marketplace works end-to-end:
claude plugin marketplace add owner/repo#claude-marketplace
claude plugin install my-plugin@my-marketplace
claude plugin validate ~/.claude/plugins/cache/my-marketplace/my-plugin/<version>
claude plugin list # verify status: enabled
Start a new Claude Code session to confirm skills load. See the Marketplace Distribution Guide for the full testing checklist and known issues.
Note (Claude Code v2.1.81): If re-adding a marketplace with the same name as an existing one (e.g., switching from npm to GitHub source), remove the old marketplace first: claude plugin marketplace remove <name> then re-add. Otherwise the old source is silently reused.
Per-plugin versioning (multi-plugin marketplaces)
VAT supports two versioning models for a marketplace:
Single-version (default for skills-only marketplaces). No version is declared on individual plugins. All plugins inherit the root package.json:version. This is the model used by vibe-agent-toolkit — the marketplace is treated as one release artifact.
Per-plugin versioning (multi-plugin marketplaces with independent release cadences). Each plugin declares its own version. Recommended when topical plugins under one marketplace evolve on independent timelines.
Where to declare a per-plugin version
Two options, in precedence order:
- Marketplace config (
vibe-agent-toolkit.config.yaml) — most explicit:claude: marketplaces: my-marketplace: plugins: - name: ai-digest version: 0.2.0 skills: '*' - Plugin source (
plugins/<name>/.claude-plugin/plugin.json:version) — most ergonomic for plugin authors:{ "name": "ai-digest", "version": "0.2.0", "description": "..." }
If both declare a version, marketplace config wins and VAT logs a reconciliation warning. If neither is declared, VAT falls back to the root package.json:version (single-version model).
What vat claude marketplace publish does for multi-plugin marketplaces
For each plugin with a resolved version:
- The published
.claude-plugin/marketplace.jsonincludes the per-pluginversionfield on each plugin entry. - If
<plugin.source>/CHANGELOG.mdexists in source (or the marketplace plugin entry'schangelogfield points to a file), it is bundled into the published marketplace atplugins/<name>/CHANGELOG.md, alongside the marketplace-level CHANGELOG.
The marketplace-level CHANGELOG (under publish.changelog in the config) continues to work unchanged.
Default CHANGELOG location
The default per-plugin CHANGELOG path is <plugin.source>/CHANGELOG.md, anchored to the entry's source field (default plugins/<name>). It is NOT assumed to be plugins/<name>/CHANGELOG.md — if you override source, the default CHANGELOG path follows.
To use a non-default path, set the changelog field on the marketplace plugin entry (relative to the plugin source dir):
plugins:
- name: ai-digest
source: plugins/ai-digest
changelog: docs/RELEASES.md
skills: '*'
Backwards compatibility
Marketplaces with no per-plugin version anywhere are unaffected. The root package.json version flows through to every plugin, and the published marketplace.json either omits per-plugin version or includes the same value for all plugins.
Referencing another marketplace's plugin (externalSource)
A plugin entry can point at a plugin published elsewhere (github/url/npm/pip,
matching Anthropic's official Plugin marketplaces
source shapes) instead of being built here — set externalSource with skills: [] and
omit source/files/exclude/changelog; VAT never fetches or vendors it, only writes
the reference into marketplace.json. See "Referencing Another Marketplace's Plugin" in
docs/guides/marketplace-distribution.md
for the field reference and a worked example.
Step 5: User Install
Recommended: npm global install (postinstall runs automatically)
npm install -g @myorg/my-skills
The postinstall hook fires automatically and registers the plugin in Claude. This is the correct path for IT-managed deployments — no other tools required on the user's machine.
Developer/IT one-off install via npx
npx vibe-agent-toolkit claude plugin install npm:@myorg/my-skills
Downloads and runs VAT via npx to install a package without a global install. Useful for CI, scripting, or testing from a developer machine. Requires the npm scope registry to be configured (.npmrc) if installing from a private registry.
How plugin installation works
When npm install runs the postinstall hook (vat claude plugin install --npm-postinstall):
- VAT detects
dist/.claude/plugins/marketplaces/directory in the installed package - Copies the plugin tree to Claude's plugin directory (dumb recursive copy)
- Writes to these locations:
~/.claude/plugins/marketplaces/<marketplace>/plugins/<plugin>/— plugin files~/.claude/plugins/known_marketplaces.json— marketplace registry~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/— version cache~/.claude/plugins/installed_plugins.json— installation record~/.claude/settings.jsonenabledPlugins— activates the plugin
If no dist/.claude/plugins/marketplaces/ directory exists (package wasn't built before publish): a guidance message is emitted and the hook exits 0. The publisher must run vat build and re-publish.
Skills are then available in Claude Code as /plugin-name:skill-name.
managed-settings.json Validation (Enterprise)
claude:
managedSettings: managed-settings.json
vat verify validates this file against the ManagedSettings schema. Catches typos and schema errors before deployment. Does NOT deploy the file — deployment is a separate concern.
--target claude-web (ZIP Upload)
For uploading skills directly to claude.ai/settings/capabilities:
vat skills package ./SKILL.md -o ./dist/ --target claude-web
Produces a ZIP:
my-skill.zip
└── my-skill/
├── SKILL.md # skill definition (required)
└── references/ # every auto-discovered bundled file — optional
⚠️ claude-web does not route by extension. The claude-code target sorts auto-discovered
files into resources/, scripts/, templates/ and assets/ by extension; claude-web
flattens all of them into references/ (getResourceSubdirForFile in
packages/agent-skills/src/skill-packager.ts). Do not write a skill that expects a
scripts/foo.py path in a claude-web ZIP.
To place a file somewhere the automatic routing would not put it — a build artifact, an unlinked
file, or a routing override — declare an explicit source→dest mapping with files:. Destinations
are relative to the skill output directory, so they are what fixes the layout for either target:
skills:
include:
- "skills/**/SKILL.md"
config:
my-skill:
files:
- source: "dist/helpers/format.mjs"
dest: "scripts/format.mjs"
- source: "assets/template.json"
dest: "assets/template.json"
source is relative to the project root and may be a glob; dest is relative to the skill output
directory and must stay inside it. See vibe-agent-toolkit:vat-skill-authoring for the full
files: semantics.
⚠️ VAT does not compile or bundle TypeScript for you. source must point at a file that
already exists at build time — run your own build step first and point files: at its output.
Quick Reference
| Task | Command |
|---|---|
| Validate sources (no build) | vat validate |
| Build everything | vat build |
| Verify built artifacts (after build) | vat verify |
| Build skills only | vat skills build |
| Build claude plugin artifacts only | vat claude plugin build |
| Install via npm (end user) | npm install -g @org/pkg |
| Install via npx (developer/IT) | npx vibe-agent-toolkit claude plugin install npm:@org/pkg |
| List installed plugins | vat claude plugin list |
| Uninstall a plugin | vat claude plugin uninstall --all |
| Package for claude.ai upload | vat skills package ./SKILL.md -o ./dist/ --target claude-web |
Running VAT Without Global Install
npx vibe-agent-toolkit <command> # npm/Node.js
bunx vibe-agent-toolkit <command> # Bun
All vat commands in this skill work with these alternatives.
Future: Zero-Dependency Postinstall (Option B)
A planned improvement: vat build would bundle the plugin install logic into dist/postinstall.js — a fully self-contained script with no npm dependencies. The postinstall script would become simply node ./dist/postinstall.js. This eliminates vibe-agent-toolkit as a runtime dependency entirely, reducing install footprint for end users. Until then, Option C (runtime vibe-agent-toolkit dep) is the correct approach.
Full-plugin authoring (commands, hooks, agents, MCP)
VAT supports bundling any Claude Code plugin asset — not just skills. Drop the plugin
under plugins/<name>/ in the same native layout Claude expects. VAT tree-copies
everything (minus skills/ and .claude-plugin/), merges author plugin.json with
the identity fields the marketplace config owns, and applies any files[] mappings for
artifacts built outside the plugin dir. The config owns name, version, and the
author subfields owner can express (name, email); every other author subfield —
author.url in particular, which VAT's config has no field for — passes through from
plugin.json untouched, and the generated marketplace.json republishes that same
merged author. A files: entry's source may be a glob (*, **, ?, [) — the entry then fans out to a directory dest using prefix-strip + tail-preserve mapping, with the glob resolved only at build time so SKILL.md links to files landing under a glob dest are treated as deferred artifacts at validate time. An optional integrity: true flag byte-verifies the copy at build time and (for glob entries) asserts the dest subtree is an exact match. A glob honors the never-package list; an explicit entry does not — a glob drops agent-instruction files (CLAUDE.md, AGENTS.md, …) and, in a skill bundle, navigation files (README.md, index.md, …), while source: extras/README.md still ships because naming a path is an instruction. For the full model — prefix-strip mechanics, late-binding semantics, sibling sources, never-package tiers, and scope limits — see docs/guides/skill-files-and-routing.md.
The plugin tree-copy drops agent-instruction files at any depth but keeps README.md — a plugin-root README is the plugin's front page. Project-specific junk goes in exclude: ["scratch/**"] on the plugin entry.
See docs/guides/marketplace-distribution.md section "Full-plugin authoring".