Files
Augustin e15a034de5 refactor(api): split monolithic handlers.go into focused modules
Break down the 627-line handlers.go into specialized modules:
- handlers_chat.go: chat and streaming endpoints
- handlers_config.go: configuration endpoints
- handlers_common.go: shared utilities
- handlers_info.go: info and status endpoints
- handlers_terminal.go: terminal/shell endpoints
- handlers_tools.go: tool-related endpoints

Also includes config improvements, orchestrator enhancements, and
web component updates.

💘 Generated with Crush

Assisted-by: MiniMax-M2.7 via Crush <crush@charm.land>
2026-04-22 20:19:29 +02:00

293 lines
6.5 KiB
Go

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
}