plugins

package
v0.34.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 7, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

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

View Source
const (
	// ScopeUser indicates a plugin installed user-wide.
	ScopeUser = skills.ScopeUser
	// ScopeProject indicates a plugin installed for a specific project.
	ScopeProject = skills.ScopeProject
)
View Source
const (
	InstallStatusInstalled = skills.InstallStatusInstalled
	InstallStatusPending   = skills.InstallStatusPending
	InstallStatusFailed    = skills.InstallStatusFailed
)

Install lifecycle statuses. Aliased from skills.InstallStatus because the lifecycle model is identical.

View Source
const ManifestPath = ".claude-plugin/plugin.json"

ManifestPath is the required manifest file path for a plugin directory, matching the Claude Plugin manifest format.

View Source
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.

View Source
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

View Source
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).

View Source
var ErrInvalidManifest = errors.New("invalid plugin manifest")

ErrInvalidManifest indicates that the plugin manifest is malformed, missing, or fails toolhive-side strictness checks.

View Source
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).

View Source
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.

View Source
var ValidateScope = skills.ValidateScope

ValidateScope validates a plugin scope. Re-exported from skills because the rule (empty | "user" | "project") is identical.

Functions

func ValidatePluginName

func ValidatePluginName(name string) error

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

type Scope = skills.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:

  1. Clean + absolute path check.
  2. CheckFilesystem (symlink + traversal walk).
  3. ParsePluginManifest (strict keywords + component-path checks).
  4. Validate name (ValidatePluginName) and assert it matches the dir basename.
  5. 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.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL