plugin

package
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 30, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package plugin implements the Claude Code plugin marketplace format — marketplace.json catalogs, plugin.json manifests, the on-disk install state — plus the ask-side operations over it: add/refresh marketplaces, install/enable plugins at user or project scope, publish local skills, agents, and workflows into a marketplace, and import the state Claude Code already holds under ~/.claude/plugins.

The file shapes are byte-compatible with Claude Code so a marketplace built for one tool installs in the other. ask-only content (workflows, the `provider:` key on agents) rides in the same plugin directory; Claude Code ignores what it does not know. Plugins may also ship MCP servers as a plugin-root `.mcp.json` or an `mcps/` directory of `.mcp.json`-format files; ask attaches them to sessions, and Claude Code reads the root `.mcp.json` too.

Index

Constants

View Source
const (
	MarketplaceManifestRel = ".claude-plugin/marketplace.json"
	PluginManifestRel      = ".claude-plugin/plugin.json"
)
View Source
const (
	SourcePath      = "path"
	SourceGitHub    = "github"
	SourceURL       = "url"
	SourceGitSubdir = "git-subdir"
	SourceGit       = "git"
)

Source kinds for a plugin entry.

View Source
const (
	MarketplaceSourceGitHub    = "github"
	MarketplaceSourceGit       = "git"
	MarketplaceSourceDirectory = "directory"
	MarketplaceSourceURL       = "url"
)

Marketplace source kinds — the `source` field of known_marketplaces.json.

Variables

View Source
var ClaudeHome = func() string {
	home, err := os.UserHomeDir()
	if err != nil || home == "" {
		return ""
	}
	return filepath.Join(home, ".claude")
}

ClaudeHome locates Claude Code's state; swappable for tests.

View Source
var HTTPClient = http.DefaultClient

HTTPClient fetches marketplace.json URLs; swappable for tests.

View Source
var Now = time.Now

Now is the clock for timestamps; swappable for tests.

View Source
var RunGit = func(ctx context.Context, dir string, args ...string) (string, error) {
	cmd := exec.CommandContext(ctx, "git", args...)
	if dir != "" {
		cmd.Dir = dir
	}
	cmd.Env = append(os.Environ(), "GIT_TERMINAL_PROMPT=0")
	out, err := cmd.CombinedOutput()
	if err != nil {
		return string(out), fmt.Errorf("git %s: %w: %s", strings.Join(args, " "), err, strings.TrimSpace(string(out)))
	}
	return string(out), nil
}

RunGit executes git with args in dir and returns its combined output. Swappable so tests never spawn a subprocess.

Functions

func BumpPatch

func BumpPatch(v string) string

BumpPatch increments the patch number of a semver string; anything that is not major.minor.patch is returned unchanged.

func CacheDir

func CacheDir() string

func EntryLocalDir

func EntryLocalDir(m Marketplace, entry Entry) (string, bool)

EntryLocalDir returns the plugin directory for a path-source entry inside the marketplace's local clone, so its contents can be listed before installing. Remote entries have no local directory.

func ForgetPublication

func ForgetPublication(cwd, kind, name, scope string) error

ForgetPublication drops the link (the marketplace copy is untouched).

func HashBytes

func HashBytes(data []byte) string

HashBytes hashes file content the way HashPath hashes a file.

func HashPath

func HashPath(p string) string

HashPath hashes a file, or a directory's relative paths and contents (sorted, .git skipped), so two copies compare by content alone.

func InitMarketplace

func InitMarketplace(ctx context.Context, dir, name, owner string) error

InitMarketplace turns dir into an empty marketplace (git-initialised) so the user can publish into it and push it wherever they like.

func MarketplacesDir

func MarketplacesDir() string

func ProjectFilePath

func ProjectFilePath(cwd string) string

ProjectFilePath is <project root>/.ask/plugins.json.

func PublishedCopyPath

func PublishedCopyPath(m Marketplace, pub Publication) string

PublishedCopyPath is where the marketplace holds the published item.

func RecordPublication

func RecordPublication(cwd string, pub Publication) error

RecordPublication stores pub — in the project file for project-scope items (committed, so the team sees where the item lives), in the user file otherwise.

func RefreshAll

func RefreshAll(ctx context.Context, cwd string) []error

RefreshAll refreshes every registered marketplace, collecting failures.

func RefreshMarketplace

func RefreshMarketplace(ctx context.Context, cwd string, m Marketplace) error

RefreshMarketplace pulls the latest catalog for m.

func RemoveMarketplace

func RemoveMarketplace(cwd, name string, scope Scope) error

