feat: initial release of muyue - AI-powered dev environment assistant
Some checks failed
CI / build (macos-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled

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:
Augustin
2026-04-19 22:29:20 +02:00
commit f0ccd265da
25 changed files with 4743 additions and 0 deletions

255
internal/mcp/mcp.go Normal file
View File

@@ -0,0 +1,255 @@
package mcp
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"github.com/muyue/muyue/internal/config"
)
type MCPServer struct {
Name string `json:"name"`
Command string `json:"command"`
Args []string `json:"args"`
Env map[string]string `json:"env,omitempty"`
Installed bool `json:"installed"`
Category string `json:"category"`
}
var knownMCPServers = []MCPServer{
{
Name: "filesystem",
Command: "npx",
Args: []string{"-y", "@modelcontextprotocol/server-filesystem"},
Category: "core",
},
{
Name: "github",
Command: "npx",
Args: []string{"-y", "@modelcontextprotocol/server-github"},
Env: map[string]string{"GITHUB_PERSONAL_ACCESS_TOKEN": ""},
Category: "vcs",
},
{
Name: "git",
Command: "npx",
Args: []string{"-y", "@modelcontextprotocol/server-git"},
Category: "vcs",
},
{
Name: "fetch",
Command: "npx",
Args: []string{"-y", "@modelcontextprotocol/server-fetch"},
Category: "web",
},
{
Name: "memory",
Command: "npx",
Args: []string{"-y", "@modelcontextprotocol/server-memory"},
Category: "core",
},
{
Name: "sequential-thinking",
Command: "npx",
Args: []string{"-y", "@modelcontextprotocol/server-sequential-thinking"},
Category: "ai",
},
{
Name: "brave-search",
Command: "npx",
Args: []string{"-y", "@modelcontextprotocol/server-brave-search"},
Env: map[string]string{"BRAVE_API_KEY": ""},
Category: "web",
},
{
Name: "sqlite",
Command: "npx",
Args: []string{"-y", "@modelcontextprotocol/server-sqlite"},
Category: "database",
},
{
Name: "postgres",
Command: "npx",
Args: []string{"-y", "@modelcontextprotocol/server-postgres"},
Category: "database",
},
{
Name: "docker",
Command: "npx",
Args: []string{"-y", "@modelcontextprotocol/server-docker"},
Category: "devops",
},
{
Name: "minimax-web-search",
Command: "npx",
Args: []string{"-y", "@minimax/mcp-web-search"},
Env: map[string]string{"MINIMAX_API_KEY": ""},
Category: "ai",
},
{
Name: "minimax-image",
Command: "npx",
Args: []string{"-y", "@minimax/mcp-image-understanding"},
Env: map[string]string{"MINIMAX_API_KEY": ""},
Category: "ai",
},
}
func ScanServers() []MCPServer {
servers := make([]MCPServer, len(knownMCPServers))
for i, s := range knownMCPServers {
servers[i] = s
if s.Command == "npx" {
_, err := exec.LookPath("npx")
servers[i].Installed = err == nil
} else {
_, err := exec.LookPath(s.Command)
servers[i].Installed = err == nil
}
}
return servers
}
func GenerateCrushMCPConfig(cfg *config.MuyueConfig, homeDir string) error {
if homeDir == "" {
home, _ := os.UserHomeDir()
homeDir = home
}
configDir := filepath.Join(homeDir, ".config", "crush")
crusherPath := filepath.Join(configDir, "crush.json")
os.MkdirAll(configDir, 0755)
existing := map[string]interface{}{}
data, err := os.ReadFile(crusherPath)
if err == nil {
json.Unmarshal(data, &existing)
}
mcps := map[string]interface{}{}
core := []MCPServer{
{Name: "filesystem", Command: "npx", Args: []string{"-y", "@modelcontextprotocol/server-filesystem", homeDir + "/projects"}},
{Name: "fetch", Command: "npx", Args: []string{"-y", "@modelcontextprotocol/server-fetch"}},
{Name: "memory", Command: "npx", Args: []string{"-y", "@modelcontextprotocol/server-memory"}},
}
if cfg != nil {
for _, p := range cfg.AI.Providers {
if p.Name == "minimax" && p.APIKey != "" {
core = append(core, MCPServer{
Name: "minimax-web-search",
Command: "npx",
Args: []string{"-y", "@minimax/mcp-web-search"},
Env: map[string]string{"MINIMAX_API_KEY": p.APIKey},
})
core = append(core, MCPServer{
Name: "minimax-image",
Command: "npx",
Args: []string{"-y", "@minimax/mcp-image-understanding"},
Env: map[string]string{"MINIMAX_API_KEY": p.APIKey},
})
}
}
}
for _, s := range core {
entry := map[string]interface{}{
"command": s.Command,
"args": s.Args,
}
if len(s.Env) > 0 {
entry["env"] = s.Env
}
mcps[s.Name] = entry
}
existing["mcps"] = mcps
out, err := json.MarshalIndent(existing, "", " ")
if err != nil {
return err
}
return os.WriteFile(crusherPath, out, 0644)
}
func GenerateClaudeMCPConfig(cfg *config.MuyueConfig, homeDir string) error {
if homeDir == "" {
home, _ := os.UserHomeDir()
homeDir = home
}
configPath := filepath.Join(homeDir, ".claude.json")
existing := map[string]interface{}{}
data, err := os.ReadFile(configPath)
if err == nil {
json.Unmarshal(data, &existing)
}
mcpservers := map[string]interface{}{}
core := []struct {
name string
cmd string
args []string
env map[string]string
}{
{"filesystem", "npx", []string{"-y", "@modelcontextprotocol/server-filesystem", homeDir + "/projects"}, nil},
{"fetch", "npx", []string{"-y", "@modelcontextprotocol/server-fetch"}, nil},
{"memory", "npx", []string{"-y", "@modelcontextprotocol/server-memory"}, nil},
{"sequential-thinking", "npx", []string{"-y", "@modelcontextprotocol/server-sequential-thinking"}, nil},
}
if cfg != nil {
for _, p := range cfg.AI.Providers {
if p.Name == "minimax" && p.APIKey != "" {
core = append(core, struct {
name string
cmd string
args []string
env map[string]string
}{"minimax-web-search", "npx", []string{"-y", "@minimax/mcp-web-search"}, map[string]string{"MINIMAX_API_KEY": p.APIKey}})
}
}
}
for _, s := range core {
entry := map[string]interface{}{
"command": s.cmd,
"args": s.args,
}
if len(s.env) > 0 {
entry["env"] = s.env
}
mcpservers[s.name] = entry
}
existing["mcpServers"] = mcpservers
out, err := json.MarshalIndent(existing, "", " ")
if err != nil {
return err
}
return os.WriteFile(configPath, out, 0644)
}
func ConfigureAll(cfg *config.MuyueConfig) error {
home, _ := os.UserHomeDir()
if err := GenerateCrushMCPConfig(cfg, home); err != nil {
return fmt.Errorf("crush MCP config: %w", err)
}
if err := GenerateClaudeMCPConfig(cfg, home); err != nil {
return fmt.Errorf("claude MCP config: %w", err)
}
return nil
}