diff --git a/src/Piwigo.Client.Cli/BaseOptions.cs b/src/Piwigo.Client.Cli/BaseOptions.cs
new file mode 100644
index 0000000..d0852aa
--- /dev/null
+++ b/src/Piwigo.Client.Cli/BaseOptions.cs
@@ -0,0 +1,15 @@
+using CommandLine;
+
+namespace Piwigo.Client.Cli;
+
+internal class BaseOptions
+{
+ [Option('s', "server", Required = true, HelpText = "Piwigo server url")]
+ public string ServerUrl { get; set; } = null!;
+
+ [Option('u', "user", Required = true, HelpText = "Username to log into piwigo")]
+ public string UserName { get; set; } = null!;
+
+ [Option('p', "password", Required = true, HelpText = "Password to log into piwigo")]
+ public string Password { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/src/Piwigo.Client.Cli/EventIds.cs b/src/Piwigo.Client.Cli/EventIds.cs
new file mode 100644
index 0000000..6069e1d
--- /dev/null
+++ b/src/Piwigo.Client.Cli/EventIds.cs
@@ -0,0 +1,11 @@
+using Microsoft.Extensions.Logging;
+
+namespace Piwigo.Client.Cli;
+
+internal static class EventIds
+{
+ public static EventId ActionSucceeded { get; } = new(0, "action completed successfully");
+ public static EventId Upload { get; } = new(1, "upload an image");
+ public static EventId SystemError { get; } = new(-1, "system error");
+ public static EventId PiwigoError { get; } = new(-2, "piwigo error");
+}
\ No newline at end of file
diff --git a/src/Piwigo.Client.Cli/Piwigo.Client.Cli.csproj b/src/Piwigo.Client.Cli/Piwigo.Client.Cli.csproj
index d4fd98d..6be7118 100644
--- a/src/Piwigo.Client.Cli/Piwigo.Client.Cli.csproj
+++ b/src/Piwigo.Client.Cli/Piwigo.Client.Cli.csproj
@@ -8,8 +8,14 @@
false
+
+
+
+
+
+
diff --git a/src/Piwigo.Client.Cli/Program.cs b/src/Piwigo.Client.Cli/Program.cs
index e5dff12..25610b5 100644
--- a/src/Piwigo.Client.Cli/Program.cs
+++ b/src/Piwigo.Client.Cli/Program.cs
@@ -1,3 +1,8 @@
-// See https://aka.ms/new-console-template for more information
+using CommandLine;
+using Microsoft.Extensions.Logging;
+using Piwigo.Client.Cli;
-Console.WriteLine("Hello, World!");
\ No newline at end of file
+var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole().AddDebug());
+
+return await Parser.Default.ParseArguments(args).MapResult(async opts => await UploadHandler.UploadAndReturnExitCode(opts, loggerFactory),
+ _ => Task.FromResult(-1)); // Invalid arguments
\ No newline at end of file
diff --git a/src/Piwigo.Client.Cli/UploadHandler.cs b/src/Piwigo.Client.Cli/UploadHandler.cs
new file mode 100644
index 0000000..8cc6686
--- /dev/null
+++ b/src/Piwigo.Client.Cli/UploadHandler.cs
@@ -0,0 +1,30 @@
+using Microsoft.Extensions.Logging;
+
+namespace Piwigo.Client.Cli;
+
+internal static class UploadHandler
+{
+ public static async Task UploadAndReturnExitCode(UploadOptions options, ILoggerFactory loggerFactory)
+ {
+ var logger = loggerFactory.CreateLogger(nameof(UploadHandler));
+
+ try
+ {
+ logger.LogInformation(EventIds.Upload, "Uploading {ImagePath}", options.ImagePath);
+ var client = PiwigoClient.CreateClient(options.ServerUrl, options.UserName, options.Password, loggerFactory);
+ var result = await client.UploadImageAsync(new FileInfo(options.ImagePath));
+ logger.LogInformation(EventIds.ActionSucceeded, "Uploaded {ImagePath} with new image id {ImageId}", options.ImagePath, result.ImageId);
+ return EventIds.ActionSucceeded.Id;
+ }
+ catch (PiwigoException ex)
+ {
+ logger.LogCritical(EventIds.PiwigoError, ex, "{Message}", ex.Message);
+ return EventIds.PiwigoError.Id;
+ }
+ catch (Exception ex)
+ {
+ logger.LogCritical(EventIds.SystemError, ex, "{Message}", ex.Message);
+ return EventIds.SystemError.Id;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Piwigo.Client.Cli/UploadOptions.cs b/src/Piwigo.Client.Cli/UploadOptions.cs
new file mode 100644
index 0000000..515a5c7
--- /dev/null
+++ b/src/Piwigo.Client.Cli/UploadOptions.cs
@@ -0,0 +1,12 @@
+using System.Diagnostics.CodeAnalysis;
+using CommandLine;
+
+namespace Piwigo.Client.Cli;
+
+[Verb("upload", HelpText = "Add file contents to the index.")]
+[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "done by command line parser")]
+internal class UploadOptions : BaseOptions
+{
+ [Option('i', "image", Required = true, HelpText = "Path to the image to upload")]
+ public string ImagePath { get; set; } = null!;
+}
\ No newline at end of file