feat: initial release of muyue - AI-powered dev environment assistant
Complete implementation of muyue v0.1.0, a single-binary Go tool that transforms the development environment with AI-powered orchestration. Core features: - TUI with 5 tabs (Dashboard/Chat/Workflow/Agents/Config) using Charm stack - AI chat via MiniMax M2.7 with async message handling - Structured Plan→Execute workflow engine (gather→plan→review→execute) - System scanner detecting 14 tools + 8 runtimes across Linux/macOS/Windows - Auto-installer for Crush, Claude Code, BMAD, Starship, runtimes - Background update daemon with hourly checks - LSP auto-config for 16 language servers - MCP auto-config for 12 servers (deployed to Crush + Claude Code) - Skills system with 5 built-ins + AI-powered generation - Crush/Claude Code proxy for unified control - HTML preview server for visual outputs - First-time setup wizard with interactive profiling - Cross-platform: Linux (primary), macOS, Windows, WSL CI/CD: - GitHub Actions CI: build + test + lint on Linux/macOS/Windows - Release workflow: cross-compile 6 binaries with checksums on tag push 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land>
This commit is contained in:
209
internal/lsp/lsp.go
Normal file
209
internal/lsp/lsp.go
Normal file
@@ -0,0 +1,209 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/muyue/muyue/internal/config"
|
||||
)
|
||||
|
||||
type LSPServer struct {
|
||||
Name string `json:"name"`
|
||||
Language string `json:"language"`
|
||||
Command string `json:"command"`
|
||||
InstallCmd string `json:"install_cmd"`
|
||||
ConfigFile string `json:"config_file"`
|
||||
Installed bool `json:"installed"`
|
||||
}
|
||||
|
||||
type LSPConfig struct {
|
||||
Servers []LSPServer `json:"servers"`
|
||||
}
|
||||
|
||||
var knownServers = []LSPServer{
|
||||
{Name: "gopls", Language: "go", Command: "gopls", InstallCmd: "go install golang.org/x/tools/gopls@latest"},
|
||||
{Name: "pyright", Language: "python", Command: "pyright", InstallCmd: "npm install -g pyright"},
|
||||
{Name: "typescript-language-server", Language: "typescript", Command: "typescript-language-server", InstallCmd: "npm install -g typescript-language-server typescript"},
|
||||
{Name: "vscode-json-language-server", Language: "json", Command: "vscode-json-language-server", InstallCmd: "npm install -g vscode-langservers-extracted"},
|
||||
{Name: "vscode-html-language-server", Language: "html", Command: "vscode-html-language-server", InstallCmd: "npm install -g vscode-langservers-extracted"},
|
||||
{Name: "vscode-css-language-server", Language: "css", Command: "vscode-css-language-server", InstallCmd: "npm install -g vscode-langservers-extracted"},
|
||||
{Name: "yaml-language-server", Language: "yaml", Command: "yaml-language-server", InstallCmd: "npm install -g yaml-language-server"},
|
||||
{Name: "bash-language-server", Language: "bash", Command: "bash-language-server", InstallCmd: "npm install -g bash-language-server"},
|
||||
{Name: "rust-analyzer", Language: "rust", Command: "rust-analyzer", InstallCmd: "rustup component add rust-analyzer"},
|
||||
{Name: "clangd", Language: "c/c++", Command: "clangd", InstallCmd: ""},
|
||||
{Name: "lua-language-server", Language: "lua", Command: "lua-language-server", InstallCmd: "npm install -g lua-language-server"},
|
||||
{Name: "dockerfile-language-server", Language: "dockerfile", Command: "docker-langserver", InstallCmd: "npm install -g dockerfile-language-server-nodejs"},
|
||||
{Name: "tailwindcss-language-server", Language: "tailwind", Command: "tailwindcss-language-server", InstallCmd: "npm install -g @tailwindcss/language-server"},
|
||||
{Name: "svelte-language-server", Language: "svelte", Command: "svelteserver", InstallCmd: "npm install -g svelte-language-server"},
|
||||
{Name: "vue-language-server", Language: "vue", Command: "vue-language-server", InstallCmd: "npm install -g @vue/language-server"},
|
||||
{Name: "golangci-lint-langserver", Language: "go-lint", Command: "golangci-lint-langserver", InstallCmd: "go install github.com/nametake/golangci-lint-langserver@latest"},
|
||||
}
|
||||
|
||||
func ScanServers() []LSPServer {
|
||||
servers := make([]LSPServer, len(knownServers))
|
||||
for i, s := range knownServers {
|
||||
servers[i] = s
|
||||
_, err := exec.LookPath(s.Command)
|
||||
servers[i].Installed = err == nil
|
||||
}
|
||||
return servers
|
||||
}
|
||||
|
||||
func InstallServer(name string) error {
|
||||
for _, s := range knownServers {
|
||||
if s.Name == name {
|
||||
if s.InstallCmd == "" {
|
||||
return fmt.Errorf("%s has no auto-install command, install manually", name)
|
||||
}
|
||||
cmd := exec.Command("bash", "-c", s.InstallCmd)
|
||||
cmd.Env = os.Environ()
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("install %s: %s: %w", name, string(output), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("unknown LSP server: %s", name)
|
||||
}
|
||||
|
||||
func InstallForLanguages(languages []string) []LSPServer {
|
||||
langMap := map[string][]string{
|
||||
"go": {"gopls"},
|
||||
"typescript": {"typescript-language-server", "vscode-json-language-server", "vscode-html-language-server", "vscode-css-language-server"},
|
||||
"javascript": {"typescript-language-server", "vscode-json-language-server", "vscode-html-language-server", "vscode-css-language-server"},
|
||||
"python": {"pyright"},
|
||||
"rust": {"rust-analyzer"},
|
||||
"c": {"clangd"},
|
||||
"cpp": {"clangd"},
|
||||
"json": {"vscode-json-language-server"},
|
||||
"yaml": {"yaml-language-server"},
|
||||
"bash": {"bash-language-server"},
|
||||
"html": {"vscode-html-language-server"},
|
||||
"css": {"vscode-css-language-server"},
|
||||
"lua": {"lua-language-server"},
|
||||
"docker": {"dockerfile-language-server"},
|
||||
"svelte": {"svelte-language-server"},
|
||||
"vue": {"vue-language-server"},
|
||||
}
|
||||
|
||||
installed := map[string]bool{}
|
||||
var results []LSPServer
|
||||
|
||||
for _, lang := range languages {
|
||||
if servers, ok := langMap[lang]; ok {
|
||||
for _, srv := range servers {
|
||||
if installed[srv] {
|
||||
continue
|
||||
}
|
||||
installed[srv] = true
|
||||
if err := InstallServer(srv); err != nil {
|
||||
results = append(results, LSPServer{Name: srv, Language: lang, Installed: false})
|
||||
} else {
|
||||
results = append(results, LSPServer{Name: srv, Language: lang, Installed: true})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func GenerateCrushConfig(cfg *config.MuyueConfig) error {
|
||||
if cfg == nil {
|
||||
return fmt.Errorf("config is nil")
|
||||
}
|
||||
|
||||
configDir, err := config.ConfigDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type lspEntry struct {
|
||||
Command []string `json:"command"`
|
||||
}
|
||||
|
||||
lspConfig := map[string]lspEntry{}
|
||||
|
||||
for _, lang := range cfg.Profile.Languages {
|
||||
switch lang {
|
||||
case "go":
|
||||
lspConfig["go"] = lspEntry{Command: []string{"gopls"}}
|
||||
case "python":
|
||||
lspConfig["python"] = lspEntry{Command: []string{"pyright-langserver", "--stdio"}}
|
||||
case "typescript", "javascript":
|
||||
lspConfig["typescript"] = lspEntry{Command: []string{"typescript-language-server", "--stdio"}}
|
||||
case "rust":
|
||||
lspConfig["rust"] = lspEntry{Command: []string{"rust-analyzer"}}
|
||||
case "c", "cpp":
|
||||
lspConfig["c"] = lspEntry{Command: []string{"clangd"}}
|
||||
case "lua":
|
||||
lspConfig["lua"] = lspEntry{Command: []string{"lua-language-server"}}
|
||||
}
|
||||
}
|
||||
|
||||
if len(lspConfig) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(lspConfig, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lspPath := filepath.Join(configDir, "crush.json")
|
||||
existing, err := os.ReadFile(lspPath)
|
||||
if err == nil {
|
||||
var existingConfig map[string]interface{}
|
||||
if json.Unmarshal(existing, &existingConfig) == nil {
|
||||
var newConfig map[string]interface{}
|
||||
if json.Unmarshal(data, &newConfig) == nil {
|
||||
for k, v := range newConfig {
|
||||
existingConfig[k] = v
|
||||
}
|
||||
data, _ = json.MarshalIndent(existingConfig, "", " ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return os.WriteFile(lspPath, data, 0644)
|
||||
}
|
||||
|
||||
func EnsureCrushConfig(cfg *config.MuyueConfig) error {
|
||||
configDir, _ := config.ConfigDir()
|
||||
crusherPath := filepath.Join(configDir, "crush.json")
|
||||
|
||||
if _, err := os.Stat(crusherPath); err != nil {
|
||||
home, _ := os.UserHomeDir()
|
||||
homeCrush := filepath.Join(home, ".config", "crush", "crush.json")
|
||||
if _, err := os.Stat(homeCrush); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
defaultConfig := map[string]interface{}{
|
||||
"version": "1",
|
||||
}
|
||||
|
||||
data, _ := json.MarshalIndent(defaultConfig, "", " ")
|
||||
os.MkdirAll(filepath.Dir(crusherPath), 0755)
|
||||
return os.WriteFile(crusherPath, data, 0644)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func PlatformTool() string {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
return "apt"
|
||||
case "darwin":
|
||||
return "brew"
|
||||
case "windows":
|
||||
return "winget"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user