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

plugins

package
v0.40.0 Latest Latest
Warning

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

Go to latest
Published: Oct 14, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package plugins implements knot's plugin system: a plugin is a folder in the configured PluginsPath whose declarations — permissions, menus, pages, handlers, MCP tools, logos, icons — live statically in a [tool.knot] table. Nothing a plugin declares is produced by executing plugin code; loading is pure parsing, and plugin code only ever runs per-invocation in a user-bound environment.

The [tool.knot] table is sourced two ways, parsed identically:

  • Pure-script plugin: knot reads main.py and parses its metadata block.
  • Peer plugin: a bin/ peer (a scriptling plugin-protocol executable in any language) returns the table as static manifest data in its handshake (Custom["tool.knot"]); knot reads it there. Such a plugin needs no companion main.py. A peer plugin may still ship a main.py for scriptling handlers, in which case main.py's block is authoritative.

Handlers a plugin declares are addressed as plugin.<name>.<function>: a peer exports them over the plugin protocol, a main.py exposes them by being registered as the plugin.<name> library — the dispatch layer calls the qualified name and does not care which kind answers.

A peer is a component of the plugin: it is spawned and handshaked at load so its manifest and version are available for declaration parsing and requirement verification. Whether it also registers a callable surface depends on the plugin's handler declarations.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func MCPToolParamSchemaType

func MCPToolParamSchemaType(declared string) string

MCPToolParamSchemaType maps a declared parameter type to its JSON schema type; unknown types map to "string".

func PluginEnvLibraries

func PluginEnvLibraries() []string

PluginEnvLibraries lists the library names plugin handler environments register — the same set metadata dependency resolution resolves against. Exported for the drift guard test (service package), which builds a real plugin env and asserts every name imports.

func QualifiedPermission

func QualifiedPermission(pluginName, id string) string

QualifiedPermission builds the canonical grant for a plugin permission id.

func ScriptNamespace

func ScriptNamespace(pluginName string) string

ScriptNamespace maps a plugin folder name to the scriptling module identifier its main.py is registered under (plugin.<ScriptNamespace>). Folder names may contain "-" (a legal plugin name) but module names may not, so hyphens become underscores; already-valid names are unchanged. Deterministic, so every node computes the same import name.

func SetRegistry

func SetRegistry(r *Registry)

SetRegistry replaces the global registry (tests; Load is the normal path).

func ValidHandlerName

func ValidHandlerName(name string) bool

ValidHandlerName reports whether name is a legal handler reference (a function name or module.function path) — the same shape [tool.knot] pages and field_handlers declare, and what handler-URL dispatch accepts.

Types

type FailedPlugin

type FailedPlugin struct {
	Name   string `json:"name"`
	Path   string `json:"path"`
	Reason string `json:"reason"`
}

FailedPlugin records a plugin whose folder/file was present but which failed validation or requirements; its data (when storage exists) is retained and it is surfaced on the admin plugins page.

type FieldHandler

type FieldHandler struct {
	Id      string // qualified: plugin.<name>.<id>
	Label   string
	Handler string // function in the entry file
	// Permission is an optional additive gate on the options endpoint:
	// UseSpaces (space forms drive the fetches) always applies, and a
	// declared gate narrows who may invoke the handler further.
	Permission string `json:"permission,omitempty"` // qualified grant
}

Page is one declared page: an internal URL under /plugins/<name> whose handler runs per-request as the requesting user. Handler is a function in the entry file, or "module.fn" for a sibling module of a folder plugin. FieldHandler is a declared data source a template's custom fields can bind to: the space form turns the field into an autocompleter whose suggestions come from calling Handler with {_data: id, query: ...}.

type Handler

type Handler struct {
	Handler    string `json:"handler"`              // function name or module.function
	Permission string `json:"permission,omitempty"` // qualified grant, as Page; empty: any logged-in user
}

Handler is a declared handler gate: one [[tool.knot.handlers]] entry. The declared permission is the handler's gate wherever it is called — same semantics as row/column gates — overriding the calling page's. A declaration is also the opt-in for plugin-root addressability (/plugins/<name>/<handler>): undeclared handlers inherit the calling page's gate and are only reachable through a page path.

type MCPTool

