First step of the model-agnostic image-generation framework. Lands the
plumbing other components (skill, ComfyUI/Replicate adapters, agents)
will plug into:
- internal/backend: Backend interface (Request/Result), thread-safe
Registry with init-time Register, plus a Mock reference adapter that
emits a deterministic gradient PNG for smoke tests.
- internal/config: YAML loader for ~/.config/imagen.yaml. Framework owns
default_backend + output settings + a per-backend block; each adapter
owns the schema below its own block via BackendSpec.Raw.
- internal/output: filename templating ({date}/{time}/{slug}/{seed}/
{backend}/{ext}), JSON metadata sidecar, --output override path.
- internal/prompt: embedded styles.yaml, style-preset suffix application.
- internal/server: 501 stub — HTTP surface lands in a follow-up issue.
- cmd/imagen: generate / backends / config (init|validate|path) / serve
/ version subcommands. Stdlib-only flag parsing with a small helper to
honour positional prompt args ahead of flags (matches the issue spec).
- Tests for output (slug, naming template, sidecar), backend (mock PNG
validity + determinism, registry build + duplicate panic), config
(round-trip + validation), prompt (style apply + unknown-style error).
- CLAUDE.md, README.md, docs/architecture.md, docs/usage.md, Makefile.
Acceptance criteria from #211:
1. go build ./... — clean
2. imagen backends — lists registered backends, exits 0
3. imagen generate "test prompt" --backend mock --output /tmp/x.png —
writes a 1024x1024 PNG plus an x.png.json sidecar
4. imagen config init | imagen config validate — round-trips cleanly
5. CLAUDE.md "Adding a new adapter" — six-step recipe
76 lines
2.1 KiB
Go
76 lines
2.1 KiB
Go
package backend
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"sync"
|
|
)
|
|
|
|
// Constructor builds a Backend from a name and its sub-block of raw config.
|
|
// The framework hands the adapter only its own slice of imagen.yaml, so the
|
|
// adapter owns its schema completely.
|
|
type Constructor func(name string, cfg map[string]any) (Backend, error)
|
|
|
|
// Registry holds the name → Constructor table. Adapters call Register from
|
|
// their package init() to make themselves available to the CLI.
|
|
type Registry struct {
|
|
mu sync.RWMutex
|
|
ctors map[string]Constructor
|
|
}
|
|
|
|
// NewRegistry returns an empty registry.
|
|
func NewRegistry() *Registry {
|
|
return &Registry{ctors: make(map[string]Constructor)}
|
|
}
|
|
|
|
// Register adds a constructor under typeName (e.g. "comfyui", "mock").
|
|
// Re-registering an existing type panics — names are global per binary.
|
|
func (r *Registry) Register(typeName string, ctor Constructor) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if _, exists := r.ctors[typeName]; exists {
|
|
panic(fmt.Sprintf("backend type %q already registered", typeName))
|
|
}
|
|
r.ctors[typeName] = ctor
|
|
}
|
|
|
|
// Build instantiates a backend of typeName using cfg. instanceName is the
|
|
// user-facing name from imagen.yaml (e.g. "flux-schnell-local").
|
|
func (r *Registry) Build(typeName, instanceName string, cfg map[string]any) (Backend, error) {
|
|
r.mu.RLock()
|
|
ctor, ok := r.ctors[typeName]
|
|
r.mu.RUnlock()
|
|
if !ok {
|
|
return nil, fmt.Errorf("backend type %q not registered, available: %v", typeName, r.Types())
|
|
}
|
|
return ctor(instanceName, cfg)
|
|
}
|
|
|
|
// Types returns the registered backend type names, sorted.
|
|
func (r *Registry) Types() []string {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
out := make([]string, 0, len(r.ctors))
|
|
for k := range r.ctors {
|
|
out = append(out, k)
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|
|
|
|
// Has reports whether typeName is registered.
|
|
func (r *Registry) Has(typeName string) bool {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
_, ok := r.ctors[typeName]
|
|
return ok
|
|
}
|
|
|
|
// Default is the process-wide registry adapters register against.
|
|
var Default = NewRegistry()
|
|
|
|
// Register is shorthand for Default.Register.
|
|
func Register(typeName string, ctor Constructor) {
|
|
Default.Register(typeName, ctor)
|
|
}
|