package api import ( "encoding/json" "net/http" "os/exec" "github.com/muyue/muyue/internal/config" "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) handleUpdatePreferences(w http.ResponseWriter, r *http.Request) { if r.Method != "PUT" { writeError(w, "PUT only", http.StatusMethodNotAllowed) return } if s.config == nil { writeError(w, "no config", http.StatusNotFound) return } var body struct { Language string `json:"language"` KeyboardLayout string `json:"keyboard_layout"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeError(w, err.Error(), http.StatusBadRequest) return } if body.Language != "" { s.config.Profile.Preferences.Language = body.Language } if body.KeyboardLayout != "" { s.config.Profile.Preferences.KeyboardLayout = body.KeyboardLayout } if err := config.Save(s.config); err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } 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) }