type MCPTool struct {
	Name        string             `json:"name"`                 // MCP tool name; defaults to the handler name
	Description string             `json:"description"`          // shown to MCP clients
	Handler     string             `json:"handler"`              // function in the entry file
	Permission  string             `json:"permission,omitempty"` // qualified grant, as Handler
	Parameters  []MCPToolParameter `json:"parameters,omitempty"` // optional: the tool's input schema
}

MCPTool is one [[tool.knot.mcp_tools]] entry: a plugin handler exposed as an MCP tool. The optional permission narrows which users see and may call the tool (knot enforces at both list and execute time); both empty means any MCP user. No input schema is declared — the tool's parameters arrive in the handler's params dict and the MCP schema is an empty object.

type MCPToolParameter

type MCPToolParameter struct {
	Name        string `json:"name"`
	Type        string `json:"type"` // string, int, float, bool, list
	Description string `json:"description,omitempty"`
	Default     any    `json:"default,omitempty"`
	Required    bool   `json:"required,omitempty"`
}

MCPToolParameter is one declared tool parameter: what MCP clients (and LLMs) see in the tool's input schema. Parameters still arrive in the handler's params dict untyped — the declaration is documentation and client ergonomics, not coercion.

type Menu struct {
	PluginName string `json:"plugin,omitempty"`
	Label      string `json:"label"`
	URL        string `json:"url"`
	Permission string `json:"permission,omitempty"`
	Icon       string `json:"icon,omitempty"`
	IconSVG    string `json:"-"`
}

Menu is one declared sidebar entry. Permission, when set, is the fully qualified grant (plugin.<name>.<id>) that gates the item. Icon is a relative path to an SVG asset in the plugin folder; IconSVG holds its sanitized inner markup, rendered inline with the site's icon styling so a currentColor-stroked SVG themes like every built-in icon.

type Page

type Page struct {
	PluginName string `json:"plugin,omitempty"`
	Path       string `json:"path"`                 // "/dashboard" — served at /plugins/<name>/dashboard
	Handler    string `json:"handler"`              // function in the entry file ("module.fn" allowed)
	Label      string `json:"label,omitempty"`      // page title
	MenuLabel  string `json:"menu_label,omitempty"` // set: the page also appears in the sidebar under this label
	Permission string `json:"permission,omitempty"` // qualified grant, as Menu
	Icon       string `json:"icon,omitempty"`
	IconSVG    string `json:"-"`
	// Default marks the page as the post-login landing page. At most one
	// takes effect (first plugin by name, then declaration order) — see
	// DefaultPageURL.
	Default bool `json:"default,omitempty"`
}

func (Page) URL

func (pg Page) URL() string

URL is the served location of the page.

type Peer

type Peer struct {
	Name    string `json:"name"`
	Version string `json:"version"`
	Healthy bool   `json:"healthy"`
	Error   string `json:"error,omitempty"`
}

Peer reports one loaded binary peer's handshake identity.

type PermissionDecl

type PermissionDecl struct {
	Id    string `json:"id"`    // fully qualified, e.g. plugin.metrics.read
	Label string `json:"label"` // derived display name, e.g. "read"
}

PermissionDecl is one declared permission, qualified to plugin.<name>.<id>.

type Plugin

