From 7cddfa64023c748c1a0e28e25b650a8fe266f8d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 18:52:18 +0000 Subject: [PATCH 1/6] Initial plan From e0f974f96347e0c41cf8767b3bef50d1e5b8d9e0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 19:01:01 +0000 Subject: [PATCH 2/6] Add basic project structure with Core and Cli projects Co-authored-by: Maxiimeeb <48930628+Maxiimeeb@users.noreply.github.com> --- .github/PROJECT_STRUCTURE.md | 123 +++++++++++++++ .gitignore | 60 +++++++ SRunner.slnx | 6 + src/Cli/Cli.csproj | 19 +++ src/Cli/InteractiveUI.cs | 293 +++++++++++++++++++++++++++++++++++ src/Cli/Program.cs | 31 ++++ src/Core/Core.csproj | 9 ++ src/Core/ServiceRunner.cs | 37 +++++ 8 files changed, 578 insertions(+) create mode 100644 .github/PROJECT_STRUCTURE.md create mode 100644 .gitignore create mode 100644 SRunner.slnx create mode 100644 src/Cli/Cli.csproj create mode 100644 src/Cli/InteractiveUI.cs create mode 100644 src/Cli/Program.cs create mode 100644 src/Core/Core.csproj create mode 100644 src/Core/ServiceRunner.cs diff --git a/.github/PROJECT_STRUCTURE.md b/.github/PROJECT_STRUCTURE.md new file mode 100644 index 0000000..696a4df --- /dev/null +++ b/.github/PROJECT_STRUCTURE.md @@ -0,0 +1,123 @@ +# SRunner Project Structure + +## Overview +SRunner is a C# CLI application to run configured services and stacks, built with Terminal.GUI, .NET 10, and System.CommandLine. + +## Project Structure + +``` +SRunner/ +├── .github/ # GitHub configuration and AI instructions +│ └── PROJECT_STRUCTURE.md +├── src/ # Source code directory +│ ├── Core/ # Core business logic (non-UI) +│ │ ├── ServiceConfig.cs # Service configuration model +│ │ └── ServiceRunner.cs # Service management logic +│ └── Cli/ # Command-line interface (entry point) +│ ├── Program.cs # Entry point with System.CommandLine +│ ├── InteractiveUI.cs # Terminal.GUI interactive interface +│ └── Cli.csproj # CLI project file +├── SRunner.sln # Solution file +├── .gitignore # Git ignore file +└── README.md # Project documentation +``` + +## Key Technologies + +- **Framework**: .NET 10 +- **UI Library**: Terminal.Gui 1.19.0 +- **CLI Framework**: System.CommandLine 2.0.0-rc.1.25451.107 +- **Language**: C# with nullable reference types enabled + +## Architecture + +### Core Project (`src/Core/`) +- **Purpose**: Contains business logic and models independent of the user interface +- **Components**: + - `ServiceConfig`: Data model for service configuration + - `ServiceRunner`: Manages service lifecycle and state +- **Target Framework**: net10.0 +- **Type**: Class Library + +### Cli Project (`src/Cli/`) +- **Purpose**: Entry point and user interface implementation +- **Components**: + - `Program.cs`: Main entry point using System.CommandLine for argument parsing + - `InteractiveUI.cs`: Terminal.GUI-based interactive interface +- **Target Framework**: net10.0 +- **Type**: Console Application +- **Dependencies**: + - Core project reference + - Terminal.Gui package + - System.CommandLine package + +## Usage + +### Build the Project +```bash +dotnet build +``` + +### Run the CLI +```bash +# Non-interactive mode +dotnet run --project src/Cli + +# Interactive mode with Terminal.GUI +dotnet run --project src/Cli -- --interactive +# or +dotnet run --project src/Cli -- -i +``` + +## Development Guidelines + +### Adding New Features + +1. **Core Logic**: Add new business logic to `src/Core/` + - Keep UI-independent + - Follow existing patterns + - Add models and services as needed + +2. **UI Features**: Add new UI features to `src/Cli/` + - UI code goes in `InteractiveUI.cs` or new UI classes + - CLI argument handling goes in `Program.cs` + +3. **Dependencies**: + - Keep Core project minimal with no UI dependencies + - UI packages only in Cli project + +### Code Style +- Use nullable reference types +- Follow C# naming conventions +- Keep methods focused and single-purpose +- Add XML documentation comments for public APIs + +### Project References +- Cli project references Core project +- Core project has no dependencies on Cli + +## Command-Line Options + +- `--interactive` or `-i`: Launch the Terminal.GUI interactive interface +- Without flags: Shows basic help information + +## Interactive Interface Features + +The Terminal.GUI interface provides: +- Service list view +- Add new services with dialog +- Remove services with confirmation +- View service details +- Menu bar with File and Help options +- Keyboard shortcuts for all actions + +## Future Enhancements + +Potential areas for expansion: +- Actual service execution (start/stop processes) +- Service status monitoring +- Configuration file persistence (JSON/YAML) +- Logging and diagnostics +- Service dependency management +- Multi-stack support +- Environment variable management diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b3b2d80 --- /dev/null +++ b/.gitignore @@ -0,0 +1,60 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio cache/options directory +.vs/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NuGet +*.nupkg +**/packages/* +!**/packages/build/ + +# Build folders +**/bin/ +**/obj/ + +# Rider +.idea/ +*.sln.iml + +# User-specific files +*.rsuser + +# Mono Auto Generated Files +mono_crash.* + +# Windows thumbnail cache +Thumbs.db + +# macOS +.DS_Store + +# VS Code +.vscode/ diff --git a/SRunner.slnx b/SRunner.slnx new file mode 100644 index 0000000..0fe02c5 --- /dev/null +++ b/SRunner.slnx @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/Cli/Cli.csproj b/src/Cli/Cli.csproj new file mode 100644 index 0000000..306b6b2 --- /dev/null +++ b/src/Cli/Cli.csproj @@ -0,0 +1,19 @@ + + + + + + + + + + + + + Exe + net10.0 + enable + enable + + + diff --git a/src/Cli/InteractiveUI.cs b/src/Cli/InteractiveUI.cs new file mode 100644 index 0000000..66878cf --- /dev/null +++ b/src/Cli/InteractiveUI.cs @@ -0,0 +1,293 @@ +using Terminal.Gui; +using Core; + +namespace Cli; + +/// +/// Interactive Terminal.GUI interface for SRunner +/// +public class InteractiveUI +{ + private readonly ServiceRunner _serviceRunner; + private ListView? _servicesListView; + private List _serviceNames = new(); + + public InteractiveUI() + { + _serviceRunner = new ServiceRunner(); + InitializeSampleServices(); + } + + private void InitializeSampleServices() + { + // Add some sample services for demonstration + _serviceRunner.AddService(new ServiceConfig + { + Name = "Web Server", + Command = "dotnet run", + WorkingDirectory = "/app/web", + AutoStart = true + }); + + _serviceRunner.AddService(new ServiceConfig + { + Name = "API Service", + Command = "npm start", + WorkingDirectory = "/app/api", + AutoStart = false + }); + + _serviceRunner.AddService(new ServiceConfig + { + Name = "Database", + Command = "docker-compose up", + WorkingDirectory = "/app/database", + AutoStart = true + }); + + UpdateServiceList(); + } + + private void UpdateServiceList() + { + _serviceNames = _serviceRunner.Services.Select(s => s.Name).ToList(); + } + + public void Run() + { + Application.Init(); + + try + { + var top = Application.Top; + + // Create main window + var win = new Window("SRunner - Service Manager") + { + X = 0, + Y = 0, + Width = Dim.Fill(), + Height = Dim.Fill() + }; + + // Create menu bar + var menu = new MenuBar(new MenuBarItem[] + { + new MenuBarItem("_File", new MenuItem[] + { + new MenuItem("_Quit", "Exit SRunner", () => Application.RequestStop()) + }), + new MenuBarItem("_Help", new MenuItem[] + { + new MenuItem("_About", "About SRunner", () => ShowAbout()) + }) + }); + + top.Add(menu); + + // Create label + var label = new Label("Configured Services:") + { + X = 1, + Y = 1, + Width = Dim.Fill() - 2, + Height = 1 + }; + win.Add(label); + + // Create services list + _servicesListView = new ListView(_serviceNames) + { + X = 1, + Y = 2, + Width = Dim.Fill() - 2, + Height = Dim.Fill() - 6 + }; + win.Add(_servicesListView); + + // Create buttons + var addButton = new Button("_Add Service") + { + X = 1, + Y = Pos.Bottom(_servicesListView) + 1 + }; + addButton.Clicked += OnAddService; + win.Add(addButton); + + var removeButton = new Button("_Remove Service") + { + X = Pos.Right(addButton) + 2, + Y = Pos.Bottom(_servicesListView) + 1 + }; + removeButton.Clicked += OnRemoveService; + win.Add(removeButton); + + var detailsButton = new Button("_Details") + { + X = Pos.Right(removeButton) + 2, + Y = Pos.Bottom(_servicesListView) + 1 + }; + detailsButton.Clicked += OnShowDetails; + win.Add(detailsButton); + + var quitButton = new Button("_Quit") + { + X = Pos.Right(detailsButton) + 2, + Y = Pos.Bottom(_servicesListView) + 1 + }; + quitButton.Clicked += () => Application.RequestStop(); + win.Add(quitButton); + + top.Add(win); + + Application.Run(); + } + finally + { + Application.Shutdown(); + } + } + + private void OnAddService() + { + var dialog = new Dialog("Add Service", 60, 15); + + var nameLabel = new Label("Name:") + { + X = 1, + Y = 1 + }; + dialog.Add(nameLabel); + + var nameField = new TextField("") + { + X = Pos.Right(nameLabel) + 1, + Y = 1, + Width = Dim.Fill() - 2 + }; + dialog.Add(nameField); + + var commandLabel = new Label("Command:") + { + X = 1, + Y = 3 + }; + dialog.Add(commandLabel); + + var commandField = new TextField("") + { + X = 1, + Y = 4, + Width = Dim.Fill() - 2 + }; + dialog.Add(commandField); + + var workDirLabel = new Label("Working Directory:") + { + X = 1, + Y = 6 + }; + dialog.Add(workDirLabel); + + var workDirField = new TextField("") + { + X = 1, + Y = 7, + Width = Dim.Fill() - 2 + }; + dialog.Add(workDirField); + + var okButton = new Button("OK") + { + X = Pos.Center() - 10, + Y = Pos.Bottom(dialog) - 4, + IsDefault = true + }; + okButton.Clicked += () => + { + var name = nameField.Text?.ToString() ?? ""; + var command = commandField.Text?.ToString() ?? ""; + var workDir = workDirField.Text?.ToString() ?? ""; + + if (!string.IsNullOrWhiteSpace(name) && !string.IsNullOrWhiteSpace(command)) + { + _serviceRunner.AddService(new ServiceConfig + { + Name = name, + Command = command, + WorkingDirectory = workDir, + AutoStart = false + }); + UpdateServiceList(); + if (_servicesListView != null) + { + _servicesListView.SetSource(_serviceNames); + } + Application.RequestStop(); + } + else + { + MessageBox.ErrorQuery("Error", "Name and Command are required!", "OK"); + } + }; + dialog.AddButton(okButton); + + var cancelButton = new Button("Cancel"); + cancelButton.Clicked += () => Application.RequestStop(); + dialog.AddButton(cancelButton); + + Application.Run(dialog); + } + + private void OnRemoveService() + { + if (_servicesListView?.SelectedItem >= 0 && _servicesListView.SelectedItem < _serviceNames.Count) + { + var serviceName = _serviceNames[_servicesListView.SelectedItem]; + var result = MessageBox.Query("Confirm", $"Remove service '{serviceName}'?", "Yes", "No"); + + if (result == 0) + { + _serviceRunner.RemoveService(serviceName); + UpdateServiceList(); + _servicesListView.SetSource(_serviceNames); + } + } + else + { + MessageBox.ErrorQuery("Error", "Please select a service to remove", "OK"); + } + } + + private void OnShowDetails() + { + if (_servicesListView?.SelectedItem >= 0 && _servicesListView.SelectedItem < _serviceNames.Count) + { + var serviceName = _serviceNames[_servicesListView.SelectedItem]; + var service = _serviceRunner.GetService(serviceName); + + if (service != null) + { + var details = $"Name: {service.Name}\n" + + $"Command: {service.Command}\n" + + $"Working Directory: {service.WorkingDirectory}\n" + + $"Auto Start: {service.AutoStart}"; + + MessageBox.Query("Service Details", details, "OK"); + } + } + else + { + MessageBox.ErrorQuery("Error", "Please select a service to view", "OK"); + } + } + + private void ShowAbout() + { + MessageBox.Query("About SRunner", + "SRunner v1.0\n\n" + + "CLI application to run configured services and stacks\n\n" + + "Built with Terminal.GUI and System.CommandLine", + "OK"); + } +} diff --git a/src/Cli/Program.cs b/src/Cli/Program.cs new file mode 100644 index 0000000..c04a10c --- /dev/null +++ b/src/Cli/Program.cs @@ -0,0 +1,31 @@ +using Core; + +namespace Cli; + +class Program +{ + static void Main(string[] args) + { + // Simple argument parsing for --interactive flag + bool interactive = args.Contains("--interactive") || args.Contains("-i"); + + if (interactive) + { + LaunchInteractiveMode(); + } + else + { + Console.WriteLine("SRunner - CLI application to run configured services and stacks"); + Console.WriteLine("Use --interactive or -i to launch the interactive interface"); + Console.WriteLine(); + Console.WriteLine("Options:"); + Console.WriteLine(" --interactive, -i Launch interactive Terminal.GUI interface"); + } + } + + static void LaunchInteractiveMode() + { + var ui = new InteractiveUI(); + ui.Run(); + } +} diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj new file mode 100644 index 0000000..b760144 --- /dev/null +++ b/src/Core/Core.csproj @@ -0,0 +1,9 @@ + + + + net10.0 + enable + enable + + + diff --git a/src/Core/ServiceRunner.cs b/src/Core/ServiceRunner.cs new file mode 100644 index 0000000..2cb9055 --- /dev/null +++ b/src/Core/ServiceRunner.cs @@ -0,0 +1,37 @@ +namespace Core; + +/// +/// Represents a service configuration +/// +public class ServiceConfig +{ + public string Name { get; set; } = string.Empty; + public string Command { get; set; } = string.Empty; + public string WorkingDirectory { get; set; } = string.Empty; + public bool AutoStart { get; set; } +} + +/// +/// Manages service execution +/// +public class ServiceRunner +{ + private readonly List _services = new(); + + public IReadOnlyList Services => _services.AsReadOnly(); + + public void AddService(ServiceConfig service) + { + _services.Add(service); + } + + public void RemoveService(string name) + { + _services.RemoveAll(s => s.Name == name); + } + + public ServiceConfig? GetService(string name) + { + return _services.FirstOrDefault(s => s.Name == name); + } +} From 7df6e18f444cffea576e325b9ed930791e6a4524 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 19:02:47 +0000 Subject: [PATCH 3/6] Use System.CommandLine beta4 version that works with .NET 10 Co-authored-by: Maxiimeeb <48930628+Maxiimeeb@users.noreply.github.com> --- .github/PROJECT_STRUCTURE.md | 2 +- src/Cli/Cli.csproj | 2 +- src/Cli/Program.cs | 42 +++++++++++++++++++++++------------- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/.github/PROJECT_STRUCTURE.md b/.github/PROJECT_STRUCTURE.md index 696a4df..f953248 100644 --- a/.github/PROJECT_STRUCTURE.md +++ b/.github/PROJECT_STRUCTURE.md @@ -26,7 +26,7 @@ SRunner/ - **Framework**: .NET 10 - **UI Library**: Terminal.Gui 1.19.0 -- **CLI Framework**: System.CommandLine 2.0.0-rc.1.25451.107 +- **CLI Framework**: System.CommandLine 2.0.0-beta4.22272.1 (Note: RC version 2.0.0-rc.1.25451.107 has API incompatibilities with .NET 10, so using stable beta4 version instead) - **Language**: C# with nullable reference types enabled ## Architecture diff --git a/src/Cli/Cli.csproj b/src/Cli/Cli.csproj index 306b6b2..85b3ade 100644 --- a/src/Cli/Cli.csproj +++ b/src/Cli/Cli.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/Cli/Program.cs b/src/Cli/Program.cs index c04a10c..797dddb 100644 --- a/src/Cli/Program.cs +++ b/src/Cli/Program.cs @@ -1,26 +1,38 @@ -using Core; +using System.CommandLine; +using Core; namespace Cli; class Program { - static void Main(string[] args) + static async Task Main(string[] args) { - // Simple argument parsing for --interactive flag - bool interactive = args.Contains("--interactive") || args.Contains("-i"); + // Create the root command + var rootCommand = new RootCommand("SRunner - CLI application to run configured services and stacks"); - if (interactive) - { - LaunchInteractiveMode(); - } - else + // Create the --interactive option + var interactiveOption = new Option( + name: "--interactive", + description: "Launch interactive Terminal.GUI interface"); + interactiveOption.AddAlias("-i"); + + rootCommand.AddOption(interactiveOption); + + // Set the handler for the root command + rootCommand.SetHandler((interactive) => { - Console.WriteLine("SRunner - CLI application to run configured services and stacks"); - Console.WriteLine("Use --interactive or -i to launch the interactive interface"); - Console.WriteLine(); - Console.WriteLine("Options:"); - Console.WriteLine(" --interactive, -i Launch interactive Terminal.GUI interface"); - } + if (interactive) + { + LaunchInteractiveMode(); + } + else + { + Console.WriteLine("SRunner - CLI application to run configured services and stacks"); + Console.WriteLine("Use --interactive or -i to launch the interactive interface"); + } + }, interactiveOption); + + return await rootCommand.InvokeAsync(args); } static void LaunchInteractiveMode() From cc898ed83184fcced3d720f4ee0ade7ab1b8ff31 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 19:03:15 +0000 Subject: [PATCH 4/6] Update README with comprehensive usage examples Co-authored-by: Maxiimeeb <48930628+Maxiimeeb@users.noreply.github.com> --- README.md | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/README.md b/README.md index 60c8760..4429661 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,81 @@ # SRunner CLI application to run configured services and stacks + +## Overview + +SRunner is a C# CLI application built with .NET 10, Terminal.GUI, and System.CommandLine. It provides both a command-line interface and an interactive Terminal UI for managing and running configured services and stacks. + +## Features + +- **Interactive Terminal UI**: Launch a full-featured Terminal.GUI interface with `--interactive` flag +- **Service Management**: Add, remove, and view configured services +- **Modern Architecture**: Clean separation between Core business logic and CLI interface +- **.NET 10**: Built on the latest .NET framework + +## Project Structure + +``` +SRunner/ +├── src/ +│ ├── Core/ # Business logic (non-UI) +│ │ └── ServiceRunner.cs +│ └── Cli/ # CLI and UI +│ ├── Program.cs +│ └── InteractiveUI.cs +├── .github/ +│ └── PROJECT_STRUCTURE.md # Detailed project documentation +└── SRunner.sln +``` + +## Building + +```bash +dotnet build +``` + +## Usage + +### Non-Interactive Mode + +```bash +dotnet run --project src/Cli +``` + +This displays basic information about the CLI. + +### Interactive Mode + +```bash +dotnet run --project src/Cli -- --interactive +# or +dotnet run --project src/Cli -- -i +``` + +This launches the Terminal.GUI interactive interface where you can: +- View configured services +- Add new services +- Remove existing services +- View service details +- Navigate using keyboard shortcuts + +### Help + +```bash +dotnet run --project src/Cli -- --help +``` + +## Technologies + +- **.NET 10.0**: Latest .NET framework +- **Terminal.Gui 1.19.0**: Cross-platform Terminal UI toolkit +- **System.CommandLine 2.0.0-beta4**: Command-line parsing +- **C# 13**: With nullable reference types enabled + +## Development + +See [.github/PROJECT_STRUCTURE.md](.github/PROJECT_STRUCTURE.md) for detailed development guidelines and architecture documentation. + +## License + +See [LICENSE](LICENSE) file for details. + From eebaaa54f98dc764d375d3fb3bffae50cf99d079 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 19:05:49 +0000 Subject: [PATCH 5/6] Add input validation and fix documentation references Co-authored-by: Maxiimeeb <48930628+Maxiimeeb@users.noreply.github.com> --- .github/PROJECT_STRUCTURE.md | 10 ++++------ README.md | 2 +- src/Core/ServiceRunner.cs | 38 ++++++++++++++++++++++++++++++++---- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/.github/PROJECT_STRUCTURE.md b/.github/PROJECT_STRUCTURE.md index f953248..6f626ba 100644 --- a/.github/PROJECT_STRUCTURE.md +++ b/.github/PROJECT_STRUCTURE.md @@ -11,13 +11,12 @@ SRunner/ │ └── PROJECT_STRUCTURE.md ├── src/ # Source code directory │ ├── Core/ # Core business logic (non-UI) -│ │ ├── ServiceConfig.cs # Service configuration model -│ │ └── ServiceRunner.cs # Service management logic +│ │ └── ServiceRunner.cs # Service models and management logic │ └── Cli/ # Command-line interface (entry point) │ ├── Program.cs # Entry point with System.CommandLine │ ├── InteractiveUI.cs # Terminal.GUI interactive interface │ └── Cli.csproj # CLI project file -├── SRunner.sln # Solution file +├── SRunner.slnx # Solution file ├── .gitignore # Git ignore file └── README.md # Project documentation ``` @@ -26,7 +25,7 @@ SRunner/ - **Framework**: .NET 10 - **UI Library**: Terminal.Gui 1.19.0 -- **CLI Framework**: System.CommandLine 2.0.0-beta4.22272.1 (Note: RC version 2.0.0-rc.1.25451.107 has API incompatibilities with .NET 10, so using stable beta4 version instead) +- **CLI Framework**: System.CommandLine 2.0.0-beta4.22272.1 (Note: Using stable beta4 version as it has better compatibility with the current .NET version) - **Language**: C# with nullable reference types enabled ## Architecture @@ -34,8 +33,7 @@ SRunner/ ### Core Project (`src/Core/`) - **Purpose**: Contains business logic and models independent of the user interface - **Components**: - - `ServiceConfig`: Data model for service configuration - - `ServiceRunner`: Manages service lifecycle and state + - `ServiceRunner.cs`: Contains both ServiceConfig data model and ServiceRunner management logic - **Target Framework**: net10.0 - **Type**: Class Library diff --git a/README.md b/README.md index 4429661..aa595f3 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ SRunner/ │ └── InteractiveUI.cs ├── .github/ │ └── PROJECT_STRUCTURE.md # Detailed project documentation -└── SRunner.sln +└── SRunner.slnx ``` ## Building diff --git a/src/Core/ServiceRunner.cs b/src/Core/ServiceRunner.cs index 2cb9055..6f1a490 100644 --- a/src/Core/ServiceRunner.cs +++ b/src/Core/ServiceRunner.cs @@ -5,8 +5,31 @@ /// public class ServiceConfig { - public string Name { get; set; } = string.Empty; - public string Command { get; set; } = string.Empty; + private string _name = string.Empty; + private string _command = string.Empty; + + public string Name + { + get => _name; + set + { + if (string.IsNullOrWhiteSpace(value)) + throw new ArgumentException("Service name cannot be empty", nameof(Name)); + _name = value; + } + } + + public string Command + { + get => _command; + set + { + if (string.IsNullOrWhiteSpace(value)) + throw new ArgumentException("Service command cannot be empty", nameof(Command)); + _command = value; + } + } + public string WorkingDirectory { get; set; } = string.Empty; public bool AutoStart { get; set; } } @@ -22,16 +45,23 @@ public class ServiceRunner public void AddService(ServiceConfig service) { + ArgumentNullException.ThrowIfNull(service); + + if (_services.Any(s => s.Name.Equals(service.Name, StringComparison.OrdinalIgnoreCase))) + { + throw new InvalidOperationException($"A service with the name '{service.Name}' already exists."); + } + _services.Add(service); } public void RemoveService(string name) { - _services.RemoveAll(s => s.Name == name); + _services.RemoveAll(s => s.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); } public ServiceConfig? GetService(string name) { - return _services.FirstOrDefault(s => s.Name == name); + return _services.FirstOrDefault(s => s.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); } } From 6dbaf8899bc156a7357b2f7cc7109cff9de03419 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 19:07:34 +0000 Subject: [PATCH 6/6] Fix exception parameter names and handle duplicate service errors Co-authored-by: Maxiimeeb <48930628+Maxiimeeb@users.noreply.github.com> --- src/Cli/InteractiveUI.cs | 27 +++++++++++++++++---------- src/Core/ServiceRunner.cs | 4 ++-- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/Cli/InteractiveUI.cs b/src/Cli/InteractiveUI.cs index 66878cf..d8f3c84 100644 --- a/src/Cli/InteractiveUI.cs +++ b/src/Cli/InteractiveUI.cs @@ -211,19 +211,26 @@ private void OnAddService() if (!string.IsNullOrWhiteSpace(name) && !string.IsNullOrWhiteSpace(command)) { - _serviceRunner.AddService(new ServiceConfig + try { - Name = name, - Command = command, - WorkingDirectory = workDir, - AutoStart = false - }); - UpdateServiceList(); - if (_servicesListView != null) + _serviceRunner.AddService(new ServiceConfig + { + Name = name, + Command = command, + WorkingDirectory = workDir, + AutoStart = false + }); + UpdateServiceList(); + if (_servicesListView != null) + { + _servicesListView.SetSource(_serviceNames); + } + Application.RequestStop(); + } + catch (InvalidOperationException ex) { - _servicesListView.SetSource(_serviceNames); + MessageBox.ErrorQuery("Error", ex.Message, "OK"); } - Application.RequestStop(); } else { diff --git a/src/Core/ServiceRunner.cs b/src/Core/ServiceRunner.cs index 6f1a490..13254ed 100644 --- a/src/Core/ServiceRunner.cs +++ b/src/Core/ServiceRunner.cs @@ -14,7 +14,7 @@ public string Name set { if (string.IsNullOrWhiteSpace(value)) - throw new ArgumentException("Service name cannot be empty", nameof(Name)); + throw new ArgumentException("Service name cannot be empty", nameof(value)); _name = value; } } @@ -25,7 +25,7 @@ public string Command set { if (string.IsNullOrWhiteSpace(value)) - throw new ArgumentException("Service command cannot be empty", nameof(Command)); + throw new ArgumentException("Service command cannot be empty", nameof(value)); _command = value; } }