verstak-desktop/main.go

175 lines
5.7 KiB
Go

package main
import (
"embed"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
"github.com/verstak/verstak-desktop/internal/api"
"github.com/verstak/verstak-desktop/internal/core/capability"
"github.com/verstak/verstak-desktop/internal/core/contribution"
"github.com/verstak/verstak-desktop/internal/core/events"
"github.com/verstak/verstak-desktop/internal/core/permissions"
"github.com/verstak/verstak-desktop/internal/core/plugin"
"github.com/verstak/verstak-desktop/internal/core/vault"
)
//go:embed frontend/dist
var assets embed.FS
// expandPath resolves "~" to the user's home directory.
func expandPath(path string) string {
if strings.HasPrefix(path, "~/") {
home, err := os.UserHomeDir()
if err != nil {
log.Printf("[main] expandPath: cannot get home dir: %v", err)
return path
}
return filepath.Join(home, path[2:])
}
return path
}
func main() {
// ─── Initialize Core Registries ──────────────────────────
capRegistry := capability.NewRegistry()
contribRegistry := contribution.NewRegistry()
permRegistry := permissions.NewRegistry()
eventBus := events.NewBus()
// ─── Initialize Vault ────────────────────────────────────
vaultService := vault.NewVault(eventBus)
// ─── Register Core Capabilities ─────────────────────────
// These are provided by the desktop core itself, not by plugins.
// Registered before plugin discovery so that plugins can resolve
// required capabilities (e.g. verstak/core/plugin-manager/v1) at load time.
corePluginID := "verstak-desktop"
coreCaps := []string{
"verstak/core/plugin-manager/v1",
"verstak/core/capability-registry/v1",
"verstak/core/contribution-registry/v1",
"verstak/core/permissions/v1",
"verstak/core/events/v1",
}
if err := capRegistry.Register(corePluginID, coreCaps); err != nil {
log.Fatalf("[main] failed to register core capabilities: %v", err)
}
log.Printf("[main] registered %d core capabilities", len(coreCaps))
// Register vault capability (vault is available as a core service)
if err := capRegistry.Register(corePluginID, []string{"verstak/core/vault/v1"}); err != nil {
log.Fatalf("[main] failed to register vault capability: %v", err)
}
log.Printf("[main] registered vault capability")
// ─── Plugin Discovery ───────────────────────────────────
discoveryDirs := []string{
"~/.config/verstak/plugins",
"./plugins",
}
// Expand tilde in all paths
for i, d := range discoveryDirs {
discoveryDirs[i] = expandPath(d)
}
log.Printf("[main] plugin dirs: %v", discoveryDirs)
plugins, discErrors := plugin.DiscoverPlugins(discoveryDirs)
for _, err := range discErrors {
log.Printf("[plugin] discovery warning: %v", err)
}
log.Printf("[plugin] discovered %d plugins", len(plugins))
// ─── Plugin Lifecycle: Register Capabilities + Contributions ──
for i := range plugins {
p := &plugins[i]
// Register provided capabilities
if len(p.Manifest.Provides) > 0 {
if err := capRegistry.Register(p.Manifest.ID, p.Manifest.Provides); err != nil {
log.Printf("[plugin] %s: capability registration failed: %v", p.Manifest.ID, err)
p.Status = plugin.StatusFailed
p.Error = err.Error()
continue
}
log.Printf("[plugin] %s: registered %d capabilities", p.Manifest.ID, len(p.Manifest.Provides))
}
// Resolve required capabilities
missingRequired := capRegistry.CheckRequired(p.Manifest.Requires)
if len(missingRequired) > 0 {
log.Printf("[plugin] %s: missing required capabilities: %v", p.Manifest.ID, missingRequired)
p.Status = plugin.StatusMissingRequiredCapability
p.Error = fmt.Sprintf("missing required: %s", strings.Join(missingRequired, ", "))
continue
}
// Check optional capabilities for degraded mode
missingOptional := capRegistry.CheckRequired(p.Manifest.OptionalRequires)
if len(missingOptional) > 0 {
log.Printf("[plugin] %s: missing optional capabilities (degraded): %v", p.Manifest.ID, missingOptional)
p.Status = plugin.StatusDegraded
} else {
p.Status = plugin.StatusLoaded
}
// Register contributions
if p.Manifest.Contributes != nil {
contribRegistry.Register(p.Manifest.ID, p.Manifest.Contributes)
log.Printf("[plugin] %s: contributions registered", p.Manifest.ID)
}
log.Printf("[plugin] %s: status=%s", p.Manifest.ID, p.Status)
}
// ─── Log Summary ───────────────────────────────────────
loaded := 0
degraded := 0
failed := 0
for _, p := range plugins {
switch p.Status {
case plugin.StatusLoaded:
loaded++
case plugin.StatusDegraded:
degraded++
default:
failed++
}
}
log.Printf("[main] lifecycle summary: loaded=%d degraded=%d failed=%d vault=%s",
loaded, degraded, failed, vaultService.GetVaultStatus())
// Create the App struct
app := api.NewApp(capRegistry, contribRegistry, permRegistry, eventBus, plugins, vaultService)
// ─── Wails App ───────────────────────────────────────────
err := wails.Run(&options.App{
Title: "Verstak",
Width: 1200,
Height: 800,
MinWidth: 800,
MinHeight: 600,
WindowStartState: options.Normal,
AssetServer: &assetserver.Options{
Assets: assets,
},
Bind: []interface{}{
app,
},
})
if err != nil {
log.Fatalf("Error: %v", err)
}
}