Files
MuyueWorkspace/cmd/muyue-desktop/main.go
Augustin 6f143e5ecd
Some checks failed
PR Check / check (pull_request) Failing after 11s
feat: add desktop app with React frontend, API backend, theme system
New desktop application that launches a local HTTP server with embedded
React frontend. Opens in the user's browser automatically.

Architecture:
- internal/api/: REST API exposing all internal/ packages to frontend
- cmd/muyue-desktop/: entry point, serves embedded frontend + API
- cmd/muyue-desktop/frontend/: React + Vite SPA

Frontend features:
- 4 tabs: Dashboard, Studio, Shell, Config
- Cyberpunk red theme with CSS custom properties
- Theme system: 4 built-in themes (Cyberpunk Red, Pink, Midnight Blue, Matrix Green)
- Terminal with command execution via API
- Chat interface with sidebar (agents, workflows, commands)
- Live clock, status indicators, update badges
- Glitch/scanline/fade animations between tabs
- xterm.js included for future full terminal integration

Backend API endpoints:
- GET /api/info, /api/system, /api/tools, /api/config
- GET /api/providers, /api/skills, /api/lsp, /api/mcp, /api/updates
- POST /api/scan, /api/install, /api/terminal, /api/mcp/configure

Build: cd cmd/muyue-desktop/frontend && npm run build && go build ./cmd/muyue-desktop/
Binary: ~11MB single binary with embedded frontend

Generated with Crush

Assisted-by: GLM-5.1 via Crush <crush@charm.land>
2026-04-20 22:45:00 +02:00

117 lines
2.4 KiB
Go

package main
import (
"embed"
"fmt"
"io/fs"
"log"
"net"
"net/http"
"os"
"os/exec"
"os/signal"
"syscall"
"github.com/muyue/muyue/internal/api"
"github.com/muyue/muyue/internal/config"
"github.com/muyue/muyue/internal/version"
)
//go:embed frontend/dist/*
var frontendFS embed.FS
func main() {
if len(os.Args) > 1 && os.Args[1] == "--help" {
fmt.Printf("%s Desktop v%s\n", version.Name, version.Version)
fmt.Println("Usage: muyue-desktop [options]")
fmt.Println()
fmt.Println("Options:")
fmt.Println(" --help Show this help")
fmt.Println(" --port Specify port (default: auto)")
fmt.Println(" --no-open Don't open browser")
return
}
cfg := loadConfig()
srv := api.NewServer(cfg)
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
log.Fatalf("Failed to bind: %v", err)
}
addr := listener.Addr().(*net.TCPAddr)
port := addr.Port
frontendDist, err := fs.Sub(frontendFS, "frontend/dist")
if err != nil {
log.Fatalf("Failed to load frontend: %v", err)
}
fileServer := http.FileServer(http.FS(frontendDist))
mux := http.NewServeMux()
mux.Handle("/api/", srv)
mux.Handle("/", fileServer)
go func() {
log.Printf("%s Desktop v%s", version.Name, version.Version)
log.Printf("Listening on http://127.0.0.1:%d", port)
if err := http.Serve(listener, mux); err != nil {
log.Fatalf("Server error: %v", err)
}
}()
noOpen := false
for _, arg := range os.Args[1:] {
if arg == "--no-open" {
noOpen = true
}
}
if !noOpen {
url := fmt.Sprintf("http://127.0.0.1:%d", port)
openBrowser(url)
log.Printf("Opened %s in browser", url)
}
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down...")
}
func loadConfig() *config.MuyueConfig {
if !config.Exists() {
fmt.Println("No config found. Run `muyue setup` first.")
os.Exit(1)
}
cfg, err := config.Load()
if err != nil {
fmt.Fprintf(os.Stderr, "Config error: %v\n", err)
os.Exit(1)
}
return cfg
}
func openBrowser(url string) {
var cmd *exec.Cmd
switch {
case commandExists("xdg-open"):
cmd = exec.Command("xdg-open", url)
case commandExists("open"):
cmd = exec.Command("open", url)
case commandExists("cmd"):
cmd = exec.Command("cmd", "/c", "start", url)
default:
fmt.Printf("Open manually: %s\n", url)
return
}
cmd.Start()
}
func commandExists(name string) bool {
_, err := exec.LookPath(name)
return err == nil
}