package user import ( "bytes" "fmt" "os" "path/filepath" "strings" "gopkg.in/yaml.v3" ) type Config struct { Name string `yaml:"name"` Description string `yaml:"description"` Provider string `yaml:"provider"` Model string `yaml:"model"` APIKeyEnv string `yaml:"api_key_env"` AvatarColor string `yaml:"avatar_color"` Preferences Preferences `yaml:"preferences"` } type Preferences struct { Language string `yaml:"language"` Tone string `yaml:"tone"` } type User struct { Config Config Profile string // content from PROFILE.md Dir string // users// } const DefaultUser = "default" func Load(dir string) (*User, error) { userDir := filepath.Join(dir, DefaultUser) userMD, err := os.ReadFile(filepath.Join(userDir, "USER.md")) if err != nil { return nil, fmt.Errorf("read USER.md: %w", err) } cfg, err := parseFrontmatter(userMD) if err != nil { return nil, fmt.Errorf("parse USER.md: %w", err) } profile, _ := os.ReadFile(filepath.Join(userDir, "PROFILE.md")) if cfg.Provider == "" { cfg.Provider = "deepseek" } if cfg.APIKeyEnv == "" { cfg.APIKeyEnv = "DEEPSEEK_API_KEY" } if cfg.AvatarColor == "" { cfg.AvatarColor = "#5865F2" } return &User{Config: cfg, Profile: string(profile), Dir: userDir}, nil } func parseFrontmatter(data []byte) (Config, error) { var cfg Config if !bytes.HasPrefix(data, []byte("---")) { return cfg, fmt.Errorf("missing frontmatter") } parts := bytes.SplitN(data, []byte("---"), 3) if len(parts) < 3 { return cfg, fmt.Errorf("invalid frontmatter") } return cfg, yaml.Unmarshal(parts[1], &cfg) } func (u *User) SaveProfile(content string) error { return os.WriteFile(filepath.Join(u.Dir, "PROFILE.md"), []byte(content), 0644) } func (u *User) SaveConfig(cfg Config) error { u.Config = cfg var buf bytes.Buffer buf.WriteString("---\n") enc := yaml.NewEncoder(&buf) enc.Encode(cfg) buf.WriteString("---\n") return os.WriteFile(filepath.Join(u.Dir, "USER.md"), buf.Bytes(), 0644) } func (u *User) BuildUserXML() string { var xml strings.Builder xml.WriteString("\n") xml.WriteString(fmt.Sprintf(" %s\n", u.Config.Name)) xml.WriteString(fmt.Sprintf(" %s\n", u.Config.Description)) if u.Profile != "" { xml.WriteString(" \n") xml.WriteString(u.Profile) xml.WriteString(" \n") } xml.WriteString("") return xml.String() } func (u *User) GetProvider() string { return u.Config.Provider } func (u *User) GetModel() string { return u.Config.Model } func (u *User) GetAPIKeyEnv() string { return u.Config.APIKeyEnv } func (u *User) GetName() string { return u.Config.Name } func (u *User) GetAvatarColor() string { return u.Config.AvatarColor } func Exists(dir string) bool { _, err := os.Stat(filepath.Join(dir, DefaultUser, "USER.md")) return err == nil }