Files
MuyueWorkspace/internal/config/config.go
Augustin 04b0fff791
All checks were successful
Beta Release / beta (push) Successful in 44s
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 18:34:14 +02:00

303 lines
7.5 KiB
Go

package config
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/muyue/muyue/internal/secret"
"github.com/muyue/muyue/internal/version"
"gopkg.in/yaml.v3"
)
type Profile struct {
Name string `yaml:"name"`
Pseudo string `yaml:"pseudo"`
Email string `yaml:"email"`
Languages []string `yaml:"languages"`
Preferences struct {
Editor string `yaml:"editor"`
Shell string `yaml:"shell"`
Theme string `yaml:"theme"`
DefaultAI string `yaml:"default_ai"`
AutoUpdate bool `yaml:"auto_update"`
CheckOnStart bool `yaml:"check_on_start"`
Language string `yaml:"language"`
KeyboardLayout string `yaml:"keyboard_layout"`
} `yaml:"preferences"`
}
type AIProvider struct {
Name string `yaml:"name"`
APIKey string `yaml:"api_key,omitempty"`
BaseURL string `yaml:"base_url,omitempty"`
Model string `yaml:"model"`
Active bool `yaml:"active"`
}
type ToolConfig struct {
Name string `yaml:"name"`
Installed bool `yaml:"installed"`
Version string `yaml:"version"`
AutoUpdate bool `yaml:"auto_update"`
}
type SSHConnection struct {
Name string `yaml:"name"`
Host string `yaml:"host"`
Port int `yaml:"port"`
User string `yaml:"user"`
Password string `yaml:"password,omitempty"`
KeyPath string `yaml:"key_path,omitempty"`
}
type MuyueConfig struct {
Version string `yaml:"version"`
Profile Profile `yaml:"profile"`
AI struct {
Providers []AIProvider `yaml:"providers"`
} `yaml:"ai"`
Tools []ToolConfig `yaml:"tools"`
BMAD struct {
Installed bool `yaml:"installed"`
Version string `yaml:"version"`
Global bool `yaml:"global"`
} `yaml:"bmad"`
Terminal struct {
CustomPrompt bool `yaml:"custom_prompt"`
PromptTheme string `yaml:"prompt_theme"`
SSH []SSHConnection `yaml:"ssh"`
FontSize int `yaml:"font_size"`
FontFamily string `yaml:"font_family"`
Theme string `yaml:"theme"`
} `yaml:"terminal"`
}
type TerminalTheme struct {
Name string `yaml:"name"`
Background string `yaml:"background"`
Foreground string `yaml:"foreground"`
Cursor string `yaml:"cursor"`
Black string `yaml:"black"`
Red string `yaml:"red"`
Green string `yaml:"green"`
Yellow string `yaml:"yellow"`
Blue string `yaml:"blue"`
Magenta string `yaml:"magenta"`
Cyan string `yaml:"cyan"`
White string `yaml:"white"`
}
var DEFAULT_TERMINAL_THEMES = map[string]TerminalTheme{
"default": {
Name: "Default", Background: "#0A0A0C", Foreground: "#EAE0E2",
Cursor: "#FF0033", Black: "#0A0A0C", Red: "#FF0033",
Green: "#00E676", Yellow: "#FFD740", Blue: "#448AFF",
Magenta: "#FF1A5E", Cyan: "#00BCD4", White: "#EAE0E2",
},
"monokai": {
Name: "Monokai", Background: "#272822", Foreground: "#F8F8F2",
Cursor: "#F8F8F0", Black: "#272822", Red: "#F92672",
Green: "#A6E22E", Yellow: "#E6DB74", Blue: "#66D9EF",
Magenta: "#AE81FF", Cyan: "#A1EFE4", White: "#F8F8F2",
},
"gruvbox": {
Name: "Gruvbox", Background: "#282828", Foreground: "#EBDBB2",
Cursor: "#FB4934", Black: "#282828", Red: "#CC241D",
Green: "#98971A", Yellow: "#D79921", Blue: "#458588",
Magenta: "#B16286", Cyan: "#689D6A", White: "#EBDBB2",
},
"nord": {
Name: "Nord", Background: "#2E3440", Foreground: "#D8DEE9",
Cursor: "#D8DEE9", Black: "#2E3440", Red: "#BF616A",
Green: "#A3BE8C", Yellow: "#EBCB8B", Blue: "#81A1C1",
Magenta: "#B48EAD", Cyan: "#88C0D0", White: "#D8DEE9",
},
"solarized-dark": {
Name: "Solarized Dark", Background: "#002B36", Foreground: "#839496",
Cursor: "#D33682", Black: "#002B36", Red: "#DC322F",
Green: "#859900", Yellow: "#B58900", Blue: "#268BD2",
Magenta: "#D33682", Cyan: "#2AA198", White: "#FDF6E3",
},
"dracula": {
Name: "Dracula", Background: "#282A36", Foreground: "#F8F8F2",
Cursor: "#F8F8F2", Black: "#282A36", Red: "#FF5555",
Green: "#50FA7B", Yellow: "#F1FA8C", Blue: "#BD93F9",
Magenta: "#FF79C6", Cyan: "#8BE9FD", White: "#F8F8F2",
},
}
func GetTerminalTheme(name string) TerminalTheme {
if theme, ok := DEFAULT_TERMINAL_THEMES[name]; ok {
return theme
}
return DEFAULT_TERMINAL_THEMES["default"]
}
func ConfigDir() (string, error) {
configDir, err := os.UserConfigDir()
if err != nil {
return "", err
}
dir := filepath.Join(configDir, "muyue")
legacyDir := filepath.Join(homeDir(), ".muyue")
if _, err := os.Stat(legacyDir); err == nil {
if _, err := os.Stat(dir); err != nil {
if err := os.Rename(legacyDir, dir); err != nil {
log.Printf("config migration: rename %s to %s: %v", legacyDir, dir, err)
}
}
}
return dir, nil
}
func homeDir() string {
home, err := os.UserHomeDir()
if err != nil {
return "/"
}
return home
}
func ConfigPath() (string, error) {
dir, err := ConfigDir()
if err != nil {
return "", err
}
return filepath.Join(dir, "config.yaml"), nil
}
func Exists() bool {
path, err := ConfigPath()
if err != nil {
return false
}
_, err = os.Stat(path)
return err == nil
}
func Load() (*MuyueConfig, error) {
path, err := ConfigPath()
if err != nil {
return nil, err
}
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading config: %w", err)
}
var cfg MuyueConfig
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parsing config: %w", err)
}
// Decrypt API keys
for i := range cfg.AI.Providers {
if cfg.AI.Providers[i].APIKey != "" {
decrypted, err := secret.Decrypt(cfg.AI.Providers[i].APIKey)
if err != nil {
decrypted = cfg.AI.Providers[i].APIKey
}
cfg.AI.Providers[i].APIKey = decrypted
}
}
return &cfg, nil
}
func Save(cfg *MuyueConfig) error {
dir, err := ConfigDir()
if err != nil {
return err
}
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("creating config dir: %w", err)
}
// Encrypt API keys before saving
saveCfg := *cfg
saveCfg.AI.Providers = make([]AIProvider, len(cfg.AI.Providers))
for i, p := range cfg.AI.Providers {
saveCfg.AI.Providers[i] = p
if p.APIKey != "" && !secret.IsEncrypted(p.APIKey) {
enc, err := secret.Encrypt(p.APIKey)
if err == nil {
saveCfg.AI.Providers[i].APIKey = enc
}
}
}
path := filepath.Join(dir, "config.yaml")
data, err := yaml.Marshal(&saveCfg)
if err != nil {
return fmt.Errorf("marshaling config: %w", err)
}
if err := os.WriteFile(path, data, 0600); err != nil {
return fmt.Errorf("writing config: %w", err)
}
return nil
}
func Default() *MuyueConfig {
cfg := &MuyueConfig{
Version: version.Version,
Profile: Profile{
Name: "",
Pseudo: "muyue",
Languages: []string{},
},
}
cfg.Profile.Preferences.DefaultAI = "minimax"
cfg.Profile.Preferences.AutoUpdate = true
cfg.Profile.Preferences.CheckOnStart = true
cfg.Profile.Preferences.Theme = "charm"
cfg.Profile.Preferences.Language = "fr"
cfg.Profile.Preferences.KeyboardLayout = "azerty"
cfg.AI.Providers = []AIProvider{
{
Name: "minimax",
Model: "MiniMax-M2.7",
BaseURL: "https://api.minimax.io/v1",
Active: true,
},
{
Name: "zai",
Model: "glm",
Active: false,
},
{
Name: "anthropic",
Model: "claude-sonnet-4-20250514",
Active: false,
},
{
Name: "openai",
Model: "gpt-4o",
BaseURL: "https://api.openai.com/v1",
Active: false,
},
{
Name: "ollama",
Model: "llama3",
BaseURL: "http://localhost:11434/api",
Active: false,
},
}
cfg.BMAD.Global = true
cfg.Terminal.CustomPrompt = true
cfg.Terminal.PromptTheme = "zerotwo"
return cfg
}