RemoveMarketplace drops the registration in scope. The clone is deleted once no scope references it; directory sources are never touched.

func RootDir

func RootDir() string

RootDir is ~/.config/ask/plugins.

func SetEnabled

func SetEnabled(ref Ref, enabled bool) error

SetEnabled flips a user-scope install on or off without removing it.

func UninstallPlugin

func UninstallPlugin(cwd string, ref Ref, scope Scope) error

UninstallPlugin disables ref in scope; the cached copy is removed once no scope references it.

func ValidateName

func ValidateName(name string) error

ValidateName enforces the kebab-case rule shared by marketplace, plugin, skill, and agent names.

Types

type Author

type Author struct {
	Name  string `json:"name"`
	Email string `json:"email,omitempty"`
	URL   string `json:"url,omitempty"`
}

Author accepts both the object form and a bare string.

func (*Author) UnmarshalJSON

func (a *Author) UnmarshalJSON(b []byte) error

type ClaudeState

type ClaudeState struct {
	Marketplaces   map[string]KnownMarketplace
	UserEnabled    map[string]bool
	ProjectEnabled map[string]bool
}

ClaudeState is what Claude Code holds that ask can import: registered marketplaces and the plugins enabled at user and project level.

func ReadClaudeState

func ReadClaudeState(cwd string) ClaudeState

ReadClaudeState reads ~/.claude/plugins/known_marketplaces.json, ~/.claude/settings.json, and <root>/.claude/settings.json. Missing files are simply absent from the result.

func (ClaudeState) Empty

func (s ClaudeState) Empty() bool

Empty reports whether there is nothing to import.

func (ClaudeState) EnabledRefs

func (s ClaudeState) EnabledRefs() []string

EnabledRefs is the sorted union of user- and project-enabled plugins.

type Contents

type Contents struct {
	SkillDirs     []string
	CommandFiles  []string
	AgentFiles    []string
	WorkflowFiles []string
	// MCPFiles are .mcp.json-format files the plugin ships: the plugin-root
	// .mcp.json, every *.json under mcps/, plus any manifest/entry
	// "mcpServers" paths.
	MCPFiles []string
	// InlineMCP holds inline `mcpServers` objects declared directly in the
	// plugin.json manifest or the marketplace entry (Claude Code's inline
	// form), each a raw `{"name": {…}}` map. tools.PluginMCPServers decodes
	// them alongside the file-based servers.
	InlineMCP []json.RawMessage
}

