refactor: Implement dynamic skill loading from definitions, replacing hardcoded skills, and add a new skill management command.
This commit is contained in:
@@ -1,19 +1,52 @@
|
||||
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 readonly ISkill[] AllSkills = new ISkill[]
|
||||
public static List<ISkill> AllSkills = new List<ISkill>();
|
||||
|
||||
public static string SkillsDirectory => Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
".config", "toak", "skills");
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
new TerminalSkill(),
|
||||
new TranslateSkill(),
|
||||
new ProfessionalSkill(),
|
||||
new SummarySkill()
|
||||
};
|
||||
|
||||
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<string> activeSkillNames)
|
||||
{
|
||||
var activeSkills = AllSkills.Where(s => activeSkillNames.Contains(s.Name, StringComparer.OrdinalIgnoreCase)).ToList();
|
||||
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)
|
||||
@@ -28,4 +61,71 @@ public static class SkillRegistry
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void CreateDefaultSkills()
|
||||
{
|
||||
var defaults = new List<SkillDefinition>
|
||||
{
|
||||
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 { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user