diff --git a/Directory.Packages.props b/Directory.Packages.props
index baecd84..4861562 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -5,14 +5,10 @@
-
-
-
-
-
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 1375141..0f04862 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ This is designed to helps to keep track of git branches and their azure devops w
- Extract the release zip file to any folder
- Add the "install" folder to your path environment variables
- Edit `appSettings.json` in the "install folder" and enter your `AzureDevopsOrgUri` (where your work items are stored)
-- Start `RepoCleaner.exe --config` to enter _config mode_ and enter an azure devops personal access token that has at least _Work Items_ and _Code_ read access
+- Start `RepoCleaner.exe config` to enter _config mode_ and enter an azure devops personal access token that has at least _Work Items_ and _Code_ read access
- [Configure you powershell to use UTF8 encoding](docs/doc.md#powershell), otherwise some icons will not be able to be displayed
## Quick Usage
diff --git a/docs/doc.md b/docs/doc.md
index c1f121f..7959cf6 100644
--- a/docs/doc.md
+++ b/docs/doc.md
@@ -53,7 +53,7 @@ To access work items and code on a azure devops server add an azure devops perso
The token will be stored locally on your computer as a generic Windows credential with the name _Develix:RepoCleanerAzureDevopsToken_.
```ps
-RepoCleaner.exe --config
+RepoCleaner.exe config
```

@@ -108,7 +108,7 @@ The default colors are approximately
| Color | Status |
| ----- | -------------------------------------- |
| ⚪ | Work item is in a _not started_ status |
-| 🔵 | Work item is in a _precessing_ status |
+| 🔵 | Work item is in a _processing_ status |
| 🟢 | Work item is in a _done_ status |
| ⚫ | No work item found |
diff --git a/src/RepoCleaner/App.cs b/src/RepoCleaner/App.cs
deleted file mode 100644
index f5b3c8f..0000000
--- a/src/RepoCleaner/App.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-using Develix.RepoCleaner.ConsoleComponents;
-using Develix.RepoCleaner.Model;
-using Develix.RepoCleaner.Store.ConsoleSettingsUseCase;
-using Develix.RepoCleaner.Store.RepositoryInfoUseCase;
-using Fluxor;
-using Spectre.Console;
-
-namespace Develix.RepoCleaner;
-
-public class App
-{
- private const string CredentialName = "Develix:RepoCleanerAzureDevopsToken";
-
- private readonly IStore store;
- private readonly IDispatcher dispatcher;
- private readonly IState repositoryInfoState;
- private readonly IState consoleSettingsState;
-
- public App(
- IStore store,
- IDispatcher dispatcher,
- IState repositoryInfoState,
- IState consoleSettingsState)
- {
- this.store = store ?? throw new ArgumentNullException(nameof(store));
- this.dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
- this.repositoryInfoState = repositoryInfoState ?? throw new ArgumentNullException(nameof(repositoryInfoState));
- this.consoleSettingsState = consoleSettingsState ?? throw new ArgumentNullException(nameof(consoleSettingsState));
- }
-
- public async Task Run(ConsoleArguments consoleArguments, AppSettings appSettings)
- {
- try
- {
- await store.InitializeAsync();
-
- if (consoleArguments.Config)
- {
- Config.Show(CredentialName, consoleSettingsState, dispatcher);
- return;
- }
-
- await InitConsole(consoleArguments, appSettings);
- LogErrors(repositoryInfoState);
- ShowOverviewTable();
- if (consoleSettingsState.Value.ShowDeletePrompt)
- ShowDeletePrompt();
-
- }
- catch (Exception ex)
- {
- AnsiConsole.WriteException(ex);
- }
- }
-
- private async Task InitConsole(ConsoleArguments consoleArguments, AppSettings appSettings)
- {
- var initConsole = new Init(CredentialName, dispatcher, repositoryInfoState, store);
- await initConsole.Execute(consoleArguments, appSettings);
- }
-
- private static void LogErrors(IState repositoryInfoState)
- {
- foreach (var error in repositoryInfoState.Value.ErrorMessages)
- AnsiConsole.MarkupLine(error);
- }
-
- private void ShowOverviewTable()
- {
- var overviewTable = new OverviewTable(repositoryInfoState.Value, consoleSettingsState.Value);
- AnsiConsole.Write(overviewTable.GetOverviewTable());
- }
-
- private void ShowDeletePrompt()
- {
- var branchesToDelete = Delete.Prompt(repositoryInfoState);
- Delete.Execute(branchesToDelete, repositoryInfoState);
- }
-}
diff --git a/src/RepoCleaner/ConsoleComponents/Cli/AzdoClient.cs b/src/RepoCleaner/ConsoleComponents/Cli/AzdoClient.cs
new file mode 100644
index 0000000..ccba182
--- /dev/null
+++ b/src/RepoCleaner/ConsoleComponents/Cli/AzdoClient.cs
@@ -0,0 +1,43 @@
+using Develix.AzureDevOps.Connector.Service;
+using Develix.CredentialStore.Win32;
+using Develix.Essentials.Core;
+using Develix.RepoCleaner.Model;
+
+namespace Develix.RepoCleaner.ConsoleComponents.Cli;
+
+internal static class AzdoClient
+{
+ public const string CredentialName = "Develix:RepoCleanerAzureDevopsToken";
+
+ public static async Task Login(
+ IReposService reposService,
+ IWorkItemService workItemService,
+ RepoCleanerSettings settings,
+ AppSettings appSettings)
+ {
+ IEnumerable> loginActions = settings.IncludePullRequests
+ ? [LoginWorkItemService(workItemService, appSettings.AzureDevopsOrgUri), LoginReposService(reposService, appSettings.AzureDevopsOrgUri)]
+ : [LoginWorkItemService(workItemService, appSettings.AzureDevopsOrgUri)];
+
+ var loginResults = await Task.WhenAll(loginActions);
+ if (loginResults.Where(r => !r.Valid).Select(r => r.Message).ToList() is { Count: >= 1 } errorMessages)
+ return Result.Fail($"Login failed. {Environment.NewLine}{string.Join(Environment.NewLine, errorMessages)}");
+ return Result.Ok();
+ }
+
+ private static async Task LoginWorkItemService(IWorkItemService workItemService, Uri azureDevopsUri)
+ {
+ var credential = CredentialManager.Get(CredentialName);
+ return credential.Valid
+ ? await workItemService.Initialize(azureDevopsUri, credential.Value.Password!)
+ : Result.Fail(credential.Message);
+ }
+
+ private static async Task LoginReposService(IReposService reposService, Uri azureDevopsUri)
+ {
+ var credential = CredentialManager.Get(CredentialName);
+ return credential.Valid
+ ? await reposService.Initialize(azureDevopsUri, credential.Value.Password!)
+ : Result.Fail(credential.Message);
+ }
+}
diff --git a/src/RepoCleaner/ConsoleComponents/Cli/ConfigCommand.cs b/src/RepoCleaner/ConsoleComponents/Cli/ConfigCommand.cs
new file mode 100644
index 0000000..64a4d8f
--- /dev/null
+++ b/src/RepoCleaner/ConsoleComponents/Cli/ConfigCommand.cs
@@ -0,0 +1,31 @@
+using Develix.CredentialStore.Win32;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace Develix.RepoCleaner.ConsoleComponents.Cli;
+
+internal class ConfigCommand : Command
+{
+ public override int Execute(CommandContext context)
+ {
+ var prompt = new TextPrompt("Enter [green]azure devops token[/]")
+ .PromptStyle("red")
+ .Secret();
+
+ var token = AnsiConsole.Prompt(prompt);
+
+ var credential = new Credential("token", token, AzdoClient.CredentialName);
+ var crudResult = CredentialManager.CreateOrUpdate(credential);
+
+ if (!crudResult.Valid)
+ {
+ var message = $"""
+ Storing credentials failed.
+ {crudResult.Message}";
+ """;
+ AnsiConsole.WriteLine(message);
+ return 1;
+ }
+ return 0;
+ }
+}
diff --git a/src/RepoCleaner/ConsoleComponents/Cli/Infrastructure/TypeRegistrar.cs b/src/RepoCleaner/ConsoleComponents/Cli/Infrastructure/TypeRegistrar.cs
new file mode 100644
index 0000000..f872b6d
--- /dev/null
+++ b/src/RepoCleaner/ConsoleComponents/Cli/Infrastructure/TypeRegistrar.cs
@@ -0,0 +1,27 @@
+using Microsoft.Extensions.DependencyInjection;
+using Spectre.Console.Cli;
+
+namespace Develix.RepoCleaner.ConsoleComponents.Cli.Infrastructure;
+
+public sealed class TypeRegistrar(IServiceCollection builder) : ITypeRegistrar
+{
+ private readonly IServiceCollection builder = builder;
+
+ public ITypeResolver Build() => new TypeResolver(builder.BuildServiceProvider());
+
+ public void Register(Type service, Type implementation)
+ {
+ _ = builder.AddSingleton(service, implementation);
+ }
+
+ public void RegisterInstance(Type service, object implementation)
+ {
+ _ = builder.AddSingleton(service, implementation);
+ }
+
+ public void RegisterLazy(Type service, Func