Help us shape the future of Go! Take the 2026 Go Developer Survey

plugins

package
v1.38.0-beta.1 Latest Latest
Warning

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

Go to latest
Published: Oct 24, 2026 License: MIT Imports: 37 Imported by: 0

Documentation

Overview

Package plugins discovers user-installed lerd plugins and runs them: a folder holding plugin.yaml and the files it names, each surface served by running one of those files, so a plugin can be written in anything.

Index

Constants

View Source
const (
	EventSiteLinked      = "site.linked"
	EventSiteUnlinked    = "site.unlinked"
	EventSiteSecured     = "site.secured"
	EventSitePHPChanged  = "site.php_changed"
	EventWorktreeAdded   = "worktree.added"
	EventWorktreeRemoved = "worktree.removed"
	EventServiceStarted  = "service.started"
	EventServiceStopped  = "service.stopped"
	EventLerdStarted     = "lerd.started"
	EventLerdStopped     = "lerd.stopped"
)

Events a plugin can hook. The list only grows: a hook for a name this binary does not know refuses the plugin, so removing or renaming one breaks plugins.

View Source
const (
	APILimit         = 30 * time.Second
	MCPLimit         = 2 * time.Minute
	SiteCommandLimit = 30 * time.Minute
)

How long each surface lets a plugin's executable run before stopping it. A CLI command has none: it runs in the user's terminal, where Ctrl-C is theirs.

View Source
const (
	NotificationsNone    = "none"    // the plugin does not send any
	NotificationsPending = "pending" // it asks to, and the user has not answered
	NotificationsAllowed = "allowed"
	NotificationsDenied  = "denied"
)

The states of a plugin's notifications, as every surface shows them.

View Source
const (
	SurfaceCLI         = "cli"
	SurfaceSiteCommand = "site_command"
	SurfaceHook        = "hook"
	SurfaceMCP         = "mcp"
	SurfaceAPI         = "api"
)

Surfaces name which part of lerd ran a plugin file, passed as LERD_SURFACE so one script can serve several of them.

View Source
const APIURL = "http://127.0.0.1:7073"

APIURL is where a plugin reaches lerd's API, lerd-ui's loopback listener (internal/ui listenAddr).

View Source
const APIVersion = 1

APIVersion is the manifest version this binary understands. A plugin asking for a newer one is refused rather than half-loaded.

View Source
const HookLog = "hooks.log"

HookLog is the file in a plugin's data dir that collects its hooks' output.

View Source
const (
	// JobWorktreeDatabase creates and drops a worktree's isolated database.
	JobWorktreeDatabase = "worktree_database"
)

Jobs a plugin can take over from lerd by declaring itself in charge of them under provides:. Lerd then hands the job to the plugin instead of doing it.

View Source
const ManifestFile = "plugin.yaml"

ManifestFile is the file that makes a directory a plugin.

View Source
const PagePath = "/_lerd/plugins/"

PagePath is where a site's pages load plugin page scripts and reach their plugins' endpoints, under the site's own origin.

View Source
const SurfaceProvide = "provide"

SurfaceProvide is the surface a plugin's executable runs under when lerd hands it a job.

View Source
const TokenHeader = "X-Lerd-Plugin-Token"

TokenHeader carries a plugin's token on its calls to lerd's API.

View Source
const UIDir = "ui"

UIDir is the one plugin directory the dashboard serves.

Variables

View Source
var CatalogURL = "https://plugins.lerd.sh/index.json"

CatalogURL is the approved plugin listing plugins.lerd.sh publishes from the lerd-env/plugins registry. A var so tests can point it at their own server.

View Source
var Guide string

Guide is how to build a plugin, written for an AI assistant: the MCP plugin tool returns it and lerd mcp:inject writes it beside lerd's own skill.

Functions

func ClearFailure

func ClearFailure(name string)

ClearFailure forgets the plugin's failures, once it is removed or replaced.

func Compatible

func Compatible(constraint string) bool

Compatible reports whether this lerd meets a plugin's `>=X.Y` lower bound, on major and minor; other forms are refused. An untagged or dev build loads all, since it already holds the next release. Parsed here to link no service code.

func DataDirFor

func DataDirFor(name string) string

DataDirFor is the plugin's own writable directory, kept outside the plugin so reinstalling or removing the code leaves what it stored alone.

func Dir

func Dir() string

Dir is where plugins are installed, one directory each.

