plugin

package
v0.39.6 Latest Latest
Warning

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

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

Documentation

Overview

Package plugin is the host side of the connector plugin platform: it spawns connector subprocesses on demand, hands out live gRPC clients, and reaps idle ones.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BuildModule

func BuildModule(mod connector.Module, getConn ConnGetter) connector.Module

BuildModule wires a parsed connector.Module's operations to gRPC closures that dispatch to the plugin subprocess. The host engine (service.Execute) calls these closures exactly like in-proc ops — same pattern as custom-MCP. The envelope parsing and verification happen in the loader before this is called.

func DefaultDir

func DefaultDir() string

DefaultDir is the runtime location wick scans for installed connector plugins: <appDataDir>/plugins/connectors, overridable with WICK_PLUGINS_DIR. It matches the layout `make plugins` writes to.

func ExtractArchive

func ExtractArchive(archive, destBase string) (string, error)

ExtractArchive extracts a .tar.gz or .zip into destBase and returns the directory that holds plugin.json (the archive root or its single subdir).

func InstallFromDir

func InstallFromDir(srcDir, destRoot string) error

InstallFromDir verifies the {binary, plugin.json} in srcDir and copies them into destRoot/<key>/. The manifest's sha256 + signature are checked against the binary BEFORE anything is written, so an unverified plugin never lands in the plugins dir.

func InstallFromURL

func InstallFromURL(ctx context.Context, url, destRoot string) error

InstallFromURL downloads an archive (zip or tar.gz) from url, extracts it, verifies the manifest, and installs into destRoot. Used by the marketplace install action and `<app> plugin install <url|name>`.

func InstallFromURLProgress added in v0.28.5

func InstallFromURLProgress(ctx context.Context, url, destRoot string, onProgress ProgressFunc) error

InstallFromURLProgress is InstallFromURL with staged progress reporting. The archive lands in a temp dir first; the on-disk plugin is only touched in the Replacing phase, so a failed download/verify never disturbs the existing install (download-succeeds-then-replace).

func ManifestFromZipBytes added in v0.27.0

func ManifestFromZipBytes(data []byte) (*wickplugin.Manifest, error)

ManifestFromZipBytes reads and parses plugin.json from a plugin release zip's raw bytes. Shared by the live fetcher and by tests (which build zips in-mem).

func MarshalCatalog added in v0.27.0

func MarshalCatalog(entries []Available) ([]byte, error)

MarshalCatalog renders the catalog as the pretty JSON written to plugins/plugins.json (2-space indent, trailing newline) for a stable diff.

func ResolveSource

func ResolveSource(ctx context.Context, src string) (string, func(), error)

ResolveSource turns a path / url / archive into a directory containing {binary, plugin.json}, returning the dir and a cleanup func. A bare existing directory is returned as-is.

func RunDir

func RunDir() string

RunDir is where wick pins plugin Unix sockets: <appDataDir>/run, overridable with WICK_PLUGIN_SOCKET_DIR. go-plugin creates the socket under here (0700) instead of the OS temp dir.

func VersionNewer added in v0.27.2

func VersionNewer(a, b string) bool

VersionNewer reports whether catalog version a is strictly newer than the installed version b, by semver. Versions may be bare ("1.4.2") or "v"-prefixed; both are normalized. Unparseable versions fall back to a plain string != comparison so a malformed tag still surfaces *some* update hint rather than silently hiding one.

Types

type Available

type Available struct {
	Key         string `json:"key"`
	Name        string `json:"name"`
	Description string `json:"description"`
	Version     string `json:"version"`
	// DefaultTags are the plugin's Meta.DefaultTags, carried verbatim from the
	// released manifest — the SAME []entity.DefaultTag a built-in connector
	// declares. The app derives the category from them with connectorCategory,
	// exactly as for built-ins, so a plugin groups under its real category.
	DefaultTags []entity.DefaultTag `json:"default_tags,omitempty"`
	// Assets maps "<goos>/<goarch>" → direct download URL of the release zip.
	Assets map[string]string `json:"assets"`
}

Available is one installable connector surfaced by the catalog.

Key vs Name mirrors connector.Meta: Key is the slug (= source folder = zip name = install dir = registry key — the one identity used for matching and install); Name is the free display string shown in the UI. Older catalog entries that only have "name" are tolerated: parseCatalog backfills Key from Name when Key is absent.

