> ## Documentation Index
> Fetch the complete documentation index at: https://allhandsai-extend-api-reference-docs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# openhands.sdk.plugin

> API reference for openhands.sdk.plugin module

Plugin module for OpenHands SDK.

This module provides support for loading and managing plugins that bundle
skills, hooks, MCP configurations, agents, and commands together.

It also provides support for plugin marketplaces - directories that list
available plugins with their metadata and source locations.

Additionally, it provides utilities for managing installed plugins in the
user’s home directory (\~/.openhands/plugins/installed/).

***

### class CommandDefinition

Bases: `BaseModel`

Command definition loaded from markdown file.

Commands are slash commands that users can invoke directly.
They define instructions for the agent to follow.

#### Properties

* `allowed_tools`: list\[str]
* `argument_hint`: str | None
* `content`: str
* `description`: str
* `metadata`: dict\[str, Any]
* `name`: str
* `source`: str | None

#### Methods

#### classmethod load()

Load a command definition from a markdown file.

Command markdown files have YAML frontmatter with:

* description: Command description
* argument-hint: Hint for command arguments (string or list)
* allowed-tools: List of allowed tools

The body of the markdown is the command instructions.

* Parameters:
  `command_path` – Path to the command markdown file.
* Returns:
  Loaded CommandDefinition instance.

#### model\_config = (configuration object)

Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].

#### to\_skill()

Convert this command to a keyword-triggered Skill.

Creates a Skill with a KeywordTrigger using the Claude Code namespacing
format: /`<plugin-name>`:`<command-name>`

* Parameters:
  `plugin_name` – The name of the plugin this command belongs to.
* Returns:
  A Skill object with the command content and a KeywordTrigger.

***

### class GitHubURLComponents

Bases: `NamedTuple`

Parsed components of a GitHub blob/tree URL.

#### Properties

* `branch`: str
  Alias for field number 2
* `owner`: str
  Alias for field number 0
* `path`: str
  Alias for field number 3
* `repo`: str
  Alias for field number 1

***

### class InstalledPluginInfo

Bases: `BaseModel`

Information about an installed plugin.

This model tracks metadata about a plugin installation, including
where it was installed from and when.

#### Properties

* `description`: str
* `enabled`: bool
* `install_path`: str
* `installed_at`: str
* `name`: str
* `repo_path`: str | None
* `resolved_ref`: str | None
* `source`: str
* `version`: str

#### Methods

#### classmethod from\_plugin()

Create InstalledPluginInfo from a loaded Plugin.

#### model\_config = (configuration object)

Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].

***

### class InstalledPluginsMetadata

Bases: `BaseModel`

Metadata file for tracking all installed plugins.

#### Properties

