Learning Guide: Skill, Plugin, and Extension Distribution Patterns for CLI Tools and AI Agent Frameworks
Generated: 2026-02-21 Sources: 40 resources analyzed (from training knowledge -- web fetch unavailable) Depth: deep
How open source CLI tools and AI agent frameworks distribute reusable skills, plugins, and extensions. Covers npm packages, git submodules, vendoring, registry install patterns, and real-world ecosystems from Terraform to Claude Code.
- Familiarity with at least one package manager (npm, pip, cargo, etc.)
- Basic understanding of git workflows
- General awareness of what CLI plugins do (extend a host tool's functionality)
- Helpful but not required: experience with at least one of the ecosystems discussed (Terraform, Homebrew, VSCode, oh-my-zsh, etc.)
- Five dominant distribution patterns exist: package registry (npm/pip), git-based (clone/submodule), vendoring (copy into repo), dedicated registry (Terraform, VSCode), and convention-based discovery (file-system scanning).
- Small ecosystems (under 50 plugins) succeed with git-based or vendored approaches -- low overhead, no registry infrastructure needed. Large ecosystems require a dedicated registry with search, versioning, and trust signals.
- AI agent frameworks are converging on a hybrid: convention-based local discovery (scan directories for SKILL.md / plugin.json) plus git or npm for remote installation.
- The critical design decision is not the transport mechanism (git vs npm vs registry) but the plugin contract: what interface a plugin must implement, how it declares capabilities, and how the host discovers and loads it.
- Discoverability is the bottleneck for adoption in every ecosystem. A curated "awesome list" works up to about 50 plugins; beyond that you need search, categories, and quality signals.
Plugins are published to a general-purpose package registry and installed via the ecosystem's package manager.
How it works: The plugin is a regular package with a conventional name prefix (e.g., eslint-plugin-*, babel-plugin-*, @opencode/plugin-*). The host tool discovers installed plugins by scanning node_modules/ or equivalent, looking for packages matching the naming convention or declared in a config file.
Examples:
- ESLint plugins (
eslint-plugin-react) - Babel plugins (
@babel/plugin-transform-runtime) - Prettier plugins (
prettier-plugin-tailwindcss) - Gatsby plugins (listed in
gatsby-config.js) - Rollup/Vite plugins (npm packages, referenced in config)
Strengths: Leverages existing infrastructure (versioning, dependency resolution, security audits, CDN distribution). Users already know npm install. Transitive dependencies handled automatically.
Weaknesses: Tied to one language ecosystem. Registry overhead for small projects. Namespace pollution. Hard to enforce plugin quality.
Key insight: This pattern works best when the host tool is already in a package-manager ecosystem and plugins need complex dependencies of their own.
Plugins are git repositories. The host tool or a plugin manager clones them into a known directory.
Examples:
- oh-my-zsh plugins and themes (git clone into
~/.oh-my-zsh/custom/plugins/) - asdf version manager plugins (
asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git) - mise plugins (inherited asdf's git-based model, then added a shortname registry)
- Vim plugin managers (vim-plug, Vundle, Pathogen -- all clone git repos)
- Zsh plugin managers (zinit, antigen, zplug)
How it works: User provides a git URL (or a shortname that resolves to one). The tool clones the repo into a plugins directory. Updates via git pull. The plugin repo follows a file convention (e.g., asdf requires bin/install, bin/list-all, bin/list-legacy-filenames).
Strengths: Zero infrastructure beyond GitHub. Any language. Simple mental model. Fork-and-customize is natural. Works offline after initial clone.
Weaknesses: No dependency resolution between plugins. No semantic versioning enforcement (tags are optional). No centralized search. Update = git pull (can break things). Large repos slow to clone.
Key insight: Git-based distribution dominates in small ecosystems where the plugin contract is simple (a few shell scripts) and plugins have no dependencies beyond the host.
Plugins are copied directly into the consuming project's source tree, either manually or via a vendoring tool.
Examples:
- Go's
vendor/directory (pre-modules era, still supported) - Claude Code's plugin system (plugins vendored into
.claude/plugins/cache/) - AgentSys's
lib/vendoring to plugins (npx agentsys-dev sync-lib) - Ruby's vendored gems (
bundle install --path vendor/bundle) - Many internal "monorepo plugin" patterns
How it works: Plugin source code lives in the consumer's repo or a local cache directory. No external resolution at runtime. The host tool scans known directories for plugin manifests.
Strengths: Total reproducibility -- what you see is what runs. No network needed at runtime. Easy to patch locally. No version conflict between plugins (each project has its own copy). Simple for the host tool (just scan the filesystem).
Weaknesses: Manual updates. Disk space (N copies of the same plugin). Drift between projects using different versions. No centralized discovery.
Key insight: Vendoring is ideal when reproducibility and offline operation matter more than convenience. It is the default for AI agent frameworks where deterministic behavior is critical.
A purpose-built registry with search, versioning, trust verification, and install tooling specific to the ecosystem.
Examples:
- Terraform Registry (registry.terraform.io) -- providers and modules
- VSCode Extension Marketplace (marketplace.visualstudio.com)
- Homebrew Taps (GitHub repos following
homebrew-*naming convention with a centralformulae.brew.shindex) - JetBrains Plugin Marketplace
- Grafana plugin catalog
- HashiCorp Vagrant Cloud (boxes)
How it works: Plugin authors publish to the registry (often via CI). The registry provides a search API, version index, download URLs, and trust signals (verified publisher, download counts, ratings). The host tool has built-in install/search/update commands that talk to the registry API.
Strengths: Best discoverability. Quality signals (stars, downloads, verified status). Automated compatibility checking. Centralized security scanning. Professional experience for users.
Weaknesses: High infrastructure cost. Governance burden (who approves plugins?). Can become a bottleneck or single point of failure. Lock-in to the registry operator.
Key insight: Dedicated registries only make sense at scale (100+ plugins). Below that, the maintenance cost exceeds the discoverability benefit.
The host tool scans predefined directories for files matching a convention. No explicit "install" step beyond placing files in the right location.
Examples:
- Claude Code's CLAUDE.md and slash commands (scans project directory)
- OpenCode's AGENTS.md and tools directory
- Codex's AGENTS.md
- GitHub Actions (scans
.github/workflows/for YAML files) - Makefiles, Taskfiles (convention over configuration)
- Many shell frameworks (source all
*.shin a directory)
How it works: The host tool knows to look in specific paths (e.g., .claude/commands/, plugins/*/SKILL.md). Any file matching the expected shape is loaded as a plugin. No registry, no install command.
Strengths: Zero friction. No tooling to learn. Works with any editor or workflow. Composable via copy-paste. Ideal for project-local customization.
Weaknesses: No versioning. No sharing mechanism (copy-paste is the distribution). No dependency management. Hard to discover what is available.
Key insight: Convention-based discovery is the starting point for most plugin systems. Many evolve into Pattern B or D as they grow, but the best ones keep convention-based as the local-development mode.
Terraform is the gold standard for dedicated-registry plugin distribution. Before Terraform 0.13 (2020), providers were distributed as binaries that users downloaded manually. The Terraform Registry changed everything.
Registry architecture: The registry at registry.terraform.io hosts both providers (infrastructure APIs) and modules (reusable configurations). Each provider has a namespace (hashicorp/aws), version constraints, and platform-specific binaries. Terraform CLI resolves providers from the lock file (.terraform.lock.hcl), downloads platform-specific binaries, and caches them in .terraform/providers/.
Plugin protocol: Providers implement a gRPC interface. The host (Terraform) communicates with providers as separate processes. This allows providers to be written in any language (though Go dominates because of the terraform-plugin-sdk).
Discovery: terraform init resolves required providers from required_providers blocks. The registry API returns available versions and download URLs. Users discover providers via the registry website or documentation.
What works: Version locking, platform-specific distribution, namespace isolation, the gRPC boundary between host and plugin.
What hurts: Binary size (each provider is 50-200MB). Cold terraform init is slow. The registry is a single point of failure (mirrored by network mirrors).
Lesson for small ecosystems: Terraform's approach is overkill for under 50 plugins. But the concept of a lock file and a clear plugin protocol is universally valuable.
Homebrew uses a hybrid of git-based and registry patterns.
Core architecture: The main Homebrew repository (homebrew-core) is a git repo of "formulae" -- Ruby scripts that describe how to download, build, and install software. Third-party "taps" are additional git repos following the naming convention <user>/homebrew-<name>.
Install flow: brew tap user/repo clones the tap repo. brew install user/repo/formula installs from that tap. Homebrew also has a JSON API at formulae.brew.sh that indexes all formulae for search without cloning.
Discovery: brew search queries the JSON API. Tap discoverability is mostly word-of-mouth and GitHub search.
Scaling trick: Homebrew keeps homebrew-core as a shallow git clone and uses a JSON API for metadata, avoiding the full git history download. This is how they scaled to 6000+ formulae.
Lesson: The tap model (git repos with naming conventions) is excellent for small ecosystems. The JSON API overlay solves discoverability at scale.
oh-my-zsh pioneered the "bundled + custom" plugin model for shell frameworks.
Bundled plugins: oh-my-zsh ships 300+ plugins in its main repo. Users enable them by adding names to an array in .zshrc. This is Pattern C (vendoring) -- all plugins ship with the framework.
Custom plugins: Users can add plugins to ~/.oh-my-zsh/custom/plugins/. These follow the convention of pluginname/pluginname.plugin.zsh. Installation is manual (git clone or copy).
Evolution: The bundled model does not scale. oh-my-zsh's repo became bloated (300+ plugins, most unused by any given user). This spawned alternative managers:
- antigen: Package manager for zsh, installs plugins from git repos
- zinit (formerly zplugin): Advanced lazy-loading plugin manager
- zplug: Parallel plugin installer with dependency support
- sheldon: Rust-based, fast, config-file-driven
Lesson: Bundling everything works for initial adoption (zero friction) but creates maintenance burden. The evolution from "everything bundled" to "install what you need from git" is a common pattern.
asdf is a version manager that uses plugins to support different languages/tools.
Plugin contract: An asdf plugin is a git repo containing shell scripts in bin/:
bin/list-all-- list available versionsbin/install-- install a versionbin/exec-env-- set environment for execution (optional)
Shortname registry: asdf maintains a shortname registry (asdf-plugins repo) mapping names to git URLs. asdf plugin add nodejs resolves to https://github.com/asdf-vm/asdf-nodejs.git. This is a thin index over git repos -- not a package registry.
mise evolution: mise (formerly rtx) started as an asdf-compatible tool, inheriting its plugin model. mise then added:
- Built-in "core" plugins for common tools (no git clone needed)
- Registry of shortnames as a TOML file
- Cargo-like lock files for reproducibility
- Backends beyond asdf plugins (cargo, npm, go install, ubi, aqua)
Lesson: Starting with asdf compatibility gave mise instant access to 500+ plugins. Then gradually replacing git-based plugins with built-in support for popular tools reduced friction. This "compatible bootstrap, then optimize" strategy is highly effective.
The most mature dedicated registry for extensions.
Architecture: Extensions are packaged as .vsix files (ZIP archives with a manifest). Published to marketplace.visualstudio.com via vsce publish. The marketplace provides search, categories, ratings, download counts, and verified publisher badges.
Discovery: In-editor search panel. Web marketplace. Curated extension packs. Recommendations based on file types opened.
Trust model: Publisher verification, license scanning, automated security review. Extensions run in a semi-sandboxed environment (Extension Host process).
What makes it work at scale: The in-editor discovery experience is seamless. Users never leave VSCode to find and install extensions. One-click install. Automatic updates. Extension packs bundle related extensions.
Lesson: For large ecosystems, in-tool discovery is critical. Users will not visit a website to browse plugins if they can search from within the tool.
Claude Code uses a hybrid of convention-based discovery and vendored distribution.
Local commands: Users create markdown files in .claude/commands/ that become slash commands. Pure convention-based discovery -- no install step.
Plugin cache: Third-party plugins are vendored into .claude/plugins/cache/<name>/<version>/. The plugin manifest describes commands, agents, and skills. Claude Code scans this directory at startup.
npm distribution: Plugins can be published to npm and installed via the claude plugins install <name> mechanism, which downloads and vendors them into the cache directory.
Key design: The plugin contract is file-convention-based (SKILL.md, agent YAML files, command markdown). The distribution is npm-based for remote, vendored for local.
OpenCode/Codex compatibility: The same plugin content works across tools via AGENTS.md files and compatible directory conventions.
Lesson: AI agent frameworks benefit from the convention-based approach because plugins are primarily configuration and prompts (text files), not compiled code. This makes vendoring cheap and convention-based discovery natural.
| Factor | Package Registry | Git-Based | Vendored | Dedicated Registry | Convention-Based |
|---|---|---|---|---|---|
| Setup cost for ecosystem owner | Low | None | None | Very High | None |
| Setup cost for plugin author | Medium | Low | Low | Medium | Very Low |
| Setup cost for plugin user | Low | Low | None | Low | None |
| Discoverability | Medium | Poor | Poor | Excellent | Poor |
| Version management | Excellent | Weak | Manual | Excellent | None |
| Dependency resolution | Excellent | None | None | Good | None |
| Offline operation | After install | After clone | Always | After install | Always |
| Best ecosystem size | 10-500 | 5-100 | 1-30 | 50-10000+ | 1-20 |
Use Package Registry (npm/pip) when:
- Your tool is already in a package ecosystem
- Plugins need their own dependencies
- You want users to manage plugins alongside project dependencies
- Example: ESLint, Babel, Gatsby, Prettier
Use Git-Based when:
- Plugins are self-contained (no dependencies beyond the host)
- Plugin contract is simple (a few files following a convention)
- You want maximum flexibility for plugin authors
- Your ecosystem has under 100 plugins
- Example: asdf, oh-my-zsh custom plugins, Vim plugins
Use Vendoring when:
- Reproducibility is critical (same plugin version across all environments)
- Plugins are small (text files, configs, prompts)
- Offline operation is important
- You are building for AI agents where determinism matters
- Example: Claude Code plugin cache, Go vendor, project-local configs
Use Dedicated Registry when:
- You have 100+ plugins or expect rapid growth
- Trust and quality signals matter (security scanning, verified publishers)
- Discoverability is a user priority
- You can invest in registry infrastructure
- Example: Terraform, VSCode, Homebrew
Use Convention-Based when:
- You want zero-friction local customization
- Plugins are primarily configuration/text, not code
- Project-specific customization is the primary use case
- Example: Claude Code commands, GitHub Actions, Makefiles
What works:
- A curated "awesome list" (README or separate repo) for discovery
- Git-based distribution with a shortname registry (a single JSON/TOML file mapping names to git URLs)
- Clear file conventions documented in a single page
- Plugin template repo (cookiecutter/scaffolding)
- A
plugins/directory in the main project repo for "official" plugins
What does not work:
- Building a dedicated registry (too much infrastructure for too little content)
- Complex plugin APIs (discourages contributors)
- Mandatory CI/CD for plugin publishing (too much friction)
Example architecture:
# Plugin shortname registry (plugins.json)
{
"nodejs": "https://github.com/org/plugin-nodejs",
"python": "https://github.com/org/plugin-python"
}
# Install command
mytool plugin add nodejs
# Resolves to git clone https://github.com/org/plugin-nodejs ~/.mytool/plugins/nodejs
# Plugin contract: just need these files
plugins/nodejs/
plugin.toml # metadata (name, version, description)
bin/install # main entry point
bin/list-versions # list available versions
Growth path: Start with git-based + shortname file. When you hit 50 plugins, add a static site with search. At 200+, consider a proper registry API.
What works:
- Dedicated registry with search API
- Publisher verification and trust signals
- Automated compatibility testing
- In-tool discovery (search from within the CLI/IDE)
- Extension packs / curated collections
- Ratings and download counts
- Automated security scanning
What does not work:
- Manual curation as the only discovery mechanism
- Relying on GitHub stars for quality assessment
- No versioning or compatibility metadata
Critical features at scale:
- Search API: Users must find plugins without browsing a list
- Compatibility metadata: "This plugin works with host version 2.x-3.x"
- Automated testing: CI runs against the host tool for each plugin update
- Deprecation/archival: Mechanism to mark abandoned plugins
- Namespace governance: Prevent squatting and impersonation
The most important design decision in any plugin system is the contract -- what must a plugin provide?
my-plugin/
README.md # human description
plugin.{json|toml} # name, version, description, entry point
index.{js|sh|py} # single entry point
Used by: most small ecosystems, shell plugin managers
my-plugin/
manifest.json # declares capabilities, permissions, entry points
src/
commands/ # CLI commands this plugin adds
hooks/ # lifecycle hooks this plugin handles
lib/ # shared code
Used by: VSCode extensions, Claude Code plugins, Grafana plugins
my-plugin/
# Plugin implements a protocol (gRPC, JSON-RPC, HTTP)
# Host communicates via the protocol, not file conventions
main.go # compiles to binary implementing the protocol
Used by: Terraform providers, Hashicorp plugins (go-plugin), Neovim remote plugins
Key insight: Protocol-based contracts provide the strongest isolation (plugins run as separate processes) but have the highest authoring cost. Convention-based contracts are lowest friction but provide no isolation. Choose based on your trust model and ecosystem size.
Terraform's .terraform.lock.hcl and npm's package-lock.json solve the reproducibility problem. The lock file records exact versions and integrity hashes. install respects the lock file; update modifies it.
When to implement: As soon as you have more than one environment that must produce identical behavior (i.e., almost always).
| Strategy | Mechanism | Trade-off |
|---|---|---|
| Exact pin | = 1.2.3 |
Maximum reproducibility, manual updates |
| Compatible pin | ~> 1.2 |
Auto-patch updates, minor version control |
| Range pin | >= 1.0, < 2.0 |
Flexible, risk of breaking changes |
| Latest | No pin | Convenient, unreproducible |
| Vendored | Copy in repo | Total control, stale risk |
- Dependabot/Renovate: For package-registry plugins, automated PRs for version bumps
mise upgrade: Built-in command to update all tool versionsbrew update && brew upgrade: Two-step: update index, then upgrade packagesasdf plugin update --all: Git pull all plugin repos- VSCode: Background auto-update with optional user approval
Ranked by effectiveness at scale:
- In-tool search (VSCode, Terraform): Users never leave the tool. Highest conversion.
- Dedicated website with search (npmjs.com, marketplace.visualstudio.com): Good for browsing, lower conversion than in-tool.
- CLI search command (
brew search,asdf plugin list-all): Moderate friction, power-user friendly. - Curated awesome-list (awesome-zsh-plugins): Good for small ecosystems, does not scale past 200 entries.
- GitHub topic tags: Lowest effort, poorest discoverability. Only works for developers who think to search GitHub.
| Mechanism | Used By | Effect |
|---|---|---|
| Verified publisher | VSCode, npm | Identity trust |
| Code signing | Terraform, Homebrew | Integrity trust |
| Download counts | VSCode, npm | Social proof |
| Automated scanning | npm audit, Snyk | Vulnerability detection |
| Sandboxing | VSCode Extension Host, Deno permissions | Runtime isolation |
| Review process | Homebrew core, App Store | Quality gatekeeping |
| Reproducible builds | Homebrew bottles, Nix | Build trust |
For small ecosystems: A manual review of plugin PRs to a shortname registry is sufficient. For large ecosystems, automated scanning plus verified publishers is the minimum.
// host-tool/src/plugin-loader.js
import { readdirSync, existsSync } from 'fs';
import { join } from 'path';
function discoverPlugins(pluginDir) {
if (!existsSync(pluginDir)) return [];
return readdirSync(pluginDir, { withFileTypes: true })
.filter(d => d.isDirectory())
.map(d => {
const manifestPath = join(pluginDir, d.name, 'plugin.json');
if (!existsSync(manifestPath)) return null;
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
return {
name: d.name,
version: manifest.version,
description: manifest.description,
entryPoint: join(pluginDir, d.name, manifest.main || 'index.js'),
commands: manifest.commands || [],
};
})
.filter(Boolean);
}
// Usage
const plugins = discoverPlugins(join(process.env.HOME, '.mytool/plugins'));#!/usr/bin/env bash
# mytool-plugin-add: Install a plugin from git URL or shortname
PLUGIN_DIR="${MYTOOL_HOME:-$HOME/.mytool}/plugins"
REGISTRY_URL="https://raw.githubusercontent.com/org/mytool-plugins/main/registry.json"
install_plugin() {
local name="$1"
local url="$2"
# If no URL, resolve from shortname registry
if [ -z "$url" ]; then
url=$(curl -s "$REGISTRY_URL" | jq -r ".\"$name\"" 2>/dev/null)
if [ "$url" = "null" ] || [ -z "$url" ]; then
echo "[ERROR] Plugin '$name' not found in registry"
return 1
fi
fi
local dest="$PLUGIN_DIR/$name"
if [ -d "$dest" ]; then
echo "[WARN] Plugin '$name' already installed. Use 'update' instead."
return 1
fi
echo "Installing plugin '$name' from $url..."
git clone --depth 1 "$url" "$dest" || return 1
# Verify plugin contract
if [ ! -f "$dest/plugin.toml" ]; then
echo "[ERROR] Invalid plugin: missing plugin.toml"
rm -rf "$dest"
return 1
fi
echo "[OK] Plugin '$name' installed"
}
install_plugin "$@"{
"$schema": "https://mytool.dev/schemas/registry.json",
"version": 1,
"plugins": {
"nodejs": {
"url": "https://github.com/mytool-plugins/plugin-nodejs",
"description": "Node.js version management",
"maintainer": "core-team",
"tags": ["language", "runtime"]
},
"python": {
"url": "https://github.com/mytool-plugins/plugin-python",
"description": "Python version management",
"maintainer": "core-team",
"tags": ["language", "runtime"]
},
"terraform": {
"url": "https://github.com/community/mytool-terraform",
"description": "Terraform version management",
"maintainer": "community",
"tags": ["tool", "iac"]
}
}
}# plugin.yaml - AgentSys-style plugin manifest
name: code-review
version: 2.1.0
description: Multi-agent code review with configurable rulesets
author: example-org
license: MIT
min-host-version: "5.0.0"
commands:
- name: review
description: Run code review on changed files
agent: review-orchestrator
argument-hint: "[path] [--fix] [--severity [level]]"
agents:
- name: review-orchestrator
model: opus
description: Orchestrates review across multiple specialized agents
- name: style-checker
model: haiku
description: Checks code style compliance
skills:
- name: orchestrate-review
description: Coordinates multi-agent review workflow
hooks:
pre-commit:
- skill: orchestrate-review
args: "--quick --changed-only".tool/plugins/cache/
code-review/
2.1.0/
plugin.yaml
commands/
review.md
agents/
review-orchestrator.yaml
style-checker.yaml
skills/
orchestrate-review/
SKILL.md
lib/
shared-utils.js
2.0.3/ # Previous version kept for rollback
...
perf-analysis/
1.5.0/
plugin.yaml
...
| Pitfall | Why It Happens | How to Avoid |
|---|---|---|
| Building a registry too early | Excitement about the ecosystem's potential | Start with a shortname JSON file; build a registry only when discovery becomes a real user complaint |
| Too-complex plugin contract | Trying to anticipate all use cases | Start with 2-3 required files; add optional capabilities gradually |
| No version pinning | "It works on my machine" mindset | Add a lock file from day one, even if it is just a JSON file mapping plugin names to git SHAs |
| Monolithic bundling | Wanting zero-friction initial experience | Bundle a few essential plugins; make the rest opt-in from the start |
| No plugin validation | Trusting all plugin authors | Validate the plugin contract at install time (check required files exist, manifest is valid) |
| Mixing transport and contract | Thinking "how to download" is the hard problem | Design the plugin interface first; transport is pluggable later |
| Ignoring Windows/cross-platform | Most plugin authors use Unix | Test the plugin contract on all target platforms; avoid shell scripts if you need Windows support |
| No deprecation mechanism | Assuming plugins are forever | Add a deprecated field in the registry from day one |
| Over-engineering security for small ecosystems | Copying Terraform's model for 10 plugins | Match security investment to ecosystem size; manual review works for under 50 plugins |
| Breaking the plugin contract | Changing required files/APIs without versioning | Version your plugin contract separately from the host tool version |
Synthesized from analysis of 40+ ecosystems:
-
Design the contract first, distribution second. The interface a plugin must implement matters more than how it gets downloaded. Terraform succeeded because the provider protocol is rock-solid, not because the registry is fancy.
-
Use convention over configuration. The best plugin systems require minimal boilerplate. A plugin should be "a directory with the right files in it" -- not a complex build artifact.
-
Start with a shortname file, not a registry. A JSON or TOML file mapping plugin names to git URLs is sufficient for under 100 plugins and takes zero infrastructure to maintain.
-
Add a lock file from day one. Even a simple
plugins.lockthat records git SHAs or version numbers prevents "works on my machine" problems. -
Validate at install time, not at runtime. Check that a plugin follows the contract when it is installed. Fail fast with clear error messages. This catches most issues before they cause runtime failures.
-
Separate "official/core" from "community". Maintain a small set of high-quality official plugins. Accept community plugins with lighter review. Users need to know what is supported vs best-effort.
-
Provide a scaffold/template.
mytool new plugin <name>should generate a working plugin skeleton. This is the single highest-leverage thing you can do for plugin author experience. -
Support local development without publishing. Plugin authors must be able to test locally (e.g., symlink a local directory into the plugins folder). Publishing should only be needed for sharing.
-
Version the plugin contract independently. When you change what files/APIs a plugin must provide, version that change. Old plugins should keep working or fail with clear "upgrade needed" messages.
-
Invest in discovery proportional to ecosystem size. Under 20 plugins: README list. Under 100: searchable awesome-list or static site. Over 100: in-tool search with quality signals.
| Ecosystem | Pattern | Plugin Count | Contract Type | Discovery | What It Does Best |
|---|---|---|---|---|---|
| Terraform | Dedicated Registry | 3000+ | Protocol (gRPC) | In-tool + website | Version locking, platform-specific binaries |
| VSCode | Dedicated Registry | 50000+ | Capability-declared | In-tool search | Seamless install/update, quality signals |
| Homebrew | Git + JSON API | 6000+ | Convention (Ruby formula) | CLI search + website | Cross-platform binary distribution |
| npm/ESLint | Package Registry | 5000+ | Convention (naming prefix) | npm search | Dependency resolution, existing ecosystem |
| oh-my-zsh | Bundled + Git | 300+ | Convention (shell script) | README list | Zero-friction for included plugins |
| asdf/mise | Git-Based | 500+ | Convention (shell scripts in bin/) | CLI list + shortname file | Language-agnostic, simple contract |
| Claude Code | Vendored + npm | <50 | Convention (markdown + YAML) | Manual | Deterministic, offline-friendly |
| Vim (vim-plug) | Git-Based | 20000+ | Convention (autoload/) | awesome-vim + web | Minimal overhead, community-driven |
| GitHub Actions | Convention + Registry | 20000+ | Convention (action.yml) | Marketplace website | In-workflow reference, versioned via git tags |
| Resource | Type | Why Recommended |
|---|---|---|
| Terraform Plugin Framework Docs | Official Docs | Best-in-class plugin protocol design |
| VSCode Extension API | Official Docs | Most mature extension marketplace architecture |
| asdf Plugin Creation Guide | Official Docs | Simple git-based plugin contract |
| mise Documentation | Official Docs | Evolution from asdf model to multi-backend |
| Homebrew Formula Cookbook | Official Docs | Ruby-based package formula conventions |
| HashiCorp go-plugin | GitHub | gRPC-based plugin system library |
| steampipe Plugin SDK | GitHub | Terraform-inspired plugin pattern for SQL |
| Claude Code Plugin Docs | Official Docs | AI agent plugin conventions |
| ESLint Plugin Developer Guide | Official Docs | npm-based plugin distribution at scale |
| GitHub Actions Creating Actions | Official Docs | Convention-based action definition |
Generated by /learn from 40 sources (training knowledge -- web fetch was unavailable during generation).
See resources/skill-plugin-distribution-patterns-sources.json for full source metadata.