func BuildCatalog added in v0.27.0

func BuildCatalog(releases []GHRelease, fetchManifest ManifestFetcher) []Available

BuildCatalog folds releases into one Available entry per plugin key (the tag prefix before "/v"), keeping the highest semver version, and maps each zip asset to its os/arch download URL. When fetchManifest is non-nil it is used to lift Meta.Name / Meta.Description from the chosen release's first asset; failures there are non-fatal (the entry keeps key-derived defaults).

Tag convention: plugin releases are tagged "<key>/v<version>"; core wick releases ("v<version>", no slash) are ignored.

func (Available) AssetFor

func (a Available) AssetFor(host string) string

AssetFor returns the download URL matching host (e.g. "linux/arm64"), or "".

type Catalog

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

Catalog discovers connector plugins that are AVAILABLE to install from a single curated JSON file checked into the wick repo's default branch (raw.githubusercontent.com/yogasw/wick/master/plugins/plugins.json).

This is deliberately NOT the GitHub Releases API: listing is a plain raw-file fetch, so it never hits the API rate limit and needs no token. The JSON carries, per plugin, the direct release download URL for each os/arch — the binary is pulled from the GitHub Release only when the user clicks Download.

func DefaultRegistry

func DefaultRegistry() *Catalog

DefaultRegistry builds a Catalog from env overrides:

WICK_PLUGIN_CATALOG  full URL to plugins.json (default: wick repo master, plugins/plugins.json)

Named DefaultRegistry for call-site compatibility with the earlier API.

func (*Catalog) List

func (c *Catalog) List(ctx context.Context) ([]Available, error)

List returns every installable connector from the catalog JSON. Served from cache while the TTL is fresh; otherwise a conditional GET (ETag) reuses the cache on a 304.

func (*Catalog) Resolve

func (c *Catalog) Resolve(ctx context.Context, name, host string) (Available, string, error)

Resolve returns the arch-matching download URL for the named connector. host defaults to the current runtime when empty.

type ConnGetter

type ConnGetter func(key string) (*Lease, error)

ConnGetter returns a lease on a live plugin connection for a connector key. The manager's Client method satisfies it; tests pass a fake.

type Found

type Found struct {
	Key        string
	BinaryPath string
	Manifest   wickplugin.Manifest
}

Found is one discovered plugin: its key, on-disk binary, and parsed manifest envelope.

func Scan

func Scan(dir string) ([]Found, error)

Scan walks dir/<name>/plugin.json and returns one Found per connector. Each plugin.json is a manifest envelope; the binary is resolved from the manifest's Entry (falling back to the directory name). A missing dir is not an error (returns nil) — plugins are optional.

type GHAsset added in v0.27.0

type GHAsset struct {
	Name        string `json:"name"`
	DownloadURL string `json:"browser_download_url"`
}

GHAsset is one downloadable file on a release.

type GHRelease added in v0.27.0

type GHRelease struct {
	TagName string    `json:"tag_name"`
	Assets  []GHAsset `json:"assets"`
}

GHRelease is the slice of a GitHub release this needs. Decode the `GET /repos/{owner}/{repo}/releases` response into []GHRelease.

type Lease

type Lease struct {
	Conn wickplugin.GRPCConn
	// contains filtered or unexported fields
}

Lease is a borrowed plugin connection. Release MUST be called exactly once (the adapter does so via defer) so the Manager can account for in-flight calls and free the subprocess for eviction.

func (*Lease) Release

func (l *Lease) Release()

Release returns the lease to the Manager. Safe to call on a nil-release lease.

type Manager

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

Manager owns connector plugin subprocesses keyed by connector Meta.Key.

func Load

func Load(dir string, idleTimeout time.Duration, enabled func(string) bool) (*Manager, int, error)

Load scans dir, builds a Manager over the discovered binaries, registers each plugin module via connectors.Register (replace-by-key so a plugin overrides the compiled-in builtin of the same key), and returns the Manager (caller owns its KillAll on shutdown). Returns a nil Manager when no plugins are present. When enabled is non-nil, keys for which it returns false are excluded from the Manager (not spawnable) and not registered; a nil enabled treats all discovered plugins as enabled.

func NewManager

func NewManager(binaries map[string]string, idleTimeout time.Duration) *Manager

