using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; using Toak.Serialization; namespace Toak.Core.Skills; public static class SkillRegistry { public static List AllSkills = new List(); public static string SkillsDirectory => Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config", "toak", "skills"); public static void Initialize() { if (!Directory.Exists(SkillsDirectory)) { Directory.CreateDirectory(SkillsDirectory); CreateDefaultSkills(); } AllSkills.Clear(); foreach (var file in Directory.GetFiles(SkillsDirectory, "*.json")) { try { string json = File.ReadAllText(file); var def = JsonSerializer.Deserialize(json, AppJsonSerializerContext.Default.SkillDefinition); if (def != null) { AllSkills.Add(new DynamicSkill(def)); } } catch (Exception ex) { Logger.LogDebug($"Failed to load skill from {file}: {ex.Message}"); } } } public static ISkill? DetectSkill(string transcript, IEnumerable activeSkillNames) { if (AllSkills.Count == 0) Initialize(); var activeSkills = AllSkills.Where(s => activeSkillNames.Contains(s.Name, StringComparer.OrdinalIgnoreCase)).ToList(); string normalizedTranscript = transcript.Trim(); foreach (var skill in activeSkills) { foreach (var hotword in skill.Hotwords) { if (normalizedTranscript.StartsWith(hotword, StringComparison.OrdinalIgnoreCase)) { return skill; } } } return null; } private static void CreateDefaultSkills() { var defaults = new List { new SkillDefinition { Name = "Terminal", Description = "Executes the spoken command in your shell.", Hotwords = new[] { "System terminal", "System run", "System execute" }, Action = "script", ScriptPath = "~/.config/toak/skills/terminal_action.sh", SystemPrompt = "You are a Linux terminal expert. Translate the user's request into a single, valid bash command. Output ONLY the raw command, no formatting, no markdown." }, new SkillDefinition { Name = "Translate", Description = "Translates the spoken text into another language on the fly.", Hotwords = new[] { "System translate to", "System translate into" }, Action = "type", SystemPrompt = @"You are an expert translator. The user wants to translate the following text. The first few words identify the target language (e.g. 'Translate to Spanish:', 'Translate into Hungarian:'). Translate the REST of the transcript into that target language. Output ONLY the final translated text. Do not include markdown, explanations, or quotes." }, new SkillDefinition { Name = "Professional", Description = "Rewrites text into a formal, articulate tone.", Hotwords = new[] { "System professional", "System formalize", "System formal" }, Action = "type", SystemPrompt = "Rewrite the following text to be articulate and formal. Do not add any conversational filler. Text: {transcript}" }, new SkillDefinition { Name = "Summary", Description = "Provides a direct, crisp summary of the dictation.", Hotwords = new[] { "System summary", "System concise", "System summarize" }, Action = "type", SystemPrompt = "Summarize the following text to be as concise and direct as possible. Remove all fluff. Text: {transcript}" } }; foreach (var def in defaults) { string filename = Path.Combine(SkillsDirectory, $"{def.Name.ToLowerInvariant()}.json"); string json = JsonSerializer.Serialize(def, AppJsonSerializerContext.Default.SkillDefinition); File.WriteAllText(filename, json); } // Create the default terminal wrapper script if it doesn't exist string scriptPath = Path.Combine(SkillsDirectory, "terminal_action.sh"); if (!File.Exists(scriptPath)) { File.WriteAllText(scriptPath, "#!/bin/bash\n\n# Terminal skill wrapper script.\n# The LLM output is passed as the first argument ($1).\nx-terminal-emulator -e bash -c \"$1; exec bash\"\n"); // Try to make it executable try { System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = "chmod", Arguments = $"+x \"{scriptPath}\"", CreateNoWindow = true, UseShellExecute = false })?.WaitForExit(); } catch { } } } }