go/pkg/plugin/registry.go
Snider adaa4131f9 refactor: strip to pure package library (#3)
- Fix remaining 187 pkg/ files referencing core/cli → core/go
- Move SDK library code from internal/cmd/sdk/ → pkg/sdk/ (new package)
- Create pkg/rag/helpers.go with convenience functions from internal/cmd/rag/
- Fix pkg/mcp/tools_rag.go to use pkg/rag instead of internal/cmd/rag
- Fix pkg/build/buildcmd/cmd_sdk.go and pkg/release/sdk.go to use pkg/sdk
- Remove all non-library content: main.go, internal/, cmd/, docker/,
  scripts/, tasks/, tools/, .core/, .forgejo/, .woodpecker/, Taskfile.yml
- Run go mod tidy to trim unused dependencies

core/go is now a pure Go package suite (library only).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Co-authored-by: Claude <developers@lethean.io>
Reviewed-on: #3
2026-02-16 14:23:45 +00:00

117 lines
3 KiB
Go

package plugin
import (
"encoding/json"
"path/filepath"
"sort"
core "forge.lthn.ai/core/go/pkg/framework/core"
"forge.lthn.ai/core/go/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
}