func Emit

func Emit(event string, payload map[string]string)

Emit runs every plugin's hook for event in turn, the payload as JSON on stdin and as LERD_<KEY>. Output and failure go to its hooks.log, never back to the caller, whose operation already succeeded.

func Env

func Env(payload map[string]string) map[string]string

Env turns a payload into the LERD_<KEY> variables a plugin reads it from.

func Failed

func Failed(f Failure)

Failed records f as the plugin's latest failure and tells the user when it is news: a different failure from the last, or the same one an hour later.

func IsCatalogName

func IsCatalogName(arg string) bool

IsCatalogName reports whether arg names a catalog plugin rather than a folder or a repository, which is how `install` tells them apart.

func IsEvent

func IsEvent(name string) bool

IsEvent reports whether name is an event plugins can hook.

func KeepRevoked

func KeepRevoked(name string) error

KeepRevoked lets a revoked plugin run again because the user chose to keep it. The choice is tied to this revocation, so a later one asks again.

func KillProcessGroup

func KillProcessGroup(cmd *exec.Cmd)

KillProcessGroup kills the group a started cmd leads. The group id is the leader's pid because OwnProcessGroup asked for a new group.

func Load

func Load() ([]*Plugin, []LoadError)

Load reads every installed plugin. A broken one is reported and skipped so it never takes the rest of lerd down with it.

func Missing

func Missing(reqs []string) []string

Missing returns the requirements this machine lacks.

func NotificationState

func NotificationState(p *Plugin) string

NotificationState is where the plugin stands. It is derived rather than stored, so an install or an update that starts asking is pending on its own, and an answer once given holds across updates.

func OwnProcessGroup

func OwnProcessGroup(cmd *exec.Cmd)

OwnProcessGroup makes cmd the leader of a new process group, so KillProcessGroup can stop it together with everything it spawns.

func PluginForToken

func PluginForToken(token string) string

PluginForToken returns the installed plugin the token belongs to, or "".

func Record

func Record(f Failure) bool

Record keeps f as the plugin's latest failure without telling anyone, for a surface where the user is already watching it fail, and reports whether it is news.

func RefreshCatalogOlderThan

func RefreshCatalogOlderThan(age time.Duration) error

RefreshCatalogOlderThan fetches the catalog when the copy lerd holds is older than age.

func Remove

func Remove(name string) error

Remove deletes an installed plugin's folder, broken or not, and keeps the data it stored in DataDirFor.

func RunWithin

func RunWithin(cmd *exec.Cmd, limit time.Duration) error

RunWithin runs cmd for every surface but the CLI and stops it, with all it started, once limit passes; the wait still gives up on a child that left the group holding the output open.

func RunsHere

func RunsHere(list []string) bool

RunsHere reports whether a plugin listing these platforms runs on this machine; a plugin that lists none runs everywhere lerd does.

func Scaffold

func Scaffold(name, dest string) error

Scaffold writes a new plugin called name into dest, which must not exist.

func SetFailureNotifier

func SetFailureNotifier(fn func(Failure))

SetFailureNotifier sets how this process tells the user about a new failure.

func SetNotifications

func SetNotifications(name string, allowed bool) error

SetNotifications records the user's answer for an installed plugin that asks to send notifications.

func SitePayload

func SitePayload(site *config.Site) map[string]string

SitePayload is how every surface describes a site to a plugin.

func Token

func Token(name string) (string, error)

Token is the plugin's API token: an HMAC of its name under a secret only the user can read, so lerd can tell which plugin made a call without storing a token per plugin. The secret is created on first use.

func UpdateListed

func UpdateListed(name string) error

UpdateListed replaces an installed plugin with its currently approved commit. The data it stored is kept.

func ValidName

func ValidName(name string) bool

ValidName reports whether name can be a plugin's, and so a folder name inside the plugins dir that climbs nowhere.

Types

type Catalog

type Catalog struct {
	Plugins []Listing `json:"plugins"`
	// Revoked are plugins pulled for a security problem; see Revocation.
	Revoked []Revocation `json:"revoked,omitempty"`
}

Catalog is the registry's published index.

func LoadCatalog

func LoadCatalog() (*Catalog, error)

LoadCatalog returns the approved plugins, fetching the index when the cached copy is a day old. When the fetch fails a cached copy, however old, still answers, so being offline never hides what is already known.

type Command