type Plugin struct {
	Name        string `json:"name"`
	Version     string `json:"version"`
	Description string `json:"description"`
	// APIVersion is the plugin system generation this plugin targets
	// ([tool.knot] api; absent = 1, the only generation today). Breaking
	// changes to the plugin contract — the [tool.knot] shape, the handler
	// request/response contract, the block document — happen only behind a
	// new generation, and a knot that doesn't know a generation rejects the
	// plugin loudly at load rather than mis-parsing it.
	APIVersion int    `json:"api,omitempty"`
	Dir        string `json:"-"`                    // the plugin folder
	EntryFile  string `json:"-"`                    // main.py
	LogoLight  string `json:"logo_light,omitempty"` // relative paths
	LogoDark   string `json:"logo_dark,omitempty"`
	// SiteLogo reports that the plugin's logo pair claims the main page
	// logo (login included) — a declared pair is the claim. At most one
	// plugin's claim takes effect — see SiteLogoURLs.
	Permissions   []PermissionDecl `json:"permissions"`
	Menus         []Menu           `json:"menus"`
	Pages         []Page           `json:"pages"`
	Handlers      []Handler        `json:"handlers"`
	MCPTools      []MCPTool        `json:"mcp_tools"`
	FieldHandlers []FieldHandler   `json:"field_handlers"`
	Libs          []ScriptLib      `json:"libs,omitempty"`

	// ActionIcons maps declared icon asset paths to their sanitized inner
	// SVG markup ([tool.knot] icons). Menus and pages carry their icons
	// statically; row actions are data-driven, so their icons are declared
	// once here, sanitized at load like every other plugin asset, and
	// addressable by path from a handler's action JSON at runtime.
	ActionIcons map[string]string `json:"icons,omitempty"`

	// Assets holds the declared assets a peer served from its own fetcher
	// (an embedded-assets binary plugin), fetched once at load and served
	// from memory. Empty when every asset came from the plugin folder on
	// disk — those keep streaming from disk as before.
	Assets map[string][]byte `json:"-"`

	// RequiredConfig lists the config keys this plugin requires in the
	// server's [plugins.<name>] table, declared as [tool.knot] config — a
	// list of key names. Presence is validated at load: a missing key
	// fails the plugin with a reason on the admin page rather than a
	// handler failing at first use.
	RequiredConfig []string `json:"required_config,omitempty"`

	// Config is the plugin's [plugins.<name>] table from the server
	// configuration, attached at load and injected into every handler
	// request as request["config"] (an empty table when unconfigured).
	Config map[string]any `json:"-"`

	// Exports are the modules declared in [tool.knot] export: scriptling
	// modules materialized in user tool environments as
	// plugin.<name>.<stem> — client SDKs wrapping knot.plugin.call. The
	// first declared module is also aliased as plugin.<name> itself. Each
	// source is read and linted once at load.
	Exports []ScriptExport `json:"exports,omitempty"`

	// EntrySource is the entry file's source, read once at load so request
	// dispatch does not touch the filesystem. Empty for a peer plugin with
	// no main.py.
	EntrySource string `json:"-"`

	// ScriptNamespace is the plugin.<x> library name a pure-script plugin's
	// main.py is registered and addressed under: the folder name sanitized
	// to a scriptling identifier (ScriptNamespace). Empty for a peer plugin
	// with no main.py. This is the "<name>" a bare handler declaration
	// (handler = "status") resolves against.
	ScriptNamespace string `json:"-"`

	// Namespaces are the plugin.<x> library names this plugin's declared
	// handlers resolve under, in the order they must be imported to
	// materialize the plugin.<x> bindings for dispatch. A pure-script
	// plugin contributes its folder name (its main.py registers as
	// plugin.<folder>); a peer plugin contributes each peer's handshake
	// name (the peer registers plugin.<peer>). A plugin with both
	// contributes both.
	Namespaces []string `json:"-"`
	// contains filtered or unexported fields
}

Plugin is a successfully loaded plugin: its inert registration data.

func (*Plugin) DefaultNamespace

func (p *Plugin) DefaultNamespace() string

DefaultNamespace is the plugin.<ns> a bare handler declaration resolves against: the ScriptNamespace when the plugin has a main.py (its handlers live there), otherwise its first peer namespace (a peer plugin's handlers are the peer's exports). Empty only for a plugin with neither, which cannot declare a callable handler. A manifest may still name a specific peer explicitly as plugin.<peer>.<fn> to disambiguate multiple peers.

func (*Plugin) HandlerDecl

func (p *Plugin) HandlerDecl(name string) *Handler

HandlerDecl returns the declared gate for a handler name, or nil when the handler has no [[tool.knot.handlers]] entry.

func (*Plugin) Scope

func (p *Plugin) Scope() *plugin.Manager

Scope returns the manager owning this plugin's binary peers (which the handler environment registers as plugin.* libraries), or nil.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry is the in-memory result of one boot-time scan. Replace-on-boot, never mutated after Load returns.

func GetRegistry

func GetRegistry() *Registry

GetRegistry returns the boot-time registry, or nil when plugins are not configured / not yet loaded.

func Load

func Load(pluginsPath string) (*Registry, error)

Load scans the plugins path and builds the global registry. PluginsPath empty or missing disables plugins (nil registry, no error). Failures are per-plugin: a broken plugin is recorded and the server continues.

func LoadWithConfigs

func LoadWithConfigs(pluginsPath string, configs map[string]map[string]any) (*Registry, error)