NewManager builds a Manager and starts the idle sweeper.

func (*Manager) Client

func (m *Manager) Client(key string) (*Lease, error)

Client returns a lease on a live gRPC connection for key, spawning the subprocess on first use (lazy) and re-spawning if a previous process died. Release the lease when the call completes.

func (*Manager) IsPlugin

func (m *Manager) IsPlugin(key string) bool

IsPlugin reports whether key is served by a plugin subprocess.

func (*Manager) KillAll

func (m *Manager) KillAll()

KillAll reaps every subprocess (call on app shutdown). It is safe to call more than once: the stop channel is closed at most once.

func (*Manager) RemoveBinary

func (m *Manager) RemoveBinary(key string)

RemoveBinary drops a connector key and kills its running subprocess (if any).

func (*Manager) ResolveIdentity

func (m *Manager) ResolveIdentity(ctx context.Context, key, token string) (string, string, error)

ResolveIdentity spawns-if-needed and asks the plugin to resolve an OAuth token's owner.

func (*Manager) SetBinary

func (m *Manager) SetBinary(key, path string)

SetBinary registers or updates the on-disk binary path for a connector key. If a subprocess is already running for this key it is killed so the next Client call spawns the new binary.

func (*Manager) WarmUp

func (m *Manager) WarmUp()

WarmUp eagerly spawns every warm connector that has a registered binary so it is hot before the first call. Failures are logged and skipped — boot must not abort. Call once at boot after Load wires the binaries.

type ManifestFetcher added in v0.27.0

type ManifestFetcher func(zipURL string) (*wickplugin.Manifest, error)

ManifestFetcher reads a plugin.json out of a release zip given its download URL, returning the parsed manifest. Returns an error the caller may ignore (Name/Description backfill is best-effort). Injected so BuildCatalog stays testable without network.

type Phase added in v0.28.5

type Phase string

Phase names an install step, streamed to the caller so the UI can show a staged progress indicator instead of one opaque spinner.

const (
	PhaseDownloading Phase = "downloading"
	PhaseVerifying   Phase = "verifying"
	PhaseReplacing   Phase = "replacing"
	PhaseDone        Phase = "done"
)

type Progress added in v0.28.5

type Progress struct {
	Phase Phase `json:"phase"`
	Pct   int   `json:"pct"`
}

Progress is one install-progress update. Pct is the download percentage (0–100) and is only meaningful while Phase==PhaseDownloading and the server sent a Content-Length; it is -1 when the total size is unknown.

type ProgressFunc added in v0.28.5

type ProgressFunc func(Progress)

ProgressFunc receives install-progress updates. nil is allowed (no-op).

type Reloader

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

Reloader watches the plugins dir and reconciles installed plugins into the running service without a restart. It polls on a fixed interval (no fsnotify dependency).

func NewReloader

func NewReloader(dir string, svc moduleSink, mgr *Manager, interval time.Duration, store enabledChecker) *Reloader

NewReloader builds a Reloader. interval <= 0 defaults to 5s.

func (*Reloader) Reload

func (r *Reloader) Reload(ctx context.Context)

Reload triggers an immediate reconcile (for in-process callers, e.g. CLI install).

func (*Reloader) Start

func (r *Reloader) Start(ctx context.Context)

Start runs the poll loop until Stop is called or ctx is cancelled. Call once.

func (*Reloader) Stop

func (r *Reloader) Stop()

Stop ends the poll loop.

type StateStore

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

StateStore reads and writes the plugin enable/disable overlay.

func NewStateStore

func NewStateStore(db *gorm.DB) *StateStore

NewStateStore wraps db. A nil db yields a store whose Enabled defaults to true.

func (*StateStore) Enabled

func (s *StateStore) Enabled(key string) bool

Enabled reports whether key may be registered/spawned. Missing row or any error -> true (default-on; never hide a plugin because of a read error).

func (*StateStore) List

func (s *StateStore) List() (map[string]bool, error)

List returns key -> enabled for all overlay rows.

func (*StateStore) SetEnabled

func (s *StateStore) SetEnabled(key string, enabled bool) error

SetEnabled upserts the overlay row for key. A map is used so gorm writes the literal enabled value; a struct would let the `default:true` tag override a zero-value false on insert.

Jump to

Keyboard shortcuts

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