type Command struct {
	Name  string `yaml:"name" json:"name"`
	Usage string `yaml:"usage,omitempty" json:"usage,omitempty"`
	Run   string `yaml:"run" json:"run"`
}

Command is a CLI command, invoked as `lerd <plugin>:<name>`.

type Endpoint

type Endpoint struct {
	Path    string   `yaml:"path" json:"path"`
	Methods []string `yaml:"methods,omitempty" json:"methods,omitempty"`
	Run     string   `yaml:"run" json:"run"`
	// LoopbackOnly closes the endpoint to remote dashboards unless the user
	// turned on remote full access, like lerd's own host-action routes.
	LoopbackOnly bool `yaml:"loopback_only,omitempty" json:"loopback_only,omitempty"`
	// Timeout raises APILimit for this endpoint, up to ten minutes.
	Timeout string `yaml:"timeout,omitempty" json:"timeout,omitempty"`
}

Endpoint is served at /api/plugins/<plugin>/<path>.

func (Endpoint) Limit

func (e Endpoint) Limit() time.Duration

type Failure

type Failure struct {
	Plugin  string    `json:"plugin"`
	Surface string    `json:"surface"`
	What    string    `json:"what"`
	Error   string    `json:"error"`
	At      time.Time `json:"at"`
}

Failure is the latest thing a plugin's executable got wrong, kept so the dashboard can show it and offer to report it to the plugin's author.

func Failures

func Failures(name string) []Failure

Failures returns the plugin's recent failures, newest first. A failure kept by a lerd from before the log file is the whole history until a new one.

func LastFailure

func LastFailure(name string) (Failure, bool)

LastFailure returns the plugin's latest recorded failure.

func (Failure) ReportURL

func (f Failure) ReportURL() string

ReportURL opens a new issue on the plugin's repository, filled in with what its author needs. A plugin installed from a folder has nowhere to report to.

type Feature

type Feature struct {
	Kind   string `json:"kind"`
	Name   string `json:"name"`
	Detail string `json:"detail,omitempty"`
}

Feature is one thing a plugin adds, as the dashboard lists it.

type Listing

type Listing struct {
	Name        string   `json:"name"`
	Description string   `json:"description,omitempty"`
	Repo        string   `json:"repo"`
	Commit      string   `json:"commit"`
	Author      string   `json:"author,omitempty"`
	Category    string   `json:"category,omitempty"`
	Tags        []string `json:"tags,omitempty"`
	// Version is the approved commit's own version, as its plugin.yaml says.
	Version string `json:"version,omitempty"`
	// Lerd is the approved commit's own `lerd` constraint.
	Lerd string `json:"lerd,omitempty"`
	// Requires is what the approved commit needs on the machine, as
	// Plugin.Requirements reads it.
	Requires []string `json:"requires,omitempty"`
	// Platforms is the approved commit's own `platforms`, empty for both.
	Platforms []string `json:"platforms,omitempty"`
	// Images are copied by the registry at the approved commit and published
	// beside the index, the first one being the preview. The index lists them
	// relative to itself; LoadCatalog hands them out as absolute URLs.
	Images []string `json:"images,omitempty"`
	// AddedAt and UpdatedAt are the days the plugin was first approved and its
	// current commit was; Stars is its repository's count when the index was built.
	AddedAt   string `json:"added_at,omitempty"`
	UpdatedAt string `json:"updated_at,omitempty"`
	Stars     int    `json:"stars,omitempty"`
}

Listing is one approved plugin: its repository and the exact commit a maintainer approved, which is the only one lerd will install.

func CachedListing

func CachedListing(name string) (Listing, bool)

CachedListing is the catalog's listing for name from the copy lerd already holds, never fetching: for showing what the catalog says about a plugin that is installed, where a slow network must not hold the list up.

func CachedUpdate

func CachedUpdate(name string) (*Listing, bool)

CachedUpdate is PendingUpdate read from the catalog lerd already holds, never fetching: the dashboard draws updates without waiting on the network, and the hourly refresh brings new ones in.

func FindListing

func FindListing(name string) (*Listing, error)

FindListing returns the approved listing called name.

func PendingUpdate

func PendingUpdate(name string) (*Listing, bool)

PendingUpdate returns the newer approved listing for an installed catalog plugin, or false when it is current, unlisted, needs a newer lerd or the catalog is unreachable.

func (Listing) ChangesURL

func (l Listing) ChangesURL(installed string) string