LoadWithConfigs is Load with per-plugin configuration: each plugin's [plugins.<name>] table from the server config, attached to the plugin and injected into every handler request as request["config"]. Validation happens at load so a misconfigured server fails the plugin with a reason on the admin page: a table over the size cap, or a key the plugin declared in [tool.knot] config missing. A section naming no loaded plugin is reported as a warning — stale config left behind by a removed plugin.

func (*Registry) All

func (r *Registry) All() []*Plugin

All returns the successfully loaded plugins in name order.

func (*Registry) ByName

func (r *Registry) ByName(pluginName string) *Plugin

ByName returns the loaded plugin with that name, or nil.

func (*Registry) Close

func (r *Registry) Close()

Close shuts down every spawned peer process. Called on server shutdown.

func (*Registry) DefaultPageURL

func (r *Registry) DefaultPageURL() string

DefaultPageURL returns the URL of the page claimed as the post-login landing page, or "" when no plugin claims one. Plugins are name-sorted, so the first claimant by name wins deterministically on every node.

func (*Registry) Failed

func (r *Registry) Failed() []FailedPlugin

Failed returns the plugins that were present but failed to load.

func (*Registry) FieldHandler

func (r *Registry) FieldHandler(id string) (*Plugin, *FieldHandler)

FieldHandler looks a field handler up by its qualified id.

func (*Registry) FieldHandlers

func (r *Registry) FieldHandlers() []FieldHandler

FieldHandlers lists every declared field handler across plugins.

func (*Registry) Page

func (r *Registry) Page(pluginName, pagePath string) (*Plugin, *Page)

Page finds a loaded plugin and its declared page by URL path (the path after /plugins/<name>, e.g. "/dashboard"). Returns nils when unknown.

func (*Registry) Peers

func (r *Registry) Peers(p *Plugin) []Peer

Peers reports the binary peers loaded for a plugin, with health.

func (*Registry) Present

func (r *Registry) Present() bool

Present reports whether the plugins path produced anything at all — loaded plugins, failed plugins, or load warnings. When false, the plugin surface is hidden entirely: no nav item, no admin page, no search entry.

func (*Registry) SiteLogoURLs

func (r *Registry) SiteLogoURLs() (light, dark string)

SiteLogoURLs returns the served URLs of the plugin logos that replace the main page logo, or empty strings when no plugin declares a pair. All() is name-sorted, so the alphabetically first claim wins deterministically — every node scanning the same folder computes the same winner. An explicit server.ui.logo_url config beats any plugin (the caller applies that).

func (*Registry) VisibleMenuURLs

func (r *Registry) VisibleMenuURLs(user *model.User) map[string]bool

VisibleMenuURLs is the set of URLs VisibleMenus yields, for membership checks (pin validation).

func (*Registry) VisibleMenus

func (r *Registry) VisibleMenus(user *model.User) []Menu

VisibleMenus returns the menu items the given user may see, gated per item exactly as the sidebar renders them: a declared permission requires one of the user's roles to carry the qualified plugin grant; a group requires membership. This is the single gate — the nav builder, the nav-preferences validation, and search all ask the same question, so "pinnable" and "visible" can never drift apart.

func (*Registry) Warnings

func (r *Registry) Warnings() []string

Warnings returns non-fatal load warnings (ignored files, unloadable binaries that no plugin declared a requirement on, …).

type ScriptExport

type ScriptExport struct {
	Path   string `json:"path"`
	Source string `json:"-"`
}

ScriptExport is one module declared in [tool.knot] export: its path as declared and its source, read and linted once at plugin load.

type ScriptLib

type ScriptLib struct {
	Name    string `json:"name"`    // from the filename, e.g. libs/calc.py -> "calc"
	Version string `json:"version"` // [tool.knot.lib] version, default "1.0"
	Source  string `json:"-"`
}

ScriptLib is a scriptling-authored library: a .py file in the plugin's libs/ folder, loaded in-process by knot's embedded scriptling (no CLI, no subprocess). Its public surface — functions, classes and constants not prefixed with "_" — becomes the plugin.<name> import in handler environments and user-created tools. Version comes from the optional [tool.knot.lib] table in the library's metadata block, and the consuming plugin's dependency declaration ("plugin.<lib> via <lib> >= x") validates against it the same way Go peer handshakes do.

Jump to

Keyboard shortcuts

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