using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; namespace Gott.Services; public class SkillService { private readonly string _skillsDir; public string SkillsDir => _skillsDir; public SkillService(string dataDir) { _skillsDir = Path.Combine(dataDir, "skills"); Directory.CreateDirectory(_skillsDir); } /// /// Scannt den Skills-Ordner und gibt alle verfügbaren Skills zurück. /// public List GetAllSkills() { var skills = new List(); if (!Directory.Exists(_skillsDir)) return skills; foreach (var dir in Directory.GetDirectories(_skillsDir)) { var skill = LoadSkill(dir); if (skill != null) skills.Add(skill); } return skills.OrderBy(s => s.Name).ToList(); } /// /// Lädt einen einzelnen Skill anhand seines Slugs. /// public SkillInfo GetSkill(string slug) { var dir = Path.Combine(_skillsDir, slug); if (!Directory.Exists(dir)) return null; return LoadSkill(dir); } /// /// Liest die PROMPT.md eines Skills. Gibt null zurück wenn nicht vorhanden. /// Nur Skills mit PROMPT.md können im System-Prompt verwendet werden. /// public string GetSkillPrompt(string slug) { var promptMd = Path.Combine(_skillsDir, slug, "PROMPT.md"); if (!File.Exists(promptMd)) return null; return File.ReadAllText(promptMd); } private SkillInfo LoadSkill(string dir) { var slug = Path.GetFileName(dir); var metaFile = Path.Combine(dir, "_meta.json"); var skillMd = Path.Combine(dir, "SKILL.md"); var promptMd = Path.Combine(dir, "PROMPT.md"); var info = new SkillInfo { Slug = slug, Path = Path.GetFullPath(dir), HasSkillMd = File.Exists(skillMd), HasPromptMd = File.Exists(promptMd) }; // Name/Description aus SKILL.md Frontmatter lesen if (File.Exists(skillMd)) { var content = File.ReadAllText(skillMd); var frontmatter = ParseFrontmatter(content); if (frontmatter.TryGetValue("name", out var name)) info.Name = name; if (frontmatter.TryGetValue("description", out var desc)) info.Description = desc; if (frontmatter.TryGetValue("version", out var ver)) info.Version = ver; } // _meta.json als Fallback/Ergänzung if (File.Exists(metaFile)) { try { var json = File.ReadAllText(metaFile); using var doc = JsonDocument.Parse(json); var root = doc.RootElement; if (string.IsNullOrEmpty(info.Name) && root.TryGetProperty("name", out var n)) info.Name = n.GetString(); if (string.IsNullOrEmpty(info.Version) && root.TryGetProperty("version", out var v)) info.Version = v.GetString(); if (root.TryGetProperty("slug", out var s) && !string.IsNullOrEmpty(s.GetString())) info.Slug = s.GetString(); } catch { /* Fehler in _meta.json ignorieren */ } } // Fallback: Name = Slug if (string.IsNullOrEmpty(info.Name)) info.Name = slug; return info; } /// /// Parst einen einfachen YAML-Frontmatter-Block (--- ... ---) aus einer Markdown-Datei. /// private Dictionary ParseFrontmatter(string content) { var result = new Dictionary(); if (!content.StartsWith("---")) return result; var end = content.IndexOf("---", 3, StringComparison.Ordinal); if (end < 0) return result; var frontmatter = content.Substring(3, end - 3).Trim(); foreach (var line in frontmatter.Split('\n')) { var colon = line.IndexOf(':'); if (colon < 0) continue; var key = line.Substring(0, colon).Trim(); var value = line.Substring(colon + 1).Trim(); result[key] = value; } return result; } } public class SkillInfo { public string Slug { get; set; } public string Name { get; set; } public string Description { get; set; } public string Version { get; set; } public string Path { get; set; } public bool HasSkillMd { get; set; } public bool HasPromptMd { get; set; } }