// Package main provides the LEM Desktop application. // A system tray app inspired by BugSETI that bundles: // - Local Forgejo for agentic git workflows // - InfluxDB for metrics and coordination // - Inference proxy to M3 MLX or local vLLM // - Scoring agent for automated checkpoint evaluation // - Lab dashboard for training and generation monitoring // // Built on Wails v3 — ships as a signed native binary on macOS (Lethean CIC), // Linux AppImage, and Windows installer. package main import ( "embed" "io/fs" "log" "net/http" "os" "path/filepath" "strings" "forge.lthn.ai/lthn/lem/cmd/lem-desktop/icons" "github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/events" ) //go:embed all:frontend var assets embed.FS // Tray icon data — placeholders until real icons are generated. var ( trayIconTemplate = icons.Placeholder() trayIconLight = icons.Placeholder() trayIconDark = icons.Placeholder() ) func main() { // Strip embed prefix so files serve from root. staticAssets, err := fs.Sub(assets, "frontend") if err != nil { log.Fatal(err) } // ── Configuration ── influxURL := envOr("INFLUX_URL", "http://localhost:8181") influxDB := envOr("INFLUX_DB", "training") apiURL := envOr("LEM_API_URL", "http://localhost:8080") m3Host := envOr("M3_HOST", "10.69.69.108") baseModel := envOr("BASE_MODEL", "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B") dbPath := envOr("LEM_DB", "") workDir := envOr("WORK_DIR", filepath.Join(os.TempDir(), "scoring-agent")) deployDir := envOr("LEM_DEPLOY_DIR", findDeployDir()) // ── Services ── dashboardService := NewDashboardService(influxURL, influxDB, dbPath) dockerService := NewDockerService(deployDir) agentRunner := NewAgentRunner(apiURL, influxURL, influxDB, m3Host, baseModel, workDir) trayService := NewTrayService(nil) services := []application.Service{ application.NewService(dashboardService), application.NewService(dockerService), application.NewService(agentRunner), application.NewService(trayService), } // ── Application ── app := application.New(application.Options{ Name: "LEM", Description: "Lethean Ethics Model — Training, Scoring & Inference", Services: services, Assets: application.AssetOptions{ Handler: spaHandler(staticAssets), }, Mac: application.MacOptions{ ActivationPolicy: application.ActivationPolicyAccessory, }, }) // Wire up references. trayService.app = app trayService.SetServices(dashboardService, dockerService, agentRunner) // Set up system tray. setupSystemTray(app, trayService, dashboardService, dockerService) // Show dashboard on first launch. app.Event.RegisterApplicationEventHook(events.Common.ApplicationStarted, func(event *application.ApplicationEvent) { if w, ok := app.Window.Get("dashboard"); ok { w.Show() w.Focus() } }) log.Println("Starting LEM Desktop...") log.Println(" - System tray active") log.Println(" - Dashboard ready") log.Printf(" - InfluxDB: %s/%s", influxURL, influxDB) log.Printf(" - Inference: %s", apiURL) if err := app.Run(); err != nil { log.Fatal(err) } } // spaHandler serves static files with SPA fallback for client-side routing. func spaHandler(fsys fs.FS) http.Handler { fileServer := http.FileServer(http.FS(fsys)) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { path := strings.TrimPrefix(r.URL.Path, "/") if path == "" { path = "index.html" } if _, err := fs.Stat(fsys, path); err != nil { r.URL.Path = "/" } fileServer.ServeHTTP(w, r) }) } // findDeployDir locates the deploy/ directory relative to the binary. func findDeployDir() string { // Check relative to executable. exe, err := os.Executable() if err == nil { dir := filepath.Join(filepath.Dir(exe), "deploy") if _, err := os.Stat(filepath.Join(dir, "docker-compose.yml")); err == nil { return dir } } // Check relative to working directory. if cwd, err := os.Getwd(); err == nil { dir := filepath.Join(cwd, "deploy") if _, err := os.Stat(filepath.Join(dir, "docker-compose.yml")); err == nil { return dir } } return "deploy" } func envOr(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback }