package profiler import ( "fmt" "os/exec" "sort" "github.com/charmbracelet/huh" "github.com/muyue/muyue/internal/config" "github.com/muyue/muyue/internal/platform" "github.com/muyue/muyue/internal/scanner" ) type editorCandidate struct { key string binary string label string } var allEditors = []editorCandidate{ {"VS Code", "code", "VS Code"}, {"Cursor", "cursor", "Cursor"}, {"Zed", "zed", "Zed"}, {"Neovim", "nvim", "Neovim"}, {"Vim", "vim", "Vim"}, {"Helix", "hx", "Helix"}, {"Emacs", "emacs", "Emacs"}, {"JetBrains", "jetbrains", "JetBrains (IntelliJ/GoLand/etc.)"}, {"Sublime Text", "subl", "Sublime Text"}, } type aiCandidate struct { key string label string } var allAIProviders = []aiCandidate{ {"minimax", "MiniMax M2.7"}, {"zai", "Z.AI GLM"}, {"anthropic", "Anthropic Claude"}, {"openai", "OpenAI GPT"}, {"ollama", "Ollama (local)"}, } func RunFirstTimeSetup() (*config.MuyueConfig, error) { cfg := config.Default() info := platform.Detect() result := scanner.ScanSystem() fmt.Println("Welcome to muyue! Let's set up your environment.") var name, pseudo, email string var languages []string var editor string var aiProvider string nameField := huh.NewInput(). Title("What's your name?"). Placeholder("Augustin"). Value(&name) pseudoField := huh.NewInput(). Title("Your pseudo?"). Placeholder("muyue"). Value(&pseudo) emailField := huh.NewInput(). Title("Your email (for git config)?"). Placeholder("you@example.com"). Value(&email) langOptions := buildLanguageOptions(result) langSelect := huh.NewMultiSelect[string](). Title("Which languages do you work with?"). Options(langOptions...). Value(&languages) editorOptions := buildEditorOptions(info) editorSelect := huh.NewSelect[string](). Title("Preferred editor?"). Options(editorOptions...). Value(&editor) aiOptions := buildAIOptions(info) aiSelect := huh.NewSelect[string](). Title("Which AI provider for orchestration?"). Options(aiOptions...). Value(&aiProvider) form := huh.NewForm( huh.NewGroup(nameField, pseudoField, emailField), huh.NewGroup(langSelect), huh.NewGroup(editorSelect), huh.NewGroup(aiSelect), ) if err := form.Run(); err != nil { return nil, fmt.Errorf("profile form: %w", err) } cfg.Profile.Name = name cfg.Profile.Pseudo = pseudo cfg.Profile.Email = email cfg.Profile.Languages = languages cfg.Profile.Preferences.Editor = editor cfg.Profile.Preferences.DefaultAI = aiProvider cfg.Profile.Preferences.Shell = info.Shell for i := range cfg.AI.Providers { cfg.AI.Providers[i].Active = cfg.AI.Providers[i].Name == aiProvider } return cfg, nil } func buildEditorOptions(info platform.SystemInfo) []huh.Option[string] { type scored struct { option huh.Option[string] score int } var candidates []scored for _, e := range allEditors { installed := isInstalled(e.binary) score := 0 if installed { score = 10 } switch info.OS { case platform.Linux: if e.binary == "code" || e.binary == "nvim" || e.binary == "hx" || e.binary == "vim" { score += 2 } case platform.MacOS: if e.binary == "code" || e.binary == "cursor" || e.binary == "zed" { score += 2 } case platform.Windows: if e.binary == "code" || e.binary == "cursor" { score += 2 } } label := e.label if installed { label = e.label + " (installed)" } candidates = append(candidates, scored{ option: huh.NewOption[string](label, e.key), score: score, }) } sort.SliceStable(candidates, func(i, j int) bool { return candidates[i].score > candidates[j].score }) options := make([]huh.Option[string], len(candidates)) for i, c := range candidates { options[i] = c.option } return options } func buildLanguageOptions(result *scanner.ScanResult) []huh.Option[string] { type scored struct { option huh.Option[string] score int } langs := []struct { key string label string binaries []string }{ {"go", "Go", []string{"go"}}, {"typescript", "TypeScript/JavaScript", []string{"node", "npm", "pnpm"}}, {"python", "Python", []string{"python3", "pip3", "uv"}}, {"rust", "Rust", []string{"rustc", "cargo"}}, {"java", "Java", []string{"java", "javac"}}, {"c", "C/C++", []string{"gcc", "g++", "clang"}}, {"ruby", "Ruby", []string{"ruby"}}, {"php", "PHP", []string{"php"}}, {"swift", "Swift", []string{"swift"}}, {"kotlin", "Kotlin", []string{"kotlin"}}, {"zig", "Zig", []string{"zig"}}, {"dart", "Dart/Flutter", []string{"dart", "flutter"}}, } var candidates []scored for _, l := range langs { score := 0 for _, bin := range l.binaries { if isInstalled(bin) { score = 5 break } } if result != nil { for _, r := range result.Runtimes { if r.Installed && r.Name == l.key { score += 5 break } } } candidates = append(candidates, scored{ option: huh.NewOption[string](l.label, l.key), score: score, }) } sort.SliceStable(candidates, func(i, j int) bool { return candidates[i].score > candidates[j].score }) options := make([]huh.Option[string], len(candidates)) for i, c := range candidates { options[i] = c.option } return options } func buildAIOptions(info platform.SystemInfo) []huh.Option[string] { type scored struct { option huh.Option[string] score int } var candidates []scored for _, ai := range allAIProviders { score := 0 if ai.key == "ollama" { if isInstalled("ollama") { score = 15 } else { score = -5 } } switch info.OS { case platform.Linux: if ai.key == "ollama" || ai.key == "anthropic" { score += 1 } case platform.MacOS: if ai.key == "anthropic" || ai.key == "openai" { score += 1 } } candidates = append(candidates, scored{ option: huh.NewOption[string](ai.label, ai.key), score: score, }) } sort.SliceStable(candidates, func(i, j int) bool { return candidates[i].score > candidates[j].score }) options := make([]huh.Option[string], len(candidates)) for i, c := range candidates { options[i] = c.option } return options } func isInstalled(binary string) bool { _, err := exec.LookPath(binary) return err == nil } func AskAPIKey(providerName string) (string, error) { var apiKey string field := huh.NewInput(). Title(fmt.Sprintf("Enter your %s API key:", providerName)). Description("The key will be stored locally in ~/.config/muyue/config.yaml"). EchoMode(huh.EchoModePassword). Value(&apiKey) form := huh.NewForm(huh.NewGroup(field)) if err := form.Run(); err != nil { return "", err } return apiKey, nil }