diff --git a/src/AltGen.Console/Common/HostBuilderExtensions.cs b/src/AltGen.Console/Common/HostBuilderExtensions.cs index 2447aa0..2418f36 100644 --- a/src/AltGen.Console/Common/HostBuilderExtensions.cs +++ b/src/AltGen.Console/Common/HostBuilderExtensions.cs @@ -6,6 +6,9 @@ static class HostBuilderExtensions { var registrar = new TypeRegistrar(builder); var app = new CommandApp(registrar); + + app.Configure(static c => c.AddCommand("config")); + return app; } } \ No newline at end of file diff --git a/src/AltGen.Console/Config/ConfigCommand.cs b/src/AltGen.Console/Config/ConfigCommand.cs new file mode 100644 index 0000000..b9e0c0a --- /dev/null +++ b/src/AltGen.Console/Config/ConfigCommand.cs @@ -0,0 +1,109 @@ + +using Microsoft.Extensions.Configuration; + +namespace AltGen.Console.Config; + +// TODO: We should refactor this so that +// we have a config add and a config remove command +// the config add will have basically the same code +// as below but the config remove will just +// accept a provider identifier and remove it + +sealed class ConfigCommand( + IAnsiConsole console, + IFileSystem fileSystem, + IConfiguration config +) : AsyncCommand +{ + static readonly JsonSerializerOptions JsonOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + WriteIndented = true + }; + readonly IAnsiConsole _console = console; + readonly IFileSystem _fileSystem = fileSystem; + readonly IConfiguration _config = config; + + public class Settings(IFileSystem filesystem) : CommandSettings + { + readonly IFileSystem _fileSystem = filesystem; + + [CommandArgument(1, "")] + [Description("The provider to configure.")] + public string Provider { get; init; } = string.Empty; + + [CommandArgument(2, "")] + [Description("The key for the provider.")] + public string Key { get; init; } = string.Empty; + + [CommandOption("-d|--default")] + [Description("Set the provider as the default.")] + public bool Default { get; init; } + + public string SettingsPath => _fileSystem.Path.Combine(AppContext.BaseDirectory, "appsettings.json"); + } + + public override async Task ExecuteAsync(CommandContext context, Settings settings) + { + var settingsExist = _fileSystem.File.Exists(settings.SettingsPath); + + if (settingsExist is false) + { + var appSettings = new AppSettings([ + new(settings.Provider, settings.Key, settings.Default) + ]); + + var json = JsonSerializer.Serialize(appSettings, JsonOptions); + await _fileSystem.File.WriteAllTextAsync(settings.SettingsPath, json); + _console.MarkupLine($"[bold]{settings.Provider}[/] has been configured."); + return 0; + } + + var existingJson = await _fileSystem.File.ReadAllTextAsync(settings.SettingsPath); + var existingAppSettings = JsonSerializer.Deserialize(existingJson, JsonOptions) + ?? throw new ConfigException("Failed to deserialize settings."); + var updatedAppSettings = existingAppSettings.Update(settings); + var udpatedJson = JsonSerializer.Serialize(updatedAppSettings, JsonOptions); + await _fileSystem.File.WriteAllTextAsync(settings.SettingsPath, udpatedJson); + _console.MarkupLine($"[bold]{settings.Provider}[/] has been configured."); + return 0; + } +} + +record AppSettings(ProviderSettings[] Providers) +{ + public AppSettings Update(ConfigCommand.Settings settings) + { + var updatedProviders = Providers.Select(p => + { + if (p.Provider == settings.Provider) + { + return p with { Key = settings.Key, Default = settings.Default }; + } + + if (p.Provider != settings.Provider && settings.Default) + { + return p with { Default = false }; + } + + return p; + }); + + var provider = Providers.FirstOrDefault(p => p.Provider == settings.Provider); + + if (provider is null) + { + var newProvider = new ProviderSettings(settings.Provider, settings.Key, settings.Default); + return this with { Providers = [.. updatedProviders, newProvider] }; + } + + return this with { Providers = [.. updatedProviders] }; + } +} + +record ProviderSettings(string Provider, string Key, bool Default); + +class ConfigException(string message) : Exception(message) +{ +} \ No newline at end of file diff --git a/src/AltGen.Console/Program.cs b/src/AltGen.Console/Program.cs index ac7c446..48aaed7 100644 --- a/src/AltGen.Console/Program.cs +++ b/src/AltGen.Console/Program.cs @@ -1,16 +1,4 @@ -// TODO: Need to implement config -// command. This should allow -// the user to set default values -// for provider keys and a default -// provider. -// -// For example setting a providers key -// i.e. altgen config gemini mykey -// -// Or setting provider as default -// i.e. altgen config gemini mykey --default - -await Host.CreateDefaultBuilder(args) +await Host.CreateDefaultBuilder(args) .ConfigureLogging(static l => l.ClearProviders()) .ConfigureServices(static (_, services) => { @@ -20,4 +8,4 @@ await Host.CreateDefaultBuilder(args) .AddStandardResilienceHandler(); }) .BuildApp() - .RunAsync(args); + .RunAsync(args); \ No newline at end of file diff --git a/src/AltGen.Console/Usings.cs b/src/AltGen.Console/Usings.cs index 8dab214..0782a80 100644 --- a/src/AltGen.Console/Usings.cs +++ b/src/AltGen.Console/Usings.cs @@ -4,12 +4,13 @@ global using System.Net.Http.Headers; global using System.Text.Json; global using AltGen.Console.Common; -global using AltGen.Console.Generated; +global using AltGen.Console.Config; global using AltGen.Console.Generate; +global using AltGen.Console.Generated; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; global using Spectre.Console; -global using Spectre.Console.Cli; +global using Spectre.Console.Cli; \ No newline at end of file