Files
MuyueWorkspace/internal/config/config.go
Augustin 68acabd6a1 feat(ai): add Xiaomi MiMo provider, ZAI as last-resort fallback
Add MiMo-V2.5-Pro from Xiaomi Token Plan as a new AI provider with
base URL https://token-plan-ams.xiaomimimo.com/v1. The /model change
command now switches between MiniMax and MiMo only. ZAI is always
placed last in the fallback chain as the provider of ultimate resort.
Config panel shows MiniMax and MiMo cards.

💘 Generated with Crush

Assisted-by: GLM-5.1 via Crush <crush@charm.land>
2026-04-25 21:24:39 +02:00

309 lines
8.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" json:"name"`
Pseudo string `yaml:"pseudo" json:"pseudo"`
Email string `yaml:"email" json:"email"`
Languages []string `yaml:"languages" json:"languages"`
Preferences struct {
Editor string `yaml:"editor" json:"editor"`
Shell string `yaml:"shell" json:"shell"`
Theme string `yaml:"theme" json:"theme"`
DefaultAI string `yaml:"default_ai" json:"default_ai"`
AutoUpdate bool `yaml:"auto_update" json:"auto_update"`
CheckOnStart bool `yaml:"check_on_start" json:"check_on_start"`
Language string `yaml:"language" json:"language"`
KeyboardLayout string `yaml:"keyboard_layout" json:"keyboard_layout"`
} `yaml:"preferences" json:"preferences"`
}
type AIProvider struct {
Name string `yaml:"name" json:"name"`
APIKey string `yaml:"api_key,omitempty" json:"api_key,omitempty"`
BaseURL string `yaml:"base_url,omitempty" json:"base_url,omitempty"`
Model string `yaml:"model" json:"model"`
Active bool `yaml:"active" json:"active"`
}
type ToolConfig struct {
Name string `yaml:"name" json:"name"`
Installed bool `yaml:"installed" json:"installed"`
Version string `yaml:"version" json:"version"`
AutoUpdate bool `yaml:"auto_update" json:"auto_update"`
}
type SSHConnection struct {
Name string `yaml:"name" json:"name"`
Host string `yaml:"host" json:"host"`
Port int `yaml:"port" json:"port"`
User string `yaml:"user" json:"user"`
Password string `yaml:"password,omitempty" json:"password,omitempty"`
KeyPath string `yaml:"key_path,omitempty" json:"key_path,omitempty"`
}
type MuyueConfig struct {
Version string `yaml:"version" json:"version"`
Profile Profile `yaml:"profile" json:"profile"`
AI struct {
Providers []AIProvider `yaml:"providers" json:"providers"`
} `yaml:"ai" json:"ai"`
Tools []ToolConfig `yaml:"tools" json:"tools"`
BMAD struct {
Installed bool `yaml:"installed" json:"installed"`
Version string `yaml:"version" json:"version"`
Global bool `yaml:"global" json:"global"`
} `yaml:"bmad" json:"bmad"`
Terminal struct {
CustomPrompt bool `yaml:"custom_prompt" json:"custom_prompt"`
PromptTheme string `yaml:"prompt_theme" json:"prompt_theme"`
SSH []SSHConnection `yaml:"ssh" json:"ssh"`
FontSize int `yaml:"font_size" json:"font_size"`
FontFamily string `yaml:"font_family" json:"font_family"`
Theme string `yaml:"theme" json:"theme"`
} `yaml:"terminal" json:"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: "mimo",
Model: "MiMo-V2.5-Pro",
BaseURL: "https://token-plan-ams.xiaomimimo.com/v1",
Active: false,
},
{
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
}