122 lines
4.3 KiB
C#
122 lines
4.3 KiB
C#
using System.CommandLine;
|
|
using System.Text.Json;
|
|
using Spectre.Console;
|
|
using Toak.Core.Skills;
|
|
using Toak.Serialization;
|
|
|
|
namespace Toak.Commands;
|
|
|
|
public static class SkillCommand
|
|
{
|
|
public static Command CreateCommand(Option<bool> verboseOption)
|
|
{
|
|
var skillCmd = new Command("skill", "Manage dynamic skills (list, add, remove)");
|
|
|
|
var listCmd = new Command("list", "List all available skills");
|
|
listCmd.SetHandler(ExecuteListAsync);
|
|
skillCmd.AddCommand(listCmd);
|
|
|
|
// Add
|
|
var addCmd = new Command("add", "Add a new skill interactively");
|
|
addCmd.SetHandler(ExecuteAddAsync);
|
|
skillCmd.AddCommand(addCmd);
|
|
|
|
// Remove
|
|
var removeCmd = new Command("remove", "Remove a skill");
|
|
var nameArg = new Argument<string>("name", "The name of the skill to remove");
|
|
removeCmd.AddArgument(nameArg);
|
|
removeCmd.SetHandler((name) => ExecuteRemoveAsync(name), nameArg);
|
|
skillCmd.AddCommand(removeCmd);
|
|
|
|
return skillCmd;
|
|
}
|
|
|
|
private static async Task ExecuteListAsync()
|
|
{
|
|
SkillRegistry.Initialize();
|
|
|
|
var table = new Table().Border(TableBorder.Rounded);
|
|
table.AddColumn("Name");
|
|
table.AddColumn("Action");
|
|
table.AddColumn("Hotwords");
|
|
table.AddColumn("Description");
|
|
table.AddColumn("ScriptPath");
|
|
|
|
foreach (var skill in SkillRegistry.AllSkills)
|
|
{
|
|
var def = JsonSerializer.Deserialize<SkillDefinition>(
|
|
File.ReadAllText(Path.Combine(SkillRegistry.SkillsDirectory, $"{skill.Name.ToLowerInvariant()}.json")),
|
|
AppJsonSerializerContext.Default.SkillDefinition);
|
|
|
|
if (def == null) continue;
|
|
|
|
table.AddRow(
|
|
$"[green]{def.Name}[/]",
|
|
!string.IsNullOrEmpty(def.Action) ? $"[yellow]{def.Action}[/]" : "type",
|
|
$"[blue]{string.Join(", ", def.Hotwords)}[/]",
|
|
def.Description,
|
|
def.Action == "script" ? $"[dim]{def.ScriptPath ?? "None"}[/]" : "-"
|
|
);
|
|
}
|
|
|
|
AnsiConsole.Write(table);
|
|
}
|
|
|
|
private static async Task ExecuteAddAsync()
|
|
{
|
|
AnsiConsole.MarkupLine("[bold blue]Add a new Dynamic Skill[/]");
|
|
|
|
var name = AnsiConsole.Ask<string>("Skill [green]Name[/]:");
|
|
var description = AnsiConsole.Ask<string>("Skill [green]Description[/]:");
|
|
|
|
var hotwordsStr = AnsiConsole.Ask<string>("Comma-separated [green]Hotwords[/] (e.g. 'System my skill, System do skill'):");
|
|
var hotwords = hotwordsStr.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
|
|
|
var action = AnsiConsole.Prompt(
|
|
new SelectionPrompt<string>()
|
|
.Title("What is the [green]Action[/] type?")
|
|
.AddChoices(new[] { "type", "script" })
|
|
);
|
|
|
|
string? scriptPath = null;
|
|
if (action == "script")
|
|
{
|
|
scriptPath = AnsiConsole.Ask<string>("Enter the absolute [green]Script Path[/] (~/ is allowed):");
|
|
}
|
|
|
|
var systemPrompt = AnsiConsole.Ask<string>("Enter the [green]System Prompt[/] (use {transcript} to inject the user's speech):");
|
|
|
|
var def = new SkillDefinition
|
|
{
|
|
Name = name,
|
|
Description = description,
|
|
Hotwords = hotwords,
|
|
Action = action,
|
|
ScriptPath = scriptPath,
|
|
SystemPrompt = systemPrompt
|
|
};
|
|
|
|
SkillRegistry.Initialize(); // ensure dir exists
|
|
var filename = Path.Combine(SkillRegistry.SkillsDirectory, $"{name.ToLowerInvariant()}.json");
|
|
var json = JsonSerializer.Serialize(def, AppJsonSerializerContext.Default.SkillDefinition);
|
|
File.WriteAllText(filename, json);
|
|
|
|
AnsiConsole.MarkupLine($"[bold green]Success![/] Skill '{name}' saved to {filename}");
|
|
}
|
|
|
|
private static async Task ExecuteRemoveAsync(string name)
|
|
{
|
|
SkillRegistry.Initialize();
|
|
var filename = Path.Combine(SkillRegistry.SkillsDirectory, $"{name.ToLowerInvariant()}.json");
|
|
if (File.Exists(filename))
|
|
{
|
|
File.Delete(filename);
|
|
AnsiConsole.MarkupLine($"[bold green]Success![/] Deleted skill '{name}' ({filename})");
|
|
}
|
|
else
|
|
{
|
|
AnsiConsole.MarkupLine($"[bold red]Error:[/] Skill file not found: {filename}");
|
|
}
|
|
}
|
|
}
|