Some checks failed
PR Check / check (pull_request) Failing after 11s
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>
117 lines
2.4 KiB
Go
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
|
|
}
|