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>
196 lines
4.5 KiB
Go
196 lines
4.5 KiB
Go
package scanner
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/muyue/muyue/internal/platform"
|
|
)
|
|
|
|
type ToolStatus struct {
|
|
Name string `yaml:"name"`
|
|
Installed bool `yaml:"installed"`
|
|
Version string `yaml:"version"`
|
|
Path string `yaml:"path"`
|
|
Latest string `yaml:"latest"`
|
|
NeedsUpdate bool `yaml:"needs_update"`
|
|
Category string `yaml:"category"`
|
|
}
|
|
|
|
type RuntimeStatus struct {
|
|
Name string `yaml:"name"`
|
|
Installed bool `yaml:"installed"`
|
|
Version string `yaml:"version"`
|
|
}
|
|
|
|
type ScanResult struct {
|
|
System platform.SystemInfo `yaml:"system"`
|
|
Tools []ToolStatus `yaml:"tools"`
|
|
Runtimes []RuntimeStatus `yaml:"runtimes"`
|
|
ShellSetup bool `yaml:"shell_setup"`
|
|
GitConfigured bool `yaml:"git_configured"`
|
|
}
|
|
|
|
func ScanSystem() *ScanResult {
|
|
info := platform.Detect()
|
|
result := &ScanResult{
|
|
System: info,
|
|
}
|
|
|
|
result.Tools = scanTools()
|
|
result.Runtimes = scanRuntimes()
|
|
result.ShellSetup = checkShellSetup()
|
|
result.GitConfigured = checkGitConfig()
|
|
|
|
return result
|
|
}
|
|
|
|
func scanTools() []ToolStatus {
|
|
tools := []struct {
|
|
name string
|
|
category string
|
|
version []string
|
|
}{
|
|
{"crush", "ai", []string{"version", "--short"}},
|
|
{"claude", "ai", []string{"--version"}},
|
|
{"git", "vcs", []string{"--version"}},
|
|
{"node", "runtime", []string{"--version"}},
|
|
{"npm", "runtime", []string{"--version"}},
|
|
{"pnpm", "runtime", []string{"--version"}},
|
|
{"python3", "runtime", []string{"--version"}},
|
|
{"pip3", "runtime", []string{"--version"}},
|
|
{"uv", "runtime", []string{"--version"}},
|
|
{"go", "runtime", []string{"version"}},
|
|
{"docker", "devops", []string{"--version"}},
|
|
{"gh", "devops", []string{"--version"}},
|
|
{"starship", "prompt", []string{"--version"}},
|
|
{"npx", "runtime", []string{"--version"}},
|
|
}
|
|
|
|
var statuses []ToolStatus
|
|
for _, t := range tools {
|
|
status := ToolStatus{
|
|
Name: t.name,
|
|
Category: t.category,
|
|
}
|
|
|
|
path, err := exec.LookPath(t.name)
|
|
if err != nil {
|
|
statuses = append(statuses, status)
|
|
continue
|
|
}
|
|
|
|
status.Installed = true
|
|
status.Path = path
|
|
|
|
if len(t.version) > 0 {
|
|
cmd := exec.Command(t.name, t.version...)
|
|
out, err := cmd.Output()
|
|
if err == nil {
|
|
status.Version = strings.TrimSpace(string(out))
|
|
}
|
|
}
|
|
|
|
statuses = append(statuses, status)
|
|
}
|
|
|
|
return statuses
|
|
}
|
|
|
|
func scanRuntimes() []RuntimeStatus {
|
|
runtimes := []struct {
|
|
name string
|
|
command []string
|
|
}{
|
|
{"Go", []string{"go", "version"}},
|
|
{"Node.js", []string{"node", "--version"}},
|
|
{"Python", []string{"python3", "--version"}},
|
|
{"Rust", []string{"rustc", "--version"}},
|
|
{"Java", []string{"java", "--version"}},
|
|
{"Ruby", []string{"ruby", "--version"}},
|
|
{"PHP", []string{"php", "--version"}},
|
|
{"Dotnet", []string{"dotnet", "--version"}},
|
|
}
|
|
|
|
var statuses []RuntimeStatus
|
|
for _, r := range runtimes {
|
|
status := RuntimeStatus{Name: r.name}
|
|
cmd := exec.Command(r.command[0], r.command[1:]...)
|
|
out, err := cmd.Output()
|
|
if err == nil {
|
|
status.Installed = true
|
|
status.Version = strings.TrimSpace(string(out))
|
|
}
|
|
statuses = append(statuses, status)
|
|
}
|
|
|
|
return statuses
|
|
}
|
|
|
|
func checkShellSetup() bool {
|
|
home, _ := os.UserHomeDir()
|
|
rcFiles := []string{".bashrc", ".zshrc", ".config/fish/config.fish"}
|
|
for _, f := range rcFiles {
|
|
data, err := os.ReadFile(home + "/" + f)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
content := string(data)
|
|
if strings.Contains(content, "starship") ||
|
|
strings.Contains(content, "muyue") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func checkGitConfig() bool {
|
|
for _, key := range []string{"user.name", "user.email"} {
|
|
cmd := exec.Command("git", "config", "--global", key)
|
|
if _, err := cmd.Output(); err != nil {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
var versionRegex = regexp.MustCompile(`\d+\.\d+\.\d+`)
|
|
|
|
func (s *ScanResult) Summary() string {
|
|
var b strings.Builder
|
|
|
|
fmt.Fprintf(&b, "System: %s\n", s.System.String())
|
|
fmt.Fprintf(&b, "\nTools:\n")
|
|
|
|
installed := 0
|
|
for _, t := range s.Tools {
|
|
if t.Installed {
|
|
installed++
|
|
fmt.Fprintf(&b, " [v] %s %s\n", t.Name, versionRegex.FindString(t.Version))
|
|
} else {
|
|
fmt.Fprintf(&b, " [ ] %s (not installed)\n", t.Name)
|
|
}
|
|
}
|
|
fmt.Fprintf(&b, "\nInstalled: %d/%d\n", installed, len(s.Tools))
|
|
|
|
fmt.Fprintf(&b, "\nRuntimes:\n")
|
|
for _, r := range s.Runtimes {
|
|
if r.Installed {
|
|
fmt.Fprintf(&b, " [v] %s\n", r.Version)
|
|
} else {
|
|
fmt.Fprintf(&b, " [ ] %s (not installed)\n", r.Name)
|
|
}
|
|
}
|
|
|
|
if s.GitConfigured {
|
|
fmt.Fprintf(&b, "\nGit: configured\n")
|
|
} else {
|
|
fmt.Fprintf(&b, "\nGit: not fully configured\n")
|
|
}
|
|
|
|
return b.String()
|
|
}
|