ChangesURL is GitHub's comparison of the installed commit with the approved one, "" for a repository GitHub does not host.

type LoadError

type LoadError struct {
	Dir string
	Err error
}

LoadError is a plugin directory that could not be loaded.

func (LoadError) Error

func (e LoadError) Error() string

type MCP

type MCP struct {
	Skill string    `yaml:"skill,omitempty" json:"skill,omitempty"`
	Tools []MCPTool `yaml:"tools,omitempty" json:"tools,omitempty"`
}

type MCPTool

type MCPTool struct {
	Name        string         `yaml:"name" json:"name"`
	Description string         `yaml:"description" json:"description"`
	InputSchema map[string]any `yaml:"input_schema,omitempty" json:"input_schema,omitempty"`
	Run         string         `yaml:"run" json:"run"`
	// Timeout raises MCPLimit for this tool, up to ten minutes.
	Timeout string `yaml:"timeout,omitempty" json:"timeout,omitempty"`
}

func (MCPTool) Limit

func (t MCPTool) Limit() time.Duration

type Manifest

type Manifest struct {
	APIVersion  int    `yaml:"api_version" json:"api_version"`
	Name        string `yaml:"name" json:"name"`
	Version     string `yaml:"version,omitempty" json:"version,omitempty"`
	Description string `yaml:"description,omitempty" json:"description,omitempty"`
	// Lerd is the oldest lerd release the plugin needs, as a constraint like ">=1.37".
	Lerd         string            `yaml:"lerd,omitempty" json:"lerd,omitempty"`
	Commands     []Command         `yaml:"commands,omitempty" json:"commands,omitempty"`
	SiteCommands []SiteCommand     `yaml:"site_commands,omitempty" json:"site_commands,omitempty"`
	Hooks        map[string]string `yaml:"hooks,omitempty" json:"hooks,omitempty"`
	MCP          *MCP              `yaml:"mcp,omitempty" json:"mcp,omitempty"`
	API          []Endpoint        `yaml:"api,omitempty" json:"api,omitempty"`
	UI           *UI               `yaml:"ui,omitempty" json:"ui,omitempty"`
	// Provides maps each job the plugin takes over from lerd to the executable
	// lerd runs for it.
	Provides map[string]string `yaml:"provides,omitempty" json:"provides,omitempty"`
	// Requires names the commands the plugin's scripts call beyond their own
	// interpreters, which lerd reads from each script's first line.
	Requires []string `yaml:"requires,omitempty" json:"requires,omitempty"`
	// Platforms lists where the plugin runs, linux or macos; empty means both.
	Platforms []string `yaml:"platforms,omitempty" json:"platforms,omitempty"`
	// Icon is a single-colour SVG drawn for the plugin on its card, whatever it
	// adds; a rail page's own icon stands in when it is empty.
	Icon string `yaml:"icon,omitempty" json:"icon,omitempty"`
	// Docs is a markdown file of instructions the dashboard shows once the
	// plugin is installed, apart from the README its repository shows.
	Docs string `yaml:"docs,omitempty" json:"docs,omitempty"`
	// Screenshots are images in the plugin shown on its card in the dashboard.
	Screenshots []string `yaml:"screenshots,omitempty" json:"screenshots,omitempty"`
	// Notifications says the plugin may tell the user something through lerd's
	// notifications, which it can only do once the user has allowed it.
	Notifications bool `yaml:"notifications,omitempty" json:"notifications,omitempty"`
}

type Plugin

type Plugin struct {
	Manifest
	Dir string `json:"dir"`
}

Plugin is a validated manifest and the directory it was read from.

func Find

func Find(name string) (*Plugin, error)

Find returns the installed plugin called name.

func Install

func Install(src string) (*Plugin, error)

Install copies or clones src into a staging dir inside the plugins dir, validates it, then moves it under the name its manifest declares. Nothing is left behind when the plugin turns out to be invalid.

func InstallListed

func InstallListed(name string) (*Plugin, error)

InstallListed installs an approved plugin at the commit it was approved at.

func ProviderOf

func ProviderOf(job string) *Plugin

ProviderOf returns the installed plugin in charge of job, or nil when lerd does the job itself.

func ReadPlugin

func ReadPlugin(dir string) (*Plugin, error)

ReadPlugin reads and validates the plugin in dir. Unknown keys are ignored so a manifest written for a newer lerd still loads, but an unknown value in a known key (an event, a method) is refused by name.