Contents is what a plugin directory contributes, resolved to absolute paths: skill packages (directories holding SKILL.md), single-file commands (Claude Code's commands/*.md, loaded as skills), agent definitions, and ask workflows.

func ResolveContents

func ResolveContents(dir string, entry *Entry, manifest *PluginManifest) Contents

ResolveContents applies the plugin.json / marketplace-entry component paths (strict: both merge; strict:false: the entry alone) with the default directories, and expands them to concrete files.

func (Contents) Count

func (c Contents) Count() int

func (Contents) Empty

func (c Contents) Empty() bool

type Entry

type Entry struct {
	Name        string          `json:"name"`
	Description string          `json:"description,omitempty"`
	Version     string          `json:"version,omitempty"`
	Category    string          `json:"category,omitempty"`
	Homepage    string          `json:"homepage,omitempty"`
	Author      *Author         `json:"author,omitempty"`
	Source      Source          `json:"source"`
	Strict      *bool           `json:"strict,omitempty"`
	Skills      PathList        `json:"skills,omitempty"`
	Agents      PathList        `json:"agents,omitempty"`
	Commands    PathList        `json:"commands,omitempty"`
	Workflows   PathList        `json:"workflows,omitempty"`
	MCPServers  MCPServersField `json:"mcpServers,omitempty"`
	Tags        []string        `json:"tags,omitempty"`
	Keywords    []string        `json:"keywords,omitempty"`
}

Entry is one plugin listed by a marketplace.

func (Entry) IsStrict

func (e Entry) IsStrict() bool

IsStrict reports whether plugin.json is the authority for the plugin's component paths (the default). strict:false lets the marketplace entry stand in for a missing plugin.json — how anthropics/skills ships bare SKILL.md directories.

type ImportReport

type ImportReport struct {
	Marketplaces []string
	Plugins      []string
	Skipped      []string
	Errors       []string
}

ImportReport is what ImportFromClaude did.

func ImportFromClaude

func ImportFromClaude(ctx context.Context, cwd string, st ClaudeState, scope Scope) ImportReport

ImportFromClaude registers Claude Code's marketplaces and installs its enabled plugins into ask's own store at scope. Nothing under ~/.claude is modified.

func (ImportReport) Summary

func (r ImportReport) Summary() string

Summary is the one-line toast form.

type Installed

type Installed struct {
	Ref    Ref
	Scope  Scope
	Scopes []Scope
	// Dir is the installed copy; "" (and Missing) when the project file
	// enables a plugin this machine has not fetched yet.
	Dir      string
	Version  string
	Missing  bool
	Enabled  bool
	Entry    *Entry
	Manifest *PluginManifest
}

Installed is a plugin enabled for the current project/user.

func EnabledPlugins

func EnabledPlugins(cwd string) []Installed

EnabledPlugins resolves every plugin enabled for cwd — user installs plus the project file — in ref order. Entries the project enables but this machine has not fetched come back Missing.

func FindInstalled

func FindInstalled(cwd string, ref Ref) (Installed, bool)

FindInstalled returns the enabled install for ref, if any.

func InstallPlugin

func InstallPlugin(ctx context.Context, cwd string, ref Ref, scope Scope) (Installed, error)

InstallPlugin fetches ref's plugin into the cache and enables it in scope.

func InstalledPlugins

func InstalledPlugins(cwd string) []Installed

InstalledPlugins lists every install record on this machine (enabled or not) plus the project's enabled set, for the browser's installed lens.

func (Installed) Contents

func (in Installed) Contents() Contents

Contents lists what the installed copy contributes.

func (Installed) Description

func (in Installed) Description() string

Description prefers the plugin's own manifest over the catalog entry.

type InstalledRecord

type InstalledRecord struct {
	Scope        Scope  `json:"scope"`
	ProjectRoot  string `json:"projectRoot,omitempty"`
	InstallPath  string `json:"installPath"`
	Version      string `json:"version,omitempty"`
	InstalledAt  string `json:"installedAt,omitempty"`
	LastUpdated  string `json:"lastUpdated,omitempty"`
	GitCommitSha string `json:"gitCommitSha,omitempty"`
	Enabled      *bool  `json:"enabled,omitempty"`
	Entry        *Entry `json:"entry,omitempty"`
}

InstalledRecord is one install of a plugin, keyed by "name@marketplace" in installed_plugins.json.

type KnownMarketplace

type KnownMarketplace struct {
	Source          MarketplaceSource `json:"source"`
	InstallLocation string            `json:"installLocation,omitempty"`
	LastUpdated     string            `json:"lastUpdated,omitempty"`
	AutoUpdate      *bool             `json:"autoUpdate,omitempty"`
}

KnownMarketplace is one registration — the value shape of Claude Code's known_marketplaces.json.

type MCPServersField

type MCPServersField struct {
	Paths PathList
	// Inline is the raw inline `mcpServers` object (`{"name": {…}}`) when the
	// inline-object form was used, or nil. It is the value of the mcpServers
	// key, not a wrapped `{"mcpServers": …}` document, so it decodes straight
	// into a map[string]MCPServerConfig.
	Inline json.RawMessage
}

MCPServersField is the manifest/marketplace-entry "mcpServers" component field. Claude Code allows either a path (or list of paths) to .mcp.json-format files, or an inline object of servers. We honor both: the path forms add files that ResolveContents picks up, and the inline object (the raw `{"name": {…}}` map) is preserved in Inline so ResolveContents can surface it and tools.PluginMCPServers can decode it directly.

func (MCPServersField) MarshalJSON

func (f MCPServersField) MarshalJSON() ([]byte, error)

MarshalJSON round-trips the field back to Claude Code's real "mcpServers" shape — the inline object, a path array, or null — rather than the Go struct's default `{"Paths":…,"Inline":…}`. Without this, persisting an Entry (e.g. into installed_plugins.json) and re-reading it would decode the struct blob as a spurious inline object.

func (*MCPServersField) UnmarshalJSON

func (f *MCPServersField) UnmarshalJSON(b []byte) error

type Marketplace

type Marketplace struct {
	Name   string
	Scope  Scope
	Source MarketplaceSource
	// Dir is the local clone (or the directory itself for directory
	// sources). It may not exist yet on this machine for a marketplace a
	// teammate registered in .ask/plugins.json.
	Dir      string
	Manifest *MarketplaceManifest
	// Err explains why Manifest is nil (not fetched, malformed).
	Err string
}

Marketplace is one registered marketplace plus its fetched catalog.

func AddMarketplace

func AddMarketplace(ctx context.Context, cwd, raw string, scope Scope) (Marketplace, error)

AddMarketplace registers the marketplace at raw (owner/repo, git URL, directory, or marketplace.json URL) in scope, fetching its catalog. Re-adding a source that is already registered is idempotent.

func FindMarketplace

func FindMarketplace(cwd, name string) (Marketplace, bool)

FindMarketplace looks a registered marketplace up by name.

func ListMarketplaces

func ListMarketplaces(cwd string) []Marketplace

ListMarketplaces merges the user registrations with the project file. A name registered in both scopes is listed once, as the user's.

func (Marketplace) Entry

func (m Marketplace) Entry(name string) (Entry, bool)

Entry returns the catalog entry for plugin name.

func (Marketplace) Fetched

func (m Marketplace) Fetched() bool

Fetched reports whether the catalog is available locally.

func (Marketplace) Writable

func (m Marketplace) Writable() bool

Writable reports whether Publish can land files in this marketplace: a directory or git clone we hold locally. A marketplace fetched as a bare marketplace.json URL has nowhere to put a plugin.

type MarketplaceManifest

type MarketplaceManifest struct {
	Name        string               `json:"name"`
	Description string               `json:"description,omitempty"`
	Owner       Owner                `json:"owner"`
	Metadata    *MarketplaceMetadata `json:"metadata,omitempty"`
	Plugins     []Entry              `json:"plugins"`
}

MarketplaceManifest is .claude-plugin/marketplace.json.

func ReadMarketplaceManifest

func ReadMarketplaceManifest(dir string) (*MarketplaceManifest, error)

ReadMarketplaceManifest parses dir/.claude-plugin/marketplace.json.

func (*MarketplaceManifest) Entry

func (m *MarketplaceManifest) Entry(name string) (Entry, bool)

Entry returns the marketplace's entry for name.

type MarketplaceMetadata

type MarketplaceMetadata struct {
	Description string `json:"description,omitempty"`
	Version     string `json:"version,omitempty"`
	PluginRoot  string `json:"pluginRoot,omitempty"`
}

type MarketplaceSource

type MarketplaceSource struct {
	Kind string `json:"source"`
	Repo string `json:"repo,omitempty"`
	URL  string `json:"url,omitempty"`
	Path string `json:"path,omitempty"`
}

MarketplaceSource says where a marketplace comes from.

func ParseMarketplaceSource

func ParseMarketplaceSource(cwd, raw string) (MarketplaceSource, error)

ParseMarketplaceSource classifies what the user typed after `/skills add marketplace`: a GitHub `owner/repo`, a git URL, a local directory, or a direct URL to a marketplace.json. Relative directories resolve against cwd.

func (MarketplaceSource) Display

func (s MarketplaceSource) Display() string

Display is the one-line form shown in the browser.

func (MarketplaceSource) Equal

func (MarketplaceSource) GitURL

func (s MarketplaceSource) GitURL() string

GitURL is the clone URL for git-backed sources, "" otherwise.

func (MarketplaceSource) Raw

func (s MarketplaceSource) Raw() string

Raw is the string form AddMarketplace accepts back — what a user would have typed to register this source.

type Owner

type Owner struct {
	Name  string `json:"name"`
	Email string `json:"email,omitempty"`
	URL   string `json:"url,omitempty"`
}

Owner is the marketplace owner block.

type PathList

type PathList []string

PathList is a manifest field that may be one path or a list of paths.

func (*PathList) UnmarshalJSON

func (p *PathList) UnmarshalJSON(b []byte) error

type PluginManifest

type PluginManifest struct {
	Name        string          `json:"name"`
	DisplayName string          `json:"displayName,omitempty"`
	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    []string        `json:"keywords,omitempty"`
	Skills      PathList        `json:"skills,omitempty"`
	Agents      PathList        `json:"agents,omitempty"`
	Commands    PathList        `json:"commands,omitempty"`
	Workflows   PathList        `json:"workflows,omitempty"`
	MCPServers  MCPServersField `json:"mcpServers,omitempty"`
}

PluginManifest is .claude-plugin/plugin.json.

func ReadPluginManifest

func ReadPluginManifest(dir string) (*PluginManifest, error)

ReadPluginManifest parses dir/.claude-plugin/plugin.json; a missing file is (nil, nil) — the marketplace entry then describes the plugin.

type ProjectFile

type ProjectFile struct {
	Marketplaces map[string]KnownMarketplace `json:"marketplaces,omitempty"`
	Enabled      map[string]bool             `json:"enabled,omitempty"`
	Published    map[string]Publication      `json:"published,omitempty"`
}

ProjectFile is <root>/.ask/plugins.json.

func ReadProjectFile

func ReadProjectFile(cwd string) ProjectFile

ReadProjectFile loads the project's plugin file (zero value when absent).

type Publication

type Publication struct {
	Kind        string `json:"kind"`
	Name        string `json:"name"`
	Scope       string `json:"scope"`
	Marketplace string `json:"marketplace"`
	Plugin      string `json:"plugin"`
	// File is the item's name inside the plugin: the skill directory,
	// the agent file, or the workflow file.
	File        string `json:"file"`
	Version     string `json:"version,omitempty"`
	Hash        string `json:"hash"`
	PublishedAt string `json:"publishedAt,omitempty"`
	Commit      string `json:"commit,omitempty"`
}

A Publication links a local skill, agent, or workflow to the plugin it was published as. The local copy stays the source of truth; the link records what was last published (Hash) so the two can be compared.

func FindPublication

func FindPublication(cwd, kind, name, scope string) (Publication, bool)

FindPublication returns the link for a local item.

func PublicationForRef

func PublicationForRef(cwd string, ref Ref) (Publication, bool)

PublicationForRef says whether a marketplace plugin is one the user published from a local copy here.

func Publications

func Publications(cwd string) []Publication

Publications lists every link visible from cwd.

func Pull

func Pull(cwd string, pub Publication, localPath string) (Publication, error)

Pull replaces the local copy with the marketplace copy and re-bases the publication on it.

func (Publication) Ref

func (p Publication) Ref() Ref

Ref is the plugin reference the publication points at.

type PublishRequest

type PublishRequest struct {
	PluginName  string
	Description string
	Version     string
	// SkillDirs are skill packages (directories holding SKILL.md).
	SkillDirs []string
	// AgentFiles are agent definitions (*.md).
	AgentFiles []string
	// WorkflowFiles are ask workflow definitions (*.json).
	WorkflowFiles []string
	Message       string
	// NoPush keeps the commit local even when the marketplace has a
	// remote. By default a git-backed marketplace is pulled (ff-only)
	// before the commit and pushed after it.
	NoPush bool
}

PublishRequest describes what to land in a marketplace as one plugin.

type PublishResult

type PublishResult struct {
	PluginDir string
	Version   string
	Committed bool
	Pushed    bool
	Commit    string
	Note      string
}

PublishResult reports where the plugin landed and how far git got.

func Publish

Publish copies the request's items into <marketplace>/plugins/<name>/, writes the plugin manifest, upserts the catalog entry, and commits (and pushes, when asked and a remote exists).

type Ref

type Ref struct {
	Plugin      string
	Marketplace string
}

Ref names a plugin as "name@marketplace".

func ParseRef

func ParseRef(s string) (Ref, error)

ParseRef splits "name@marketplace".

func (Ref) String

func (r Ref) String() string

type Scope

type Scope string

Scope says where a marketplace registration or plugin enablement lives.

const (
	ScopeUser    Scope = "user"
	ScopeProject Scope = "project"
)

func NormalizeScope

func NormalizeScope(s string) (Scope, error)

NormalizeScope maps "" to user and validates the rest.

type Source

type Source struct {
	Kind string
	Path string
	Repo string
	URL  string
	Ref  string
	SHA  string
}

Source locates a plugin: a path relative to the marketplace, or a git repository (optionally pinned to a ref/sha and narrowed to a subdir).

func ParseSourceString

func ParseSourceString(str string) Source

ParseSourceString classifies the string form of a plugin source.

func (Source) GitURL

func (s Source) GitURL() string

GitURL is the clone URL for a remote source, "" for a path source.

func (Source) MarshalJSON

func (s Source) MarshalJSON() ([]byte, error)

func (Source) Remote

func (s Source) Remote() bool

Remote reports whether the plugin must be fetched from a git repository.

func (Source) String

func (s Source) String() string

String is the display form of a source.

func (*Source) UnmarshalJSON

func (s *Source) UnmarshalJSON(b []byte) error

type SyncReport

type SyncReport struct {
	Installed []Ref
	Errors    []error
}

SyncReport is what SyncProject did.

func SyncProject

func SyncProject(ctx context.Context, cwd string) SyncReport

SyncProject fetches every plugin the project file enables but this machine lacks, registering the project's marketplaces as needed.

type SyncState

type SyncState int

SyncState compares the local copy and the marketplace copy against what was last published.

const (
	SyncInSync SyncState = iota
	SyncLocalChanged
	SyncMarketplaceChanged
	SyncDiverged
	SyncMissing
)

func Status

func Status(cwd string, pub Publication, localHash string) SyncState

Status classifies a publication given the current hash of the local copy (HashPath of the skill dir / agent file, HashBytes of a workflow export).

func (SyncState) String

func (s SyncState) String() string

Jump to

Keyboard shortcuts

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