All checks were successful
Beta Release / beta (push) Successful in 37s
- Merge muyue + muyue-desktop into one binary (13MB) - `muyue` starts TUI, `muyue desktop` launches web UI in browser - Move frontend from cmd/muyue-desktop/frontend/ to web/ (standard Go layout) - Add web/embed.go with //go:embed all:dist for frontend assets - Add internal/desktop/ package (server, browser open, SPA routing, signals) - Split internal/api/api.go into server.go + handlers.go - Add internal/desktop/desktop.go with SPA fallback and --port/--no-open flags - Clean package.json: remove unused @xterm/xterm, switch to ESM - Fix vite.config.js proxy to use port 8095 for dev mode - Add Makefile targets: frontend, desktop, dev-desktop - Update all CI workflows: single binary build, web/ paths - Remove cmd/muyue-desktop/ entirely 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land>
216 lines
5.2 KiB
Go
216 lines
5.2 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"os/exec"
|
|
|
|
"github.com/muyue/muyue/internal/lsp"
|
|
"github.com/muyue/muyue/internal/mcp"
|
|
"github.com/muyue/muyue/internal/scanner"
|
|
"github.com/muyue/muyue/internal/skills"
|
|
"github.com/muyue/muyue/internal/updater"
|
|
"github.com/muyue/muyue/internal/version"
|
|
)
|
|
|
|
func writeJSON(w http.ResponseWriter, data interface{}) {
|
|
json.NewEncoder(w).Encode(data)
|
|
}
|
|
|
|
func writeError(w http.ResponseWriter, msg string, code int) {
|
|
w.WriteHeader(code)
|
|
json.NewEncoder(w).Encode(map[string]string{"error": msg})
|
|
}
|
|
|
|
func (s *Server) handleInfo(w http.ResponseWriter, r *http.Request) {
|
|
writeJSON(w, map[string]interface{}{
|
|
"name": version.Name,
|
|
"version": version.Version,
|
|
"author": version.Author,
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleSystem(w http.ResponseWriter, r *http.Request) {
|
|
if s.scanResult == nil {
|
|
s.scanResult = scanner.ScanSystem()
|
|
}
|
|
writeJSON(w, map[string]interface{}{
|
|
"system": s.scanResult.System,
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleTools(w http.ResponseWriter, r *http.Request) {
|
|
if s.scanResult == nil {
|
|
s.scanResult = scanner.ScanSystem()
|
|
}
|
|
type toolInfo struct {
|
|
Name string `json:"name"`
|
|
Installed bool `json:"installed"`
|
|
Version string `json:"version"`
|
|
Path string `json:"path"`
|
|
}
|
|
tools := make([]toolInfo, len(s.scanResult.Tools))
|
|
for i, t := range s.scanResult.Tools {
|
|
tools[i] = toolInfo{
|
|
Name: t.Name,
|
|
Installed: t.Installed,
|
|
Version: t.Version,
|
|
Path: t.Path,
|
|
}
|
|
}
|
|
writeJSON(w, map[string]interface{}{
|
|
"tools": tools,
|
|
"total": len(tools),
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
|
|
if s.config == nil {
|
|
writeError(w, "no config", http.StatusNotFound)
|
|
return
|
|
}
|
|
writeJSON(w, map[string]interface{}{
|
|
"profile": s.config.Profile,
|
|
"terminal": s.config.Terminal,
|
|
"bmad": s.config.BMAD,
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleProviders(w http.ResponseWriter, r *http.Request) {
|
|
if s.config == nil {
|
|
writeError(w, "no config", http.StatusNotFound)
|
|
return
|
|
}
|
|
writeJSON(w, map[string]interface{}{
|
|
"providers": s.config.AI.Providers,
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleSkills(w http.ResponseWriter, r *http.Request) {
|
|
list, err := skills.List()
|
|
if err != nil {
|
|
writeError(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
writeJSON(w, map[string]interface{}{
|
|
"skills": list,
|
|
"count": len(list),
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleLSP(w http.ResponseWriter, r *http.Request) {
|
|
servers := lsp.ScanServers()
|
|
writeJSON(w, map[string]interface{}{
|
|
"servers": servers,
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleMCP(w http.ResponseWriter, r *http.Request) {
|
|
servers := mcp.ScanServers()
|
|
writeJSON(w, map[string]interface{}{
|
|
"servers": servers,
|
|
"configured": true,
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleMCPConfigure(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "POST" {
|
|
writeError(w, "POST only", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if err := mcp.ConfigureAll(s.config); err != nil {
|
|
writeError(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
writeJSON(w, map[string]string{"status": "ok"})
|
|
}
|
|
|
|
func (s *Server) handleUpdates(w http.ResponseWriter, r *http.Request) {
|
|
result := scanner.ScanSystem()
|
|
statuses := updater.CheckUpdates(result)
|
|
type updateInfo struct {
|
|
Tool string `json:"tool"`
|
|
Current string `json:"current"`
|
|
Latest string `json:"latest"`
|
|
NeedsUpdate bool `json:"needsUpdate"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
updates := make([]updateInfo, len(statuses))
|
|
for i, u := range statuses {
|
|
updates[i] = updateInfo{
|
|
Tool: u.Tool,
|
|
Current: u.Current,
|
|
Latest: u.Latest,
|
|
NeedsUpdate: u.NeedsUpdate,
|
|
Error: u.Error,
|
|
}
|
|
}
|
|
writeJSON(w, map[string]interface{}{
|
|
"updates": updates,
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleInstall(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "POST" {
|
|
writeError(w, "POST only", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
var body struct {
|
|
Tools []string `json:"tools"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
|
writeError(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
if len(body.Tools) == 0 {
|
|
writeError(w, "no tools specified", http.StatusBadRequest)
|
|
return
|
|
}
|
|
writeJSON(w, map[string]string{"status": "installing"})
|
|
}
|
|
|
|
func (s *Server) handleScan(w http.ResponseWriter, r *http.Request) {
|
|
s.scanResult = scanner.ScanSystem()
|
|
writeJSON(w, map[string]string{"status": "ok"})
|
|
}
|
|
|
|
func (s *Server) handleTerminal(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "POST" {
|
|
writeError(w, "POST only", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
var body struct {
|
|
Command string `json:"command"`
|
|
Cwd string `json:"cwd"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
|
writeError(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
if body.Command == "" {
|
|
writeError(w, "no command", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
shell := "/bin/sh"
|
|
if s, err := exec.LookPath("bash"); err == nil {
|
|
shell = s
|
|
}
|
|
|
|
cmd := exec.Command(shell, "-c", body.Command)
|
|
if body.Cwd != "" {
|
|
cmd.Dir = body.Cwd
|
|
}
|
|
out, err := cmd.CombinedOutput()
|
|
|
|
type termResult struct {
|
|
Output string `json:"output"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
result := termResult{Output: string(out)}
|
|
if err != nil {
|
|
result.Error = err.Error()
|
|
}
|
|
writeJSON(w, result)
|
|
}
|