Documentation
¶
Overview ¶
Package plugins provides types and interfaces for managing ToolHive plugins (Claude Plugin manifest format, .claude-plugin/plugin.json).
A plugin is an OCI artifact containing a .claude-plugin/plugin.json manifest and its component directories (commands, agents, skills, hooks). The package mirrors pkg/skills: the scoping model (user vs. project) and install-status lifecycle are identical, so Scope and InstallStatus are re-exported as type aliases from pkg/skills to avoid conversion churn at storage boundaries.
Index ¶
- Constants
- Variables
- func ValidatePluginName(name string) error
- type Author
- type BuildOptions
- type BuildResult
- type ComponentInventory
- type ComponentType
- type ContentOptions
- type DematerializeRequest
- type Dependency
- type InfoOptions
- type InstallOptions
- type InstallResult
- type InstallStatus
- type InstalledPlugin
- type ListOptions
- type LocalBuild
- type MaterializationAdapter
- type MaterializeRequest
- type MaterializeResult
- type PluginContent
- type PluginFileEntry
- type PluginInfo
- type PluginManifest
- type PluginMetadata
- type PluginService
- type PushOptions
- type Scope
- type ScopeSupport
- type UninstallOptions
- type ValidationResult
Constants ¶
const ( // ScopeUser indicates a plugin installed user-wide. ScopeUser = skills.ScopeUser // ScopeProject indicates a plugin installed for a specific project. ScopeProject = skills.ScopeProject )
const ( InstallStatusInstalled = skills.InstallStatusInstalled InstallStatusPending = skills.InstallStatusPending InstallStatusFailed = skills.InstallStatusFailed )
Install lifecycle statuses. Aliased from skills.InstallStatus because the lifecycle model is identical.
const ManifestPath = ".claude-plugin/plugin.json"
ManifestPath is the required manifest file path for a plugin directory, matching the Claude Plugin manifest format.
const MaxComponentsPerGroup = 100
MaxComponentsPerGroup caps the number of entries allowed in a single component array (commands, agents, skills, hooks). Mirrors the skills parser's MaxDependencies bound and bounds the cost of ValidatePluginDir, which walks each bundled skill path. A 64KB manifest can otherwise pack thousands of "./x" entries cheaply.
const MaxManifestSize = 64 * 1024
MaxManifestSize limits the plugin.json size to prevent JSON parsing attacks (e.g. billion laughs). Mirrors the packager's maxManifestSize.
Variables ¶
var CheckFilesystem = skills.CheckFilesystem
CheckFilesystem walks a plugin directory checking for symlinks and path traversal. Re-exported from skills (the filesystem safety check is generic).
var ErrInvalidManifest = errors.New("invalid plugin manifest")
ErrInvalidManifest indicates that the plugin manifest is malformed, missing, or fails toolhive-side strictness checks.
var NormalizeScopeAndProjectRoot = skills.NormalizeScopeAndProjectRoot
NormalizeScopeAndProjectRoot validates scope and project_root and returns normalized values. Re-exported from skills (the scope/project_root normalization rule is identical for plugins).
var ValidateProjectRoot = skills.ValidateProjectRoot
ValidateProjectRoot validates a project root path and returns its cleaned form. Re-exported from skills: project-root validation (absolute, no traversal, no symlinks, must be a git repo) is identical for plugins.
var ValidateScope = skills.ValidateScope
ValidateScope validates a plugin scope. Re-exported from skills because the rule (empty | "user" | "project") is identical.
Functions ¶
func ValidatePluginName ¶
ValidatePluginName checks that a plugin name conforms to the kebab-case rule shared with skills (2-64 lowercase alphanumeric/hyphens, no consecutive hyphens). Re-exported from skills because the rule is identical.
Types ¶
type Author ¶
type Author struct {
Name string `json:"name,omitempty"`
Email string `json:"email,omitempty"`
URL string `json:"url,omitempty"`
}
Author represents the author field of a plugin manifest.
type BuildOptions ¶
type BuildOptions = skills.BuildOptions
BuildOptions configures the behavior of the Build operation. Alias for skills.BuildOptions (Path, Tag).
type BuildResult ¶
type BuildResult = skills.BuildResult
BuildResult contains the outcome of a Build operation. Alias for skills.BuildResult (Reference).
type ComponentInventory ¶
type ComponentInventory = ociplugins.ComponentInventory
ComponentInventory summarizes the component types declared by a plugin (map of component-type name to count). Alias for the toolhive-core type.
type ComponentType ¶ added in v0.34.0
type ComponentType string
ComponentType enumerates plugin component classes. The string values match the keys of ociplugins.ComponentInventory (commands/agents/skills/hooks/ mcpServers/lspServers).
const ( ComponentCommands ComponentType = "commands" ComponentAgents ComponentType = "agents" ComponentSkills ComponentType = "skills" ComponentHooks ComponentType = "hooks" ComponentMCP ComponentType = "mcpServers" ComponentLSP ComponentType = "lspServers" )
Component type constants. The string values match the keys of ociplugins.ComponentInventory (commands/agents/skills/hooks/mcpServers/ lspServers).
type ContentOptions ¶
type ContentOptions = skills.ContentOptions
ContentOptions configures the behavior of the GetContent operation. Alias for skills.ContentOptions.
type DematerializeRequest ¶ added in v0.34.0
type DematerializeRequest struct {
// Name is the plugin name (kebab-case).
Name string
// Scope is the installation scope (user or project).
Scope Scope
// ProjectRoot is the project root path for project-scoped installs.
// Empty for user-scoped.
ProjectRoot string
}
DematerializeRequest carries everything an adapter needs to revert a plugin installation. It mirrors MaterializeRequest so the two sides of the seam stay symmetric — the first client that needs more context to revert cleanly gets it without forcing an interface break.
type Dependency ¶
type Dependency = skills.Dependency
Dependency is an external plugin dependency (OCI reference). Alias for skills.Dependency; the {Name, Reference, Digest} shape is identical.
type InfoOptions ¶
type InfoOptions = skills.InfoOptions
InfoOptions configures the behavior of the Info operation. Alias for skills.InfoOptions.
type InstallOptions ¶
type InstallOptions struct {
// Name is the plugin name or OCI reference to install.
Name string `json:"name"`
// Version is the specific version to install. Empty means latest.
Version string `json:"version,omitempty"`
// Scope is the installation scope.
Scope Scope `json:"scope,omitempty"`
// Clients lists target clients (e.g., "claude-code").
Clients []string `json:"clients,omitempty"`
// Force allows overwriting unmanaged plugin directories.
Force bool `json:"force,omitempty"`
// ProjectRoot is the project root path for project-scoped installs.
ProjectRoot string `json:"project_root,omitempty"`
// Group is the group name to add the plugin to after installation.
Group string `json:"group,omitempty"`
// LayerData is the tar.gz content from an OCI layer. Internal use only — NOT exposed via HTTP API.
LayerData []byte `json:"-"`
// Reference is the full OCI reference (e.g. ghcr.io/org/plugin:v1).
Reference string `json:"-"`
// Digest is the OCI digest for upgrade detection.
Digest string `json:"-"`
// Components is the plugin's component inventory, hydrated from the OCI
// artifact config by install-from-OCI/git flows. Internal use only.
Components ComponentInventory `json:"-"`
// Dependencies is the plugin's external dependency list, hydrated from the
// OCI artifact config. Internal use only.
Dependencies []Dependency `json:"-"`
// Tag is the OCI tag, hydrated from the resolved reference. Internal use only.
Tag string `json:"-"`
// Description is the plugin description, hydrated from the OCI artifact
// config or git manifest. Internal use only.
Description string `json:"-"`
}
InstallOptions configures the behavior of the Install operation. Mirrors skills.InstallOptions with the plugin-specific addition of Reference/Digest passthrough fields used by install-from-OCI flows.
type InstallResult ¶
type InstallResult struct {
// Plugin is the installed plugin.
Plugin InstalledPlugin `json:"plugin"`
}
InstallResult contains the outcome of an Install operation.
type InstallStatus ¶
type InstallStatus = skills.InstallStatus
InstallStatus is the lifecycle status of an installed plugin. Alias for skills.InstallStatus (installed | pending | failed).
type InstalledPlugin ¶
type InstalledPlugin struct {
// Metadata contains the plugin's metadata.
Metadata PluginMetadata `json:"metadata"`
// Scope is the installation scope (user or project).
Scope Scope `json:"scope"`
// ProjectRoot is the project root path for project-scoped plugins. Empty for user-scoped.
ProjectRoot string `json:"project_root,omitempty"`
// Reference is the full OCI reference (e.g. ghcr.io/org/plugin:v1).
Reference string `json:"reference,omitempty"`
// Tag is the OCI tag (e.g. v1.0.0).
Tag string `json:"tag,omitempty"`
// Digest is the OCI digest (sha256:...) for upgrade detection.
Digest string `json:"digest,omitempty"`
// Status is the current installation status.
Status InstallStatus `json:"status"`
// InstalledAt is the timestamp when the plugin was installed.
InstalledAt time.Time `json:"installed_at"`
// Clients is the list of client identifiers the plugin is installed for.
Clients []string `json:"clients,omitempty"`
// Components is the inventory of component types declared by the plugin
// (e.g. {"commands": 3, "skills": 2}). Extracted from the OCI artifact.
Components ComponentInventory `json:"components,omitempty"`
// Signature is the optional signing signature for the plugin artifact.
Signature string `json:"signature,omitempty"`
// Dependencies is the list of external plugin dependencies.
Dependencies []Dependency `json:"dependencies,omitempty"`
}
InstalledPlugin represents a plugin that has been installed locally.
type ListOptions ¶
type ListOptions = skills.ListOptions
ListOptions configures the behavior of the List operation. Alias for skills.ListOptions (identical shape: Scope, ClientApp, ProjectRoot, Group).
type LocalBuild ¶
type LocalBuild = skills.LocalBuild
LocalBuild represents a locally-built OCI plugin artifact in the local store. Alias for skills.LocalBuild (identical shape: Tag, Digest, Name, Description, Version).
type MaterializationAdapter ¶ added in v0.34.0
type MaterializationAdapter interface {
// Materialize extracts the plugin into the client's directory layout and,
// for config-based clients, mutates the client config. Must be idempotent
// for re-installs under the same (name, scope, projectRoot) — a force
// reinstall overwrites the prior install.
Materialize(ctx context.Context, req MaterializeRequest) (*MaterializeResult, error)
// Dematerialize removes the plugin's filesystem footprint and reverts any
// config mutations the adapter itself made. Must be idempotent: a missing
// install is not an error.
Dematerialize(ctx context.Context, req DematerializeRequest) error
// SupportedComponents returns the component types this adapter loads.
SupportedComponents() []ComponentType
// ScopeSupport reports whether a project-scoped install degrades for this
// client — i.e. the adapter can only materialize at user scope (typically
// because it mutates a user-scoped config file). The returned struct also
// carries an optional reason (not yet consumed by Info). Used by Info to
// surface ProjectScopeDegradedClients without re-running Materialize.
ScopeSupport() ScopeSupport
}
MaterializationAdapter materializes a plugin into a target client's layout and reverts its own mutations. It generalizes skills.PathResolver: instead of resolving a single skill path, it owns extraction + (optional) config mutation for a multi-component plugin tree, because the materialization strategy differs per client (Claude Code = pure filesystem; Codex = FS cache + TOML mutation).
type MaterializeRequest ¶ added in v0.34.0
type MaterializeRequest struct {
// Name is the plugin name (kebab-case).
Name string
// LayerData is the OCI tar.gz layer containing the whole plugin tree
// (commands/, agents/, skills/, hooks/, .claude-plugin/). Produced by
// OCI pull or built in-memory from a git clone.
LayerData []byte
// Scope is the installation scope (user or project).
Scope Scope
// ProjectRoot is the project root path for project-scoped installs.
// Empty for user-scoped.
ProjectRoot string
// Components is the plugin's component inventory, used to warn on
// component types the adapter does not materialize (e.g. Codex drops
// commands/agents).
Components ComponentInventory
}
MaterializeRequest carries everything an adapter needs to install a plugin into a target client's directory layout.
type MaterializeResult ¶ added in v0.34.0
type MaterializeResult struct {
// InstalledComponents lists the component types the adapter materialized.
InstalledComponents []ComponentType
// DroppedComponents lists component types the plugin declares that this
// adapter does NOT materialize.
DroppedComponents []ComponentType
// InstallPath is the root directory the adapter wrote (informational).
InstallPath string
// ProjectScopeDegraded is true when a project-scope install degrades
// because the target client only supports user-scope materialization.
ProjectScopeDegraded bool
}
MaterializeResult reports what was written and what was deliberately dropped.
type PluginContent ¶
type PluginContent struct {
// Name is the plugin name from the OCI config labels.
Name string `json:"name"`
// Description is the plugin description from the OCI config labels.
Description string `json:"description,omitempty"`
// Version is the plugin version from the OCI config labels.
Version string `json:"version,omitempty"`
// License is the SPDX license identifier from the OCI config labels.
License string `json:"license,omitempty"`
// Manifest is the raw .claude-plugin/plugin.json body.
Manifest string `json:"manifest"`
// Files is the list of all files in the artifact with their sizes.
Files []PluginFileEntry `json:"files"`
}
PluginContent contains the manifest body and file listing extracted from an OCI plugin artifact without installing it.
type PluginFileEntry ¶
type PluginFileEntry = skills.SkillFileEntry
PluginFileEntry represents a single file within a plugin artifact. Alias for skills.SkillFileEntry (same {Path, Size} shape).
type PluginInfo ¶
type PluginInfo struct {
// Metadata contains the plugin's metadata.
Metadata PluginMetadata `json:"metadata"`
// InstalledPlugin contains the full installation record.
InstalledPlugin *InstalledPlugin `json:"installed_plugin,omitempty"`
// UnmaterializedComponents lists, per client type, the component types the
// plugin declares that the installed client adapter does NOT load. Populated
// by Info by diffing InstalledPlugin.Components against each installed
// client adapter's SupportedComponents.
UnmaterializedComponents map[string][]ComponentType `json:"unmaterialized_components,omitempty"`
// ProjectScopeDegradedClients lists the client types for which a
// project-scoped install degraded (the adapter could only materialize at
// user scope — e.g. Codex always writes to the user-scoped config.toml).
// Populated by Info; empty for user-scoped installs. Recomputed at read
// time from the stored scope + each adapter's capability, mirroring the
// UnmaterializedComponents pattern (no persistence needed — the degradation
// is deterministic from scope + client type).
ProjectScopeDegradedClients []string `json:"project_scope_degraded_clients,omitempty"`
}
PluginInfo contains detailed information about an installed plugin.
type PluginManifest ¶
type PluginManifest struct {
Name string `json:"name"`
Version string `json:"version,omitempty"`
Description string `json:"description,omitempty"`
Author Author `json:"author,omitempty"`
Homepage string `json:"homepage,omitempty"`
Repository string `json:"repository,omitempty"`
License string `json:"license,omitempty"`
// Keywords MUST be a JSON array (a string is a hard error — see strictStringSlice).
Keywords strictStringSlice `json:"keywords"`
// Component directories: each entry must be a relative path starting with "./".
Commands []string `json:"commands,omitempty"`
Agents []string `json:"agents,omitempty"`
Skills []string `json:"skills,omitempty"`
Hooks []string `json:"hooks,omitempty"`
McpServers json.RawMessage `json:"mcpServers,omitempty"`
LspServers json.RawMessage `json:"lspServers,omitempty"`
// Raw is the full original document, preserved for round-tripping unknown fields.
Raw json.RawMessage `json:"-"`
}
PluginManifest represents the toolhive-readable fields of .claude-plugin/plugin.json. Unknown fields are preserved in Raw so the manifest can be round-tripped without loss.
func ParsePluginManifest ¶
func ParsePluginManifest(pluginDir string) (*PluginManifest, error)
ParsePluginManifest reads and parses .claude-plugin/plugin.json from pluginDir. It performs toolhive-specific pre-build strictness checks only; the packager re-walks the directory and re-validates at build time.
Strictness (exit gate):
- keywords MUST be a JSON array; a string is a hard error.
- component paths (commands/agents/skills/hooks) must be relative, start with "./", and contain no ".." traversal segments.
The full document is preserved in .Raw (unknown fields preserved).
func ParsePluginManifestFromBytes ¶ added in v0.34.0
func ParsePluginManifestFromBytes(content []byte) (*PluginManifest, error)
ParsePluginManifestFromBytes parses manifest bytes into a PluginManifest, applying the same strictness checks as ParsePluginManifest. Split out for callers (e.g. the git install flow) that already hold the manifest bytes rather than a directory on disk.
type PluginMetadata ¶
type PluginMetadata struct {
// Name is the unique name of the plugin (kebab-case).
Name string `json:"name"`
// Version is the semantic version of the plugin.
Version string `json:"version,omitempty"`
// Description is a human-readable description of the plugin.
Description string `json:"description,omitempty"`
// Author is the plugin author or maintainer.
Author string `json:"author,omitempty"`
// License is the SPDX license identifier for the plugin.
License string `json:"license,omitempty"`
// Keywords is a list of keywords for categorization/search.
Keywords []string `json:"keywords,omitempty"`
}
PluginMetadata contains metadata about a plugin, drawn from the .claude-plugin/plugin.json manifest.
type PluginService ¶
type PluginService interface {
// Validate checks whether a plugin definition is valid.
Validate(ctx context.Context, path string) (*ValidationResult, error)
// Build builds a plugin from a local directory into an OCI artifact.
Build(ctx context.Context, opts BuildOptions) (*BuildResult, error)
// Push pushes a built plugin artifact to a remote registry.
Push(ctx context.Context, opts PushOptions) error
// ListBuilds returns all locally-built OCI plugin artifacts in the local store.
ListBuilds(ctx context.Context) ([]LocalBuild, error)
// DeleteBuild removes a locally-built OCI plugin artifact from the local store.
DeleteBuild(ctx context.Context, tag string) error
// GetContent retrieves the plugin.json body and file listing from an OCI
// artifact without installing it. Works for both remote registry references
// and local build tags.
GetContent(ctx context.Context, opts ContentOptions) (*PluginContent, error)
// Install resolves a plugin reference (OCI, git, or registry name), extracts
// it, materializes it into each target client's directory layout via the
// configured MaterializationAdapters, and persists the install record.
Install(ctx context.Context, opts InstallOptions) (*InstallResult, error)
// Uninstall removes a plugin from all target clients (dematerialize) and
// deletes the install record. Idempotent for already-removed plugins.
Uninstall(ctx context.Context, opts UninstallOptions) error
// List returns installed plugins, optionally filtered by scope, client, or
// group membership.
List(ctx context.Context, opts ListOptions) ([]InstalledPlugin, error)
// Info returns details for a single installed plugin, including the
// component types each installed client adapter does NOT load.
Info(ctx context.Context, opts InfoOptions) (*PluginInfo, error)
}
PluginService declares the plugin lifecycle surface (mirrors skills.SkillService).
Phase 3 widens the Phase-2 surface with Install, Uninstall, List, and Info — the install/materialization lifecycle. The build/validate/push/content methods from Phase 2 stay stable; this is purely additive.
type PushOptions ¶
type PushOptions = skills.PushOptions
PushOptions configures the behavior of the Push operation. Alias for skills.PushOptions (Reference).
type Scope ¶
Scope is the installation scope for a plugin. It is an alias for skills.Scope because the scoping model is identical: a plugin is installed either user-wide or project-local, and the storage layer keys both on the same (scope, project_root) pair.
type ScopeSupport ¶ added in v0.34.0
type ScopeSupport struct {
// DegradesOnProjectScope is true when a project-scoped install degrades
// for this client (typically because the adapter mutates a user-scoped
// config file).
DegradesOnProjectScope bool
// Reason is an optional human-readable explanation intended for future
// surfacing via Info (not yet consumed by Info — it currently reads only
// DegradesOnProjectScope). Empty when the client does not degrade.
Reason string
}
ScopeSupport lets Info report project-scope degradation without re-running Materialize. A single client-wide boolean can't express "degrades only for these components" or carry a reason, so the descriptor generalizes the former DegradesOnProjectScope() bool.
type UninstallOptions ¶
type UninstallOptions = skills.UninstallOptions
UninstallOptions configures the behavior of the Uninstall operation. Alias for skills.UninstallOptions (identical shape).
type ValidationResult ¶
type ValidationResult = skills.ValidationResult
ValidationResult contains the outcome of a Validate operation. Alias for skills.ValidationResult.
func ValidatePluginDir ¶
func ValidatePluginDir(path string) (*ValidationResult, error)
ValidatePluginDir validates a plugin directory at the given path. It is the plugin analogue of skills.ValidateSkillDir. Steps:
- Clean + absolute path check.
- CheckFilesystem (symlink + traversal walk).
- ParsePluginManifest (strict keywords + component-path checks).
- Validate name (ValidatePluginName) and assert it matches the dir basename.
- For each manifest.Skills entry, reuse skills.ValidateSkillDir on the bundled skill at <pluginDir>/<path-without-leading-./> — this reuses the pkg/skills validator for bundled skills rather than duplicating it.
I/O errors are returned as error; validation issues are returned in ValidationResult.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package adapters contains MaterializationAdapter implementations for each MCP client that supports plugins.
|
Package adapters contains MaterializationAdapter implementations for each MCP client that supports plugins. |
|
Package mocks is a generated GoMock package.
|
Package mocks is a generated GoMock package. |
|
Package pluginsvc provides the default implementation of plugins.PluginService.
|
Package pluginsvc provides the default implementation of plugins.PluginService. |