* `plugins`: dict\[str, [InstalledPluginInfo](#class-installedplugininfo)]

#### Methods

#### classmethod get\_path()

Get the metadata file path for the given installed plugins directory.

#### classmethod load\_from\_dir()

Load metadata from the installed plugins directory.

#### model\_config = (configuration object)

Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].

#### save\_to\_dir()

Save metadata to the installed plugins directory.

***

### class Marketplace

Bases: `BaseModel`

A plugin marketplace that lists available plugins and skills.

Follows the Claude Code marketplace structure for compatibility,
with an additional skills field for standalone skill references.

The marketplace.json file is located in .plugin/ or .claude-plugin/
directory at the root of the marketplace repository.

#### Properties

* `description`: str | None
* `metadata`: [MarketplaceMetadata](#class-marketplacemetadata) | None
* `model_config`: = (configuration object)
  Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
* `name`: str
* `owner`: [MarketplaceOwner](#class-marketplaceowner)
* `path`: str | None
* `plugins`: list\[[MarketplacePluginEntry](#class-marketplacepluginentry)]
* `skills`: list\[[MarketplaceEntry](#class-marketplaceentry)]

#### Methods

#### get\_plugin()

Get a plugin entry by name.

* Parameters:
  `name` – Plugin name to look up.
* Returns:
  MarketplacePluginEntry if found, None otherwise.

#### classmethod load()

Load a marketplace from a directory.

Looks for marketplace.json in .plugin/ or .claude-plugin/ directories.

* Parameters:
  `marketplace_path` – Path to the marketplace directory.
* Returns:
  Loaded Marketplace instance.
* Raises:
  * `FileNotFoundError` – If the marketplace directory or manifest doesn’t exist.
  * `ValueError` – If the marketplace manifest is invalid.

#### resolve\_plugin\_source()

Resolve a plugin’s source to a full path or URL.

* Returns:
  * source: Resolved source string (path or URL)
  * ref: Branch, tag, or commit reference (None for local paths)
  * subpath: Subdirectory path within the repo (None if not specified)
* Return type:
  Tuple of (source, [ref](#class-ref), subpath) where

***

### class MarketplaceEntry

Bases: `BaseModel`

Base class for marketplace entries (plugins and skills).

Both plugins and skills are pointers to directories:

* Plugin directories contain: plugin.json, skills/, commands/, agents/, etc.
* Skill directories contain: SKILL.md and optionally scripts/, references/, assets/

Source is a string path (local path or GitHub URL).

#### Properties

* `author`: [PluginAuthor](#class-pluginauthor) | None
* `category`: str | None
* `description`: str | None
* `homepage`: str | None
* `model_config`: = (configuration object)
  Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
* `name`: str
* `source`: str
* `version`: str | None

***

### class MarketplaceMetadata

Bases: `BaseModel`

Optional metadata for a marketplace.

#### Properties

* `description`: str | None
* `model_config`: = (configuration object)
  Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
* `version`: str | None

***

### class MarketplaceOwner

Bases: `BaseModel`

Owner information for a marketplace.

The owner represents the maintainer or team responsible for the marketplace.

#### Properties

* `email`: str | None
* `name`: str

#### Methods

#### model\_config = (configuration object)

Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].

***

### class MarketplacePluginEntry

Bases: [`MarketplaceEntry`](#class-marketplaceentry)

Plugin entry in a marketplace.

Extends MarketplaceEntry with Claude Code compatibility fields for
inline plugin definitions (when strict=False).

Plugins support both string sources and complex source objects
(MarketplacePluginSource) for GitHub/git URLs with ref and path.

#### Properties

* `agents`: str | list\[str] | None
* `commands`: str | list\[str] | None
* `hooks`: str | HooksConfigDict | None
* `keywords`: list\[str]
* `license`: str | None
* `lsp_servers`: LspServersDict | None
* `mcp_servers`: McpServersDict | None
* `model_config`: = (configuration object)
  Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
* `repository`: str | None
* `source`: str | [MarketplacePluginSource](#class-marketplacepluginsource)
* `strict`: bool
* `tags`: list\[str]

#### Methods

#### to\_plugin\_manifest()

Convert to PluginManifest (for strict=False entries).

***

### class MarketplacePluginSource

Bases: `BaseModel`

Plugin source specification for non-local sources.

Supports GitHub repositories and generic git URLs.

#### Properties

* `model_config`: = (configuration object)
  Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
* `path`: str | None
* `ref`: str | None
* `repo`: str | None
* `source`: str
* `url`: str | None

#### Methods

#### validate\_source\_fields()

Validate that required fields are present based on source type.

***

### class Plugin

Bases: `BaseModel`

A plugin that bundles skills, hooks, MCP config, agents, and commands.

Plugins follow the Claude Code plugin structure for compatibility:

plugin-name/
├── .claude-plugin/           # or .plugin/
│   └── plugin.json          # Plugin metadata
├── commands/                # Slash commands (optional)
├── agents/                  # Specialized agents (optional)
├── skills/                  # Agent Skills (optional)
├── hooks/                   # Event handlers (optional)
│   └── hooks.json
├── .mcp.json                # External tool configuration (optional)
└── README.md                # Plugin documentation

#### Properties

* `agents`: list\[AgentDefinition]
* `commands`: list\[[CommandDefinition](#class-commanddefinition)]
* `description`: str
  Get the plugin description.
* `hooks`: HookConfig | None
* `manifest`: [PluginManifest](#class-pluginmanifest)
* `mcp_config`: dict\[str, Any] | None
* `name`: str
  Get the plugin name.
* `path`: str
* `skills`: list\[Skill]
* `version`: str
  Get the plugin version.

#### Methods

#### add\_mcp\_config\_to()

Add this plugin’s MCP servers to an MCP config.

Plugin MCP servers override existing servers with the same name.

Merge semantics (Claude Code compatible):

* mcpServers: deep-merge by server name (last plugin wins for same server)
* Other top-level keys: shallow override (plugin wins)

- Parameters:
  `mcp_config` – Existing MCP config (or None to create new)
- Returns:
  New MCP config dict with this plugin’s servers added

#### add\_skills\_to()

Add this plugin’s skills to an agent context.

Plugin skills override existing skills with the same name.
Includes both explicit skills and command-derived skills.

* Parameters:
  * `agent_context` – Existing agent context (or None to create new)
  * `max_skills` – Optional max total skills (raises ValueError if exceeded)
* Returns:
  New AgentContext with this plugin’s skills added
* Raises:
  `ValueError` – If max\_skills limit would be exceeded

#### classmethod fetch()

Fetch a plugin from a remote source and return the local cached path.

This method fetches plugins from remote sources (GitHub repositories, git URLs)
and caches them locally. Use the returned path with Plugin.load() to load
the plugin.

* Parameters:
  * `source` –

    Plugin source - can be:

    * Any git URL (GitHub, GitLab, Bitbucket, Codeberg, self-hosted, etc.)
      e.g., “[https://gitlab.com/org/repo](https://gitlab.com/org/repo)”, “[git@bitbucket.org](mailto:git@bitbucket.org):team/repo.git”
    * ”github:owner/repo” - GitHub shorthand (convenience syntax)
    * ”/local/path” - Local path (returned as-is)
  * `cache_dir` – Directory for caching. Defaults to \~/.openhands/cache/plugins/
  * `ref` – Optional branch, tag, or commit to checkout.
  * `update` – If True and cache exists, update it. If False, use cached as-is.
  * `repo_path` – Subdirectory path within the git repository
    (e.g., ‘plugins/my-plugin’ for monorepos). Only relevant for git
    sources, not local paths. If specified, the returned path will
    point to this subdirectory instead of the repository root.
* Returns:
  Path to the local plugin directory (ready for Plugin.load()).
  If repo\_path is specified, returns the path to that subdirectory.
* Raises:
  [PluginFetchError](#class-pluginfetcherror) – If fetching fails or repo\_path doesn’t exist.

#### get\_all\_skills()

Get all skills including those converted from commands.

Returns skills from both the skills/ directory and commands/ directory.
Commands are converted to keyword-triggered skills using the format
/`<plugin-name>`:`<command-name>`.

* Returns:
  Combined list of skills (original + command-derived skills).

#### classmethod load()

Load a plugin from a directory.

* Parameters:
  `plugin_path` – Path to the plugin directory.
* Returns:
  Loaded Plugin instance.
* Raises:
  * `FileNotFoundError` – If the plugin directory doesn’t exist.
  * `ValueError` – If the plugin manifest is invalid.

#### classmethod load\_all()

Load all plugins from a directory.

* Parameters:
  `plugins_dir` – Path to directory containing plugin subdirectories.
* Returns:
  List of loaded Plugin instances.

#### model\_config = (configuration object)

Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].

***

### class PluginAuthor

Bases: `BaseModel`

Author information for a plugin.

#### Properties

* `email`: str | None
* `name`: str

#### Methods

#### classmethod from\_string()

Parse author from string format ‘Name `<email>`’.

#### model\_config = (configuration object)

Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].

***

### class PluginManifest

Bases: `BaseModel`

Plugin manifest from plugin.json.

#### Properties

* `author`: [PluginAuthor](#class-pluginauthor) | None
* `description`: str
* `model_config`: = (configuration object)
  Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
* `name`: str
* `version`: str

***

### class PluginSource

Bases: `BaseModel`

Specification for a plugin to load.

This model describes where to find a plugin and is used by load\_plugins()
to fetch and load plugins from various sources.

#### Examples

```pycon theme={null}
>>> # GitHub repository
>>> PluginSource(source="github:owner/repo", ref="v1.0.0")
```

```pycon theme={null}
>>> # Plugin from monorepo subdirectory
>>> PluginSource(
...     source="github:owner/monorepo",
...     repo_path="plugins/my-plugin"
... )
```

```pycon theme={null}
>>> # Local path
>>> PluginSource(source="/path/to/plugin")
```

#### Properties

* `ref`: str | None
* `repo_path`: str | None
* `source`: str

#### Methods

#### model\_config = (configuration object)

Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].

#### classmethod validate\_repo\_path()

Validate repo\_path is a safe relative path within the repository.

***

### class ResolvedPluginSource

Bases: `BaseModel`

A plugin source with resolved ref (pinned to commit SHA).

Used for persistence to ensure deterministic behavior across pause/resume.
When a conversation is resumed, the resolved ref ensures we get exactly
the same plugin version that was used when the conversation started.

The resolved\_ref is the actual commit SHA that was fetched, even if the
original ref was a branch name like ‘main’. This prevents drift when
branches are updated between pause and resume.

#### Properties

* `original_ref`: str | None
* `repo_path`: str | None
* `resolved_ref`: str | None
* `source`: str

#### Methods

#### classmethod from\_plugin\_source()

Create a ResolvedPluginSource from a PluginSource and resolved ref.

#### model\_config = (configuration object)

Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].

#### to\_plugin\_source()

Convert back to PluginSource using the resolved ref.

When loading from persistence, use the resolved\_ref to ensure we get
the exact same version that was originally fetched.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.