go/pkg/plugin/registry.go
Claude 52d358daa2 refactor: rename module from github.com/host-uk/core to forge.lthn.ai/core/cli
Move module identity to our own Forgejo instance. All import paths
updated across 434 Go files, sub-module go.mod files, and go.work.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 05:53:52 +00:00

117 lines
3 KiB
Go

package plugin
import (
"encoding/json"
"path/filepath"
"sort"
core "forge.lthn.ai/core/cli/pkg/framework/core"
"forge.lthn.ai/core/cli/pkg/io"
)
const registryFilename = "registry.json"
// Registry manages installed plugins.
// Plugin metadata is stored in a registry.json file under the base path.
type Registry struct {
medium io.Medium
basePath string // e.g., ~/.core/plugins/
plugins map[string]*PluginConfig
}
// NewRegistry creates a new plugin registry.
func NewRegistry(m io.Medium, basePath string) *Registry {
return &Registry{
medium: m,
basePath: basePath,
plugins: make(map[string]*PluginConfig),
}
}
// List returns all installed plugins sorted by name.
func (r *Registry) List() []*PluginConfig {
result := make([]*PluginConfig, 0, len(r.plugins))
for _, cfg := range r.plugins {
result = append(result, cfg)
}
sort.Slice(result, func(i, j int) bool {
return result[i].Name < result[j].Name
})
return result
}
// Get returns a plugin by name.
// The second return value indicates whether the plugin was found.
func (r *Registry) Get(name string) (*PluginConfig, bool) {
cfg, ok := r.plugins[name]
return cfg, ok
}
// Add registers a plugin in the registry.
func (r *Registry) Add(cfg *PluginConfig) error {
if cfg.Name == "" {
return core.E("plugin.Registry.Add", "plugin name is required", nil)
}
r.plugins[cfg.Name] = cfg
return nil
}
// Remove unregisters a plugin from the registry.
func (r *Registry) Remove(name string) error {
if _, ok := r.plugins[name]; !ok {
return core.E("plugin.Registry.Remove", "plugin not found: "+name, nil)
}
delete(r.plugins, name)
return nil
}
// registryPath returns the full path to the registry file.
func (r *Registry) registryPath() string {
return filepath.Join(r.basePath, registryFilename)
}
// Load reads the plugin registry from disk.
// If the registry file does not exist, the registry starts empty.
func (r *Registry) Load() error {
path := r.registryPath()
if !r.medium.IsFile(path) {
// No registry file yet; start with empty registry
r.plugins = make(map[string]*PluginConfig)
return nil
}
content, err := r.medium.Read(path)
if err != nil {
return core.E("plugin.Registry.Load", "failed to read registry", err)
}
var plugins map[string]*PluginConfig
if err := json.Unmarshal([]byte(content), &plugins); err != nil {
return core.E("plugin.Registry.Load", "failed to parse registry", err)
}
if plugins == nil {
plugins = make(map[string]*PluginConfig)
}
r.plugins = plugins
return nil
}
// Save writes the plugin registry to disk.
func (r *Registry) Save() error {
if err := r.medium.EnsureDir(r.basePath); err != nil {
return core.E("plugin.Registry.Save", "failed to create plugin directory", err)
}
data, err := json.MarshalIndent(r.plugins, "", " ")
if err != nil {
return core.E("plugin.Registry.Save", "failed to marshal registry", err)
}
if err := r.medium.Write(r.registryPath(), string(data)); err != nil {
return core.E("plugin.Registry.Save", "failed to write registry", err)
}
return nil
}