using ClaudeDo.Data.Models; namespace ClaudeDo.Worker.Agents; public sealed class AgentFileService { private readonly string _agentsDir; public AgentFileService(string agentsDir) { _agentsDir = agentsDir; } public Task> ScanAsync(CancellationToken ct = default) { var agents = new List(); if (!Directory.Exists(_agentsDir)) return Task.FromResult(agents); foreach (var file in Directory.EnumerateFiles(_agentsDir, "*.md")) { ct.ThrowIfCancellationRequested(); var (name, description) = ParseFrontmatter(file); agents.Add(new AgentInfo(name, description, file)); } agents.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase)); return Task.FromResult(agents); } public async Task ReadAsync(string path, CancellationToken ct = default) { return await File.ReadAllTextAsync(path, ct); } public async Task WriteAsync(string path, string content, CancellationToken ct = default) { var dir = Path.GetDirectoryName(path); if (dir is not null) Directory.CreateDirectory(dir); await File.WriteAllTextAsync(path, content, ct); } public Task DeleteAsync(string path, CancellationToken ct = default) { ct.ThrowIfCancellationRequested(); if (File.Exists(path)) File.Delete(path); return Task.CompletedTask; } private static (string name, string description) ParseFrontmatter(string filePath) { var fileName = Path.GetFileNameWithoutExtension(filePath); string name = fileName; string description = ""; try { using var reader = new StreamReader(filePath); var firstLine = reader.ReadLine(); if (firstLine?.Trim() != "---") return (name, description); while (reader.ReadLine() is { } line) { if (line.Trim() == "---") break; if (line.StartsWith("name:")) name = line["name:".Length..].Trim(); else if (line.StartsWith("description:")) description = line["description:".Length..].Trim(); } } catch { /* Can't read file -- use filename fallback */ } return (name, description); } }