diff --git a/src/AltGen.Console/AltGen.Console.csproj b/src/AltGen.Console/AltGen.Console.csproj
index b8472d7..d51b937 100644
--- a/src/AltGen.Console/AltGen.Console.csproj
+++ b/src/AltGen.Console/AltGen.Console.csproj
@@ -9,8 +9,11 @@
+
+
+
diff --git a/src/AltGen.Console/Common/HostBuilderExtensions.cs b/src/AltGen.Console/Common/HostBuilderExtensions.cs
new file mode 100644
index 0000000..2447aa0
--- /dev/null
+++ b/src/AltGen.Console/Common/HostBuilderExtensions.cs
@@ -0,0 +1,11 @@
+namespace AltGen.Console.Common;
+
+static class HostBuilderExtensions
+{
+ public static CommandApp BuildApp(this IHostBuilder builder)
+ {
+ var registrar = new TypeRegistrar(builder);
+ var app = new CommandApp(registrar);
+ return app;
+ }
+}
\ No newline at end of file
diff --git a/src/AltGen.Console/Program.cs b/src/AltGen.Console/Program.cs
index 7b33f13..e31f069 100644
--- a/src/AltGen.Console/Program.cs
+++ b/src/AltGen.Console/Program.cs
@@ -4,11 +4,159 @@
// and then post the image to our API
// get the response and display the alt text
-using Microsoft.Extensions.Hosting;
+using System.ComponentModel;
+using System.IO.Abstractions;
+using System.Net.Http.Headers;
+
+using AltGen.Console.Common;
+
+using Spectre.Console;
await Host.CreateDefaultBuilder(args)
- .ConfigureServices(static (_, services) => { })
- .Build() // TODO: Need special extension for integrating host with Spectre.Console
- .RunAsync();
+ .ConfigureServices(static (_, services) =>
+ {
+ services.AddSingleton();
+ services.AddSingleton(AnsiConsole.Console);
+ services.AddHttpClient()
+ .AddStandardResilienceHandler();
+ })
+ .BuildApp()
+ .RunAsync(args);
-Console.WriteLine("Hello, World!");
\ No newline at end of file
+sealed class GenerateCommand(
+ IAnsiConsole console,
+ IFileSystem fileSystem,
+ IAltGenService altGenService
+) : AsyncCommand
+{
+
+ readonly IAnsiConsole _console = console;
+ readonly IFileSystem _fileSystem = fileSystem;
+ readonly IAltGenService _altGenService = altGenService;
+
+ public class Settings(IFileSystem fileSystem) : CommandSettings
+ {
+ readonly Dictionary _imageTypes = new()
+ {
+ [".jpeg"] = "image/jpeg",
+ [".jpg"] = "image/jpeg",
+ [".png"] = "image/png"
+ };
+
+ readonly List _providers = [
+ "gemini",
+ ];
+
+ readonly IFileSystem _fileSystem = fileSystem;
+
+ [CommandArgument(1, "")]
+ [Description("The provider to use for generating alt text.")]
+ public string Provider { get; init; } = string.Empty;
+
+ [CommandArgument(2, "")]
+ [Description("The key for the provider.")]
+ public string Key { get; init; } = string.Empty;
+
+ [CommandArgument(3, "")]
+ [Description("The path to the image to generate alt text for.")]
+ public string Path { get; init; } = string.Empty;
+
+ public string ContentType => _imageTypes[_fileSystem.Path.GetExtension(Path)];
+
+ public override ValidationResult Validate()
+ {
+ if (_providers.Contains(Provider) is false)
+ {
+ return ValidationResult.Error($"The provider '{Provider}' is not supported.");
+ }
+
+ var pathExists = _fileSystem.File.Exists(Path);
+
+ if (pathExists is false)
+ {
+ return ValidationResult.Error($"The path '{Path}' does not exist.");
+ }
+
+ var extension = _fileSystem.Path.GetExtension(Path);
+
+ if (_imageTypes.ContainsKey(extension) is false)
+ {
+ return ValidationResult.Error($"The file '{Path}' is not a valid image file.");
+ }
+
+ return ValidationResult.Success();
+ }
+ }
+
+ public override async Task ExecuteAsync(CommandContext context, Settings settings)
+ {
+ var fileName = _fileSystem.Path.GetFileName(settings.Path);
+ var image = await _fileSystem.File.ReadAllBytesAsync(settings.Path);
+
+ var altText = await _altGenService.GenerateAltTextAsync(
+ settings.Provider,
+ settings.Key,
+ fileName,
+ image,
+ settings.ContentType
+ );
+
+ _console.MarkupLine($"[bold]Alt Text:[/] {altText}");
+
+ return 0;
+ }
+}
+
+interface IAltGenService
+{
+ Task GenerateAltTextAsync(
+ string provider,
+ string key,
+ string fileName,
+ byte[] image,
+ string contentType
+ );
+}
+
+sealed class AltGenService(HttpClient client) : IAltGenService
+{
+ readonly HttpClient _client = client;
+
+ public async Task GenerateAltTextAsync(
+ string provider,
+ string key,
+ string fileName,
+ byte[] image,
+ string contentType
+ )
+ {
+ var byteContent = new ByteArrayContent(image);
+ byteContent.Headers.ContentType = new MediaTypeHeaderValue(contentType);
+
+ // TODO: Need to have proper URL
+ // have constants generated at build time
+ // that point to proper API URL
+ var request = new HttpRequestMessage(HttpMethod.Post, "altgen")
+ {
+ Content = new MultipartFormDataContent
+ {
+ { new StringContent(provider), "provider" },
+ { new StringContent(key), "providerKey" },
+ { new ByteArrayContent(image), "file", fileName }
+ }
+ };
+
+ var response = await _client.SendAsync(request);
+
+ if (response.IsSuccessStatusCode is false)
+ {
+ // TODO: Use accurate exception
+ throw new InvalidDataException("Failed to generate alt text.");
+ }
+
+ // TODO: Deserialize the response
+ var altText = await response.Content.ReadAsStringAsync();
+
+ return altText;
+ }
+}
\ No newline at end of file