func (*Plugin) CardIcon

func (p *Plugin) CardIcon() string

CardIcon is the icon the dashboard draws for the plugin: its own, or its rail page's, "" when it has neither.

func (*Plugin) Command

func (p *Plugin) Command(run, surface string, args []string, extra map[string]string) (*exec.Cmd, error)

Command builds the process for one of the plugin's files, with its context as LERD_* variables plus what the surface knows in extra. It creates the data dir first, so a script can write there without checking.

func (*Plugin) DocsHTML

func (p *Plugin) DocsHTML(fileURL func(rel string) string) (string, error)

DocsHTML renders the docs file offline, "" when there is none. Raw HTML is dropped, local images resolve through fileURL, and only web links stay links: a relative one points into a repository the reader may not have.

func (*Plugin) Features

func (p *Plugin) Features() []Feature

Features lists what the plugin adds, named the way the user meets each one.

func (*Plugin) Provide

func (p *Plugin) Provide(job, action string, request map[string]string) ([]byte, error)

Provide runs the plugin's executable for job as `<run> <action>`, with the request as JSON on stdin, and returns what it printed. A failure carries what the plugin wrote to stderr, since that is the only reason anyone gets.

func (*Plugin) Requirements

func (p *Plugin) Requirements() []string

Requirements lists what the plugin needs on this machine: the interpreter each of its executables names on its first line, and the commands its manifest declares under requires:.

func (*Plugin) Serves

func (p *Plugin) Serves(rel string) bool

Serves reports whether the dashboard may hand out the plugin file at rel: anything under ui/, its icon and the screenshots the manifest lists. The rest of the plugin, its scripts above all, stays private.

func (*Plugin) Surfaces

func (p *Plugin) Surfaces() []string

Surfaces names the parts of lerd the plugin adds to, in a fixed order.

type Revocation

type Revocation struct {
	Name    string   `json:"name"`
	Reason  string   `json:"reason"`
	At      string   `json:"at"`
	Commits []string `json:"commits,omitempty"`
}

Revocation is the catalog pulling a plugin for a security problem, with the reason every user who has it is shown. Commits are the approved commits it covers; with none it covers every install of the name.

func Revoked

func Revoked() []Revocation

Revoked lists the installed plugins a revocation is stopping now.

type RevokedError

type RevokedError struct{ Revocation }

RevokedError is why a revoked plugin did not load.

func (RevokedError) Error

func (e RevokedError) Error() string

type SiteCommand

type SiteCommand struct {
	Name        string `yaml:"name" json:"name"`
	Label       string `yaml:"label" json:"label"`
	Description string `yaml:"description,omitempty" json:"description,omitempty"`
	Confirm     bool   `yaml:"confirm,omitempty" json:"confirm,omitempty"`
	Run         string `yaml:"run" json:"run"`
	// Timeout raises SiteCommandLimit for this command, up to an hour.
	Timeout string `yaml:"timeout,omitempty" json:"timeout,omitempty"`
}

SiteCommand runs against one site, from its Commands menu and the palette.

func (SiteCommand) Limit

func (c SiteCommand) Limit() time.Duration

type Source

type Source struct {
	Repo   string `json:"repo"`
	Commit string `json:"commit"`
}

Source records which listing and commit an installed plugin came from, kept in the plugin's folder so it goes with the install.

func InstalledSource

func InstalledSource(name string) (Source, bool)

InstalledSource reports the listing commit an installed plugin came from; false for one installed from a folder or an unlisted repository.

type UI

type UI struct {
	Page    *UIPage `yaml:"page,omitempty" json:"page,omitempty"`
	SiteTab *UIPage `yaml:"site_tab,omitempty" json:"site_tab,omitempty"`
	// OverviewCard is drawn on the Overview of the sites the user switched the
	// plugin's card on for.
	OverviewCard *UIPage `yaml:"overview_card,omitempty" json:"overview_card,omitempty"`
	// PageScript is injected into the pages of the sites the user switched the
	// plugin on for, and runs in those sites' own origin.
	PageScript string `yaml:"page_script,omitempty" json:"page_script,omitempty"`
}

type UIPage

type UIPage struct {
	Title string `yaml:"title" json:"title"`
	Icon  string `yaml:"icon,omitempty" json:"icon,omitempty"`
	Entry string `yaml:"entry" json:"entry"`
}

Jump to

Keyboard shortcuts

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