Plugins

August 28, 2026 · View on GitHub

Distributable bundles that package skills, commands, hooks, MCP servers, and permissions under a single versioned manifest.

Overview

A plugin is a directory in ~/.agents/plugins/ containing a .claude-plugin/plugin.json manifest. When you install or sync a plugin, agents-cli copies its contents into a synthetic per-user marketplace inside the agent version home and enables it via enabledPlugins in settings.json. Plugins are a superset of individual resources: a single plugin can ship skills, slash commands, subagent definitions, hooks, MCP servers, LSP servers, monitors, bin scripts, and permission sets — installed atomically as a unit.

Only agents with plugins: true (or a version-gated since) in the capability matrix participate — today Claude, OpenClaw, Antigravity, Grok, Kimi, Cursor, Goose, Droid, and Codex >= 0.128.0 (capableAgents('plugins') in src/lib/agents.ts). Plugins can narrow further by declaring agents: [...] in their manifest. Plugins that ship executable surfaces (hooks, .mcp.json, bin/, scripts/, settings.json, permissions/) require explicit consent via --allow-exec-surfaces to be enabled after installation.

For the layered resource model that governs plugin resolution, see resource-sync.md.

Architecture

~/.agents/plugins/<name>/             Central source (user-authored, git-tracked)
  .claude-plugin/
    plugin.json                       Required manifest: name, version, description
  skills/<skill>/SKILL.md             Slash-command knowledge packs
  commands/*.md                       Slash commands (converted to skills on Codex >= 0.117.0)
  agents/*.md                         Subagent definitions
  hooks/hooks.json         ◄ exec     Hook registrations — triggers exec-surface gate
  .mcp.json                ◄ exec     MCP server declarations
  bin/                     ◄ exec     Executable binaries
  scripts/                 ◄ exec     Arbitrary shell scripts
  settings.json            ◄ exec     Agent settings merge (non-permissions keys)
  permissions/             ◄ exec     Permission group YAML files
  .user-config.json                   Per-install user config values (runtime, not shipped)
  .source                             Git remote recorded at install time

                                      On agents plugins sync / install


<version-home>/.claude/
  plugins/
    known_marketplaces.json           "agents-cli" → marketplaces/agents-cli  (registered)
    marketplaces/agents-cli/
      .claude-plugin/marketplace.json Lists every discovered plugin
      plugins/<name>/                 Copy of source (user-config placeholders resolved)
    settings.json
      enabledPlugins["<name>@agents-cli"] = true   (only when exec gate passes)

Command Reference

CommandDescription
agents plugins listTable view of all plugins with sync status across agent versions
agents plugins view <name>Metadata, resources, and installation status for one plugin
agents plugins info <name>Alias for view
agents plugins install <spec>Install from a git URL or local path
agents install plugin:<spec>Same install path (Phase 5 umbrella); same --allow-exec-surfaces gate
agents plugins update [name]Re-pull from original source and re-sync (all plugins if no name given)
agents plugins sync <name> [agent]Apply a plugin to the default version of an agent (all supported agents if none given)
agents plugins remove [name]Unsync from all agent versions; optionally delete source directory

Options

CommandFlagEffect
install--allow-exec-surfacesEnable the plugin even when it ships hooks, MCP, bin, scripts, settings, or permissions
sync--allow-exec-surfacesSame gate override for the sync path
remove--keep-sourceUnsync from agents but leave ~/.agents/plugins/<name>/ on disk

Manifest Schema

.claude-plugin/plugin.json is the required entry point. Every field maps directly to PluginManifest in src/lib/types.ts:378.

{
  "name": "git",
  "version": "1.0.0",
  "description": "Git workflow commands — atomic grouped commits and merged-branch cleanup.",
  "agents": ["claude", "openclaw"],
  "dependencies": ["other-plugin"],
  "userConfig": [
    {
      "key": "api_url",
      "description": "Base URL for the API",
      "required": true,
      "default": "https://api.example.com"
    }
  ]
}
FieldTypeRequiredDescription
namestringyesPlugin identifier; must match the directory name
versionstringyesSemVer string shown in agents plugins list
descriptionstringyesOne-line description
agentsAgentId[]noLimit to specific agents; omit to support all capable agents
dependenciesstring[]noOther plugin names required; missing deps produce a warning at install
userConfigPluginUserConfigField[]noInteractive fields prompted at install time; values stored in .user-config.json

Directory Layout

my-plugin/
  .claude-plugin/
    plugin.json         # Required manifest
  skills/
    my-skill/
      SKILL.md          # Skill definition
  commands/
    my-command.md       # Slash command
  agents/
    my-subagent.md      # Subagent definition
  hooks/
    hooks.json          # Hook registrations (exec surface)
  .mcp.json             # MCP server config (exec surface)
  bin/
    my-binary           # Executable (exec surface)
  scripts/
    setup.sh            # Setup scripts (exec surface)
  settings.json         # Settings to merge (exec surface)
  permissions/
    my-perms.yaml       # Permission group (exec surface)

Plugins that ship any of hooks/, .mcp.json, bin/, scripts/, a non-permissions settings.json, or permissions/ are installed (copied to the marketplace) but not enabled unless you pass --allow-exec-surfaces.

The gate is implemented at src/commands/plugins.ts:68:

export function shouldRefusePluginInstall(
  capabilities: PluginCapabilities,
  allowExecSurfaces: boolean
): boolean {
  return hasPluginExecSurfaces(capabilities) && !allowExecSurfaces;
}

PluginCapabilities maps each surface to a boolean flag (hasHooks, hasMcp, hasBin, hasScripts, hasSettings, hasPermissions). hasPluginExecSurfaces() returns true if any flag is true.

Without the flag, the plugin is placed in the marketplace and listed in known_marketplaces.json, but enabledPlugins["<name>@agents-cli"] is never set to true in settings.json. The plugin is present but inert until you re-run with consent.

This prevents automated sync flows (agents use claude@<v>) from silently activating third-party code that runs shell scripts or registers MCP servers in every new session.

Recipes

1. Install a plugin from GitHub

agents plugins install rush-toolkit@https://github.com/user/rush-toolkit.git

# If the plugin ships hooks or MCP and you trust the source:
agents plugins install rush-toolkit@https://github.com/user/rush-toolkit.git --allow-exec-surfaces

2. Install from a local path

agents plugins install ~/Projects/my-plugin
# or with an explicit name:
agents plugins install rush-toolkit@~/Projects/rush-toolkit

3. Sync after pulling the repo

# After git pull or manual edits to ~/.agents/plugins/my-plugin/:
agents plugins sync my-plugin
agents plugins sync my-plugin claude --allow-exec-surfaces

4. List what is installed and enabled

agents plugins list
agents plugins view rush-toolkit

5. Remove a plugin

# Remove from all agents and delete source:
agents plugins remove rush-toolkit

# Unsync only, keep source directory:
agents plugins remove rush-toolkit --keep-source

Demo

See Also

  • resource-sync.md — how plugins participate in the layered resource sync model
  • docs/subagents.md — subagent definitions that plugins can bundle
  • docs/hooks.md — hook manifests that plugins can ship
  • docs/workflows.md — workflow bundles that can reference plugins