package projects import ( "bufio" "os" "path/filepath" "sort" "strconv" "strings" "codex-agent-manager/internal/codexhome" ) type Store struct { CodexHome string } func (s Store) List() ([]Project, error) { configPath, err := codexhome.ResolveInside(s.CodexHome, "config.toml") if err != nil { return nil, err } data, err := os.ReadFile(configPath) if err != nil { if os.IsNotExist(err) { return []Project{}, nil } return nil, err } projects := parseProjectsConfig(string(data), configPath) sort.Slice(projects, func(i, j int) bool { return projects[i].Path < projects[j].Path }) for i := range projects { if projects[i].DisplayName == "" { projects[i].DisplayName = filepath.Base(projects[i].Path) } if info, err := os.Stat(projects[i].Path); err == nil && info.IsDir() { projects[i].DirectoryExists = true } } return projects, nil } func parseProjectsConfig(input string, sourcePath string) []Project { var result []Project var current *Project scanner := bufio.NewScanner(strings.NewReader(input)) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if line == "" || strings.HasPrefix(line, "#") { continue } if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { current = nil section := strings.TrimSuffix(strings.TrimPrefix(line, "["), "]") if !strings.HasPrefix(section, "projects.") { continue } path, err := strconv.Unquote(strings.TrimPrefix(section, "projects.")) if err != nil || path == "" { continue } result = append(result, Project{ Path: path, Source: SourceEvidence{Kind: "config_toml", Path: sourcePath, Confidence: "high"}, TrustLevel: "unknown", }) current = &result[len(result)-1] continue } if current == nil { continue } key, raw, ok := strings.Cut(line, "=") if !ok { continue } value, err := strconv.Unquote(strings.TrimSpace(raw)) if err != nil { continue } switch strings.TrimSpace(key) { case "trust_level": current.TrustLevel = value case "display_name": current.DisplayName = value } } return result }