From e8855a50020907a70e696820aec32a929ac8540d Mon Sep 17 00:00:00 2001 From: onihilist Date: Mon, 28 Apr 2025 16:37:55 +0200 Subject: [PATCH 1/3] fix: nano, nvim...etc now run properly --- Shell/Commands/CommandParser.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Shell/Commands/CommandParser.cs b/Shell/Commands/CommandParser.cs index f1d1a3a..f048553 100644 --- a/Shell/Commands/CommandParser.cs +++ b/Shell/Commands/CommandParser.cs @@ -101,7 +101,7 @@ public bool TryExecute(string commandLine, ShellContext context) var cmdName = parts[0]; var args = parts.Skip(1).ToArray(); - if (cmdName.StartsWith("./") || cmdName.StartsWith("/") || cmdName.Contains("/")) + if (cmdName.StartsWith("./")) { try { @@ -111,9 +111,9 @@ public bool TryExecute(string commandLine, ShellContext context) { FileName = "/bin/bash", Arguments = $"-c \"{commandLine}\"", - RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, + RedirectStandardOutput = false, + RedirectStandardError = false, + RedirectStandardInput = false, UseShellExecute = false, CreateNoWindow = true } @@ -138,7 +138,7 @@ public bool TryExecute(string commandLine, ShellContext context) } catch (Exception ex) { - Console.WriteLine($"[[[red]-[/]]] - Error executing file: {ex.Message}"); + AnsiConsole.MarkupLine($"[[[red]-[/]]] - Error executing file: {ex.Message}"); return true; } } From 8e3d747b28b4c8d649311229f0e39c17b8006a2d Mon Sep 17 00:00:00 2001 From: onihilist Date: Mon, 5 May 2025 16:59:38 +0200 Subject: [PATCH 2/3] feat in progress: dynamic plugin loader --- Commands/SetThemeCommand.cs | 1 - Shell/Commands/CommandParser.cs | 163 +++++++++++------------------- Shell/Commands/CommandRegistry.cs | 20 ++++ Shell/Plugin/PluginLoader.cs | 108 +++++++++++++------- Shell/ShellContext.cs | 2 + 5 files changed, 151 insertions(+), 143 deletions(-) diff --git a/Commands/SetThemeCommand.cs b/Commands/SetThemeCommand.cs index 3deca51..2e6c9d2 100644 --- a/Commands/SetThemeCommand.cs +++ b/Commands/SetThemeCommand.cs @@ -20,7 +20,6 @@ public void Execute(ShellContext context, string[] args) } string themeName = args[0]; - bool res = context.SetTheme(themeName); if(res){AnsiConsole.MarkupLine($"[[[green]+[/]]] - Theme set to: {themeName}");} diff --git a/Shell/Commands/CommandParser.cs b/Shell/Commands/CommandParser.cs index f048553..9462412 100644 --- a/Shell/Commands/CommandParser.cs +++ b/Shell/Commands/CommandParser.cs @@ -1,4 +1,3 @@ - using Spectre.Console; using System.Diagnostics; @@ -10,7 +9,6 @@ namespace NShell.Shell.Commands; /// public class CommandParser { - public static readonly Dictionary CustomCommands = new(); public static readonly HashSet SystemCommands = new(); private static readonly HashSet InteractiveCommands = new() @@ -37,19 +35,13 @@ private void LoadCommands() AnsiConsole.MarkupLine($"\t[[[green]+[/]]] - Loaded custom command: [yellow]{command.Name}[/]"); } - var TotalCommands = CustomCommands.Count + SystemCommands.Count; - LoadSystemCommands(); - if (TotalCommands > 0) - { - AnsiConsole.MarkupLine($"[bold grey]→ Total commands loaded:[/] [bold green]{TotalCommands}[/]"); - } - else - { - AnsiConsole.MarkupLine($"[bold grey]→ Total commands loaded:[/] [yellow]{TotalCommands}[/]"); - } + var total = CustomCommands.Count + SystemCommands.Count; + AnsiConsole.MarkupLine(total > 0 + ? $"[bold grey]→ Total commands loaded:[/] [bold green]{total}[/]" + : $"[bold grey]→ Total commands loaded:[/] [yellow]{total}[/]"); } /// @@ -63,15 +55,13 @@ private void LoadSystemCommands() { if (!Directory.Exists(dir)) continue; - var commands = Directory.GetFiles(dir) - .Select(Path.GetFileName) - .Where(f => !string.IsNullOrWhiteSpace(f)); - - foreach (var cmd in commands) + foreach (var file in Directory.GetFiles(dir)) { - SystemCommands.Add(cmd); - var safeCmd = EscapeMarkup(cmd); - //AnsiConsole.MarkupLine($"\t[[[green]+[/]]] Loaded system command: [yellow]{safeCmd}[/]"); + var cmd = Path.GetFileName(file); + if (!string.IsNullOrWhiteSpace(cmd)) + { + SystemCommands.Add(cmd); + } } } } @@ -85,73 +75,32 @@ private void LoadSystemCommands() /// Returns true if the command was successfully executed, false otherwise. public bool TryExecute(string commandLine, ShellContext context) { - string expanded = context.ExpandVariables(commandLine); + var expanded = context.ExpandVariables(commandLine); var parts = expanded.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (parts.Length == 0) return false; - bool usedSudo = false; - if (parts[0] == "sudo") - { - usedSudo = true; - parts = parts.Skip(1).ToArray(); - } + var usedSudo = parts[0] == "sudo"; + if (usedSudo) parts = parts.Skip(1).ToArray(); if (parts.Length == 0) return false; var cmdName = parts[0]; var args = parts.Skip(1).ToArray(); - + if (cmdName.StartsWith("./")) { - try - { - var process = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = "/bin/bash", - Arguments = $"-c \"{commandLine}\"", - RedirectStandardOutput = false, - RedirectStandardError = false, - RedirectStandardInput = false, - UseShellExecute = false, - CreateNoWindow = true - } - }; - - process.Start(); - string output = process.StandardOutput.ReadToEnd(); - string error = process.StandardError.ReadToEnd(); - process.WaitForExit(); - - if (!string.IsNullOrWhiteSpace(output)) - Console.WriteLine(output); - - if (!string.IsNullOrWhiteSpace(error)) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(error); - Console.ResetColor(); - } - - return true; - } - catch (Exception ex) - { - AnsiConsole.MarkupLine($"[[[red]-[/]]] - Error executing file: {ex.Message}"); - return true; - } + return ExecuteLocalFile(commandLine); } - if (CustomCommands.TryGetValue(cmdName, out var command)) + if (CustomCommands.TryGetValue(cmdName, out var customCmd)) { - if (command is IMetadataCommand meta && meta.RequiresRoot && !(usedSudo || IsRootUser())) + if (customCmd is IMetadataCommand meta && meta.RequiresRoot && !(usedSudo || IsRootUser())) { AnsiConsole.MarkupLine("[red][[-]] - This command requires root privileges. Prefix with [bold yellow]sudo[/] or run as root.[/]"); return true; } - command.Execute(context, args); + customCmd.Execute(context, args); return true; } @@ -160,11 +109,13 @@ public bool TryExecute(string commandLine, ShellContext context) var fullPath = ResolveSystemCommandPath(cmdName); if (fullPath != null) { - bool success = RunSystemCommand(fullPath, args, usedSudo); + RunSystemCommand(fullPath, args, usedSudo); + if ((cmdName == "apt" || cmdName == "apt-get") && args.Length > 0 && args[0] == "install") { CommandLoader.RefreshCommands(); } + return true; } } @@ -174,44 +125,59 @@ public bool TryExecute(string commandLine, ShellContext context) } /// - /// Resolves the full path of a system command by searching in common system directories. + /// Executes a local shell file. /// - /// The name of the system command. - /// The full path to the command if found, otherwise null. - private static string? ResolveSystemCommandPath(string cmdName) + private static bool ExecuteLocalFile(string commandLine) { - var paths = new[] { "/usr/bin", "/usr/local/bin", "/usr/games", "/bin", "/sbin", "/usr/sbin" }; + try + { + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "/bin/bash", + Arguments = $"-c \"{commandLine}\"", + UseShellExecute = false + } + }; - foreach (var path in paths) + process.Start(); + process.WaitForExit(); + + return true; + } + catch (Exception ex) { - var fullPath = Path.Combine(path, cmdName); - if (File.Exists(fullPath) && IsExecutable(fullPath)) - return fullPath; + AnsiConsole.MarkupLine($"[[[red]-[/]]] - Error executing file: {ex.Message}"); + return true; } + } + + /// + /// Resolves the full path of a system command by searching in common system directories. + /// + private static string? ResolveSystemCommandPath(string cmdName) + { + var paths = new[] { "/usr/bin", "/usr/local/bin", "/usr/games", "/bin", "/sbin", "/usr/sbin" }; - return null; + return paths.Select(path => Path.Combine(path, cmdName)) + .FirstOrDefault(fullPath => File.Exists(fullPath) && IsExecutable(fullPath)); } /// /// Checks if a file is executable. /// - /// The path to the file. - /// True if the file is executable, otherwise false. private static bool IsExecutable(string path) { - return (new FileInfo(path).Exists && (new FileInfo(path).Attributes & FileAttributes.Directory) == 0); + return new FileInfo(path).Exists; } /// /// Runs a system command, optionally using `sudo`, and handles interactive vs non-interactive command behavior. /// - /// The full path to the system command. - /// Arguments to pass to the command. - /// Whether to use `sudo` to run the command. private static bool RunSystemCommand(string path, string[] args, bool useSudo) { - bool isInteractive = InteractiveCommands.Contains(Path.GetFileName(path)); - + var isInteractive = InteractiveCommands.Contains(Path.GetFileName(path)); var startInfo = new ProcessStartInfo { FileName = useSudo ? "/usr/bin/sudo" : path, @@ -219,20 +185,18 @@ private static bool RunSystemCommand(string path, string[] args, bool useSudo) UseShellExecute = isInteractive, RedirectStandardOutput = !isInteractive, RedirectStandardError = !isInteractive, - RedirectStandardInput = false, CreateNoWindow = false }; - var process = new Process { StartInfo = startInfo }; + using var process = new Process { StartInfo = startInfo }; if (!isInteractive) { - process.OutputDataReceived += (s, e) => + process.OutputDataReceived += (_, e) => { if (!string.IsNullOrWhiteSpace(e.Data)) Console.WriteLine(e.Data); }; - - process.ErrorDataReceived += (s, e) => + process.ErrorDataReceived += (_, e) => { if (!string.IsNullOrWhiteSpace(e.Data)) { @@ -252,27 +216,14 @@ private static bool RunSystemCommand(string path, string[] args, bool useSudo) } process.WaitForExit(); - return process.ExitCode == 0; } /// /// Checks if the current user is root. /// - /// True if the current user is root, otherwise false. private static bool IsRootUser() { return Environment.UserName == "root" || Environment.GetEnvironmentVariable("USER") == "root"; } - - /// - /// Escapes markup characters in a string. - /// - /// The input string to escape. - /// The escaped string. - private static string EscapeMarkup(string input) - { - return input.Replace("[", "[[").Replace("]", "]]"); - } } - diff --git a/Shell/Commands/CommandRegistry.cs b/Shell/Commands/CommandRegistry.cs index 4c0dd8c..a2c5d5f 100644 --- a/Shell/Commands/CommandRegistry.cs +++ b/Shell/Commands/CommandRegistry.cs @@ -1,4 +1,5 @@  +using Spectre.Console; using NShell.Commands; namespace NShell.Shell.Commands; @@ -8,6 +9,9 @@ namespace NShell.Shell.Commands; /// public static class CommandRegistry { + + private static readonly Dictionary _commands = new(); + /// /// Retrieves all registered custom commands implemented in the shell. /// @@ -20,4 +24,20 @@ public static IEnumerable GetAll() new SetThemeCommand(), }; } + + /// + /// Register a new plugin command into the shell. + /// This method adds the command to the command list and prints a confirmation. + /// + /// The plugin command implementing ICustomCommand to be registered. + public static void Register(ICustomCommand command) + { + if (!_commands.ContainsKey(command.Name)) + { + _commands.Add(command.Name, command); + AnsiConsole.MarkupLine($"\t[[[green]+[/]]] - Registered plugin command: [yellow]{command.Name}[/]"); + } + } + + } \ No newline at end of file diff --git a/Shell/Plugin/PluginLoader.cs b/Shell/Plugin/PluginLoader.cs index 4ae2d3d..a02d315 100644 --- a/Shell/Plugin/PluginLoader.cs +++ b/Shell/Plugin/PluginLoader.cs @@ -1,57 +1,93 @@ using Spectre.Console; +using System.Reflection; +using NShell.Shell.Commands; -namespace NShell.Shell.Plugins +namespace NShell.Shell.Plugins; + +/// +/// PluginLoader manage all about loading, parse, execute plugins. +/// +public class PluginLoader { + private string PluginFolderPath { get; set; } = $"/home/{Environment.UserName}/.nshell/plugins"; + private readonly List _loadedPlugins = new(); + + public string[] PluginList { get; private set; } = []; + public int NumberOfPlugins { get; private set; } + + public static PluginLoader Instance { get; private set; } = new(); /// - /// PluginLoader manage all about loading, parse, execute plugins. + /// Load all plugins into ~/.nshell/plugins folder. /// - public class PluginLoader + public void LoadPlugins() { - private string PluginFolderPath { get; set; } = $"/home/{Environment.UserName}/.nshell/plugins"; - public string[] PluginList { get; set; } - public int NumberOfPlugins { get; set; } - - public static PluginLoader Instance { get; private set; } = null!; - - /// - /// Load all plugins into ~/.nshell/plugins folder. - /// - public void LoadPlugins() + if (!Directory.Exists(PluginFolderPath)) { - if (Path.Exists(PluginFolderPath)) - { - var PluginList = Directory.GetFiles( - PluginFolderPath, - "*.dll", - SearchOption.AllDirectories); + AnsiConsole.MarkupLine($"\t[[[red]-[/]]] - Plugin directory doesn't exist."); + AnsiConsole.MarkupLine($"\t[[[yellow]*[/]]] - Creating plugins directory."); + Directory.CreateDirectory(PluginFolderPath); + } + + PluginList = Directory.GetFiles(PluginFolderPath, "*.dll", SearchOption.AllDirectories); - if (PluginList.Length > 0) + if (PluginList.Length > 0) + { + foreach (var pluginPath in PluginList) + { + try { - NumberOfPlugins = PluginList.Length; - foreach (var plugin in PluginList) - { - AnsiConsole.MarkupLine($"\t[[[green]+[/]]] - Loading plugin : [yellow]{plugin}[/]."); - } - AnsiConsole.MarkupLine($"[bold grey]→ Total plugins loaded:[/] [bold green]{NumberOfPlugins}[/]"); + var assembly = Assembly.LoadFrom(pluginPath); + _loadedPlugins.Add(assembly); + AnsiConsole.MarkupLine($"\t[[[green]+[/]]] - Loading plugin: [yellow]{pluginPath}[/]"); } - else + catch (Exception ex) { - NumberOfPlugins = 0; - AnsiConsole.MarkupLine($"\t[[[yellow]*[/]]] - No plugins found."); - AnsiConsole.MarkupLine($"[bold grey]→ Total plugins loaded:[/] [bold yellow]{NumberOfPlugins}[/]"); + AnsiConsole.MarkupLine($"\t[[[red]-[/]]] - Failed to load plugin: {pluginPath} - {ex.Message}"); } } - else + + NumberOfPlugins = _loadedPlugins.Count; + AnsiConsole.MarkupLine($"[bold grey]→ Total plugins loaded:[/] [bold green]{NumberOfPlugins}[/]"); + + var pluginCommands = Instance.GetPluginCommands(); + foreach (var cmd in pluginCommands) { - PluginList = []; - AnsiConsole.MarkupLine($"\t[[[red]-[/]]] - Plugin directory doesn't exist."); - AnsiConsole.MarkupLine($"\t[[[yellow]*[/]]] - Creating plugins directory."); - Directory.CreateDirectory(PluginFolderPath); + CommandRegistry.Register(cmd); } } - + else + { + NumberOfPlugins = 0; + AnsiConsole.MarkupLine($"\t[[[yellow]*[/]]] - No plugins found."); + AnsiConsole.MarkupLine($"[bold grey]→ Total plugins loaded:[/] [bold yellow]0[/]"); + } } + /// + /// Extracts and returns all plugin commands that implement ICustomCommand. + /// + public List GetPluginCommands() + { + var commands = new List(); + + foreach (var plugin in _loadedPlugins) + { + var types = plugin.GetTypes() + .Where(t => typeof(ICustomCommand).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract); + + foreach (var type in types) + { + if (Activator.CreateInstance(type) is ICustomCommand command) + { + commands.Add(command); + CommandRegistry.Register(command); + AnsiConsole.MarkupLine($"[green][+] - Loaded plugin command: {command.Name}[/]"); + } + } + } + + return commands; + } } diff --git a/Shell/ShellContext.cs b/Shell/ShellContext.cs index 1eaabe1..28c24c2 100644 --- a/Shell/ShellContext.cs +++ b/Shell/ShellContext.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text.Json; using System.Text.Json.Nodes; +using NShell.Shell.Commands; using NShell.Shell.Themes; using Spectre.Console; @@ -14,6 +15,7 @@ public class ShellContext public string CurrentTheme { get; set; } = "default"; public string Prompt { get; private set; } public string LSColors { get; private set; } + public Dictionary CustomCommands => CommandParser.CustomCommands; public ShellContext() { From 96030d7916f57089cc0003fa1f759197e51d3f48 Mon Sep 17 00:00:00 2001 From: onihilist Date: Tue, 6 May 2025 11:55:17 +0200 Subject: [PATCH 3/3] feat in progress: dynamic plugin loader --- Commands/CdCommand.cs | 1 + Commands/SetThemeCommand.cs | 1 + NShell.csproj | 1 + Shell/Commands/ICustomCommand.cs | 2 +- Shell/Plugin/PluginLoadContext.cs | 44 ++++++++++++++++++++ Shell/Plugin/PluginLoader.cs | 68 ++++++++++++++++--------------- 6 files changed, 84 insertions(+), 33 deletions(-) create mode 100644 Shell/Plugin/PluginLoadContext.cs diff --git a/Commands/CdCommand.cs b/Commands/CdCommand.cs index 6769ff7..d689019 100644 --- a/Commands/CdCommand.cs +++ b/Commands/CdCommand.cs @@ -1,6 +1,7 @@  using System.IO; using NShell.Shell; +using NShell.Shell.Commands; using Spectre.Console; namespace NShell.Commands; diff --git a/Commands/SetThemeCommand.cs b/Commands/SetThemeCommand.cs index 2e6c9d2..34e7051 100644 --- a/Commands/SetThemeCommand.cs +++ b/Commands/SetThemeCommand.cs @@ -1,5 +1,6 @@  using NShell.Shell; +using NShell.Shell.Commands; using Spectre.Console; namespace NShell.Commands diff --git a/NShell.csproj b/NShell.csproj index f4447f6..d7439bc 100644 --- a/NShell.csproj +++ b/NShell.csproj @@ -9,6 +9,7 @@ + diff --git a/Shell/Commands/ICustomCommand.cs b/Shell/Commands/ICustomCommand.cs index a4fb5c0..0ffbf13 100644 --- a/Shell/Commands/ICustomCommand.cs +++ b/Shell/Commands/ICustomCommand.cs @@ -1,5 +1,5 @@  -namespace NShell.Shell; +namespace NShell.Shell.Commands; /// /// The ICustomCommand interface defines a contract for implementing diff --git a/Shell/Plugin/PluginLoadContext.cs b/Shell/Plugin/PluginLoadContext.cs new file mode 100644 index 0000000..4258853 --- /dev/null +++ b/Shell/Plugin/PluginLoadContext.cs @@ -0,0 +1,44 @@ +using System.Reflection; +using System.Runtime.Loader; + +public class PluginLoadContext : AssemblyLoadContext +{ + private readonly AssemblyDependencyResolver _resolver; + + public PluginLoadContext(string pluginPath) : base(isCollectible: false) + { + _resolver = new AssemblyDependencyResolver(pluginPath); + } + + protected override Assembly? Load(AssemblyName assemblyName) + { + if (assemblyName.Name == "System.Runtime" + || assemblyName.Name == "System.Private.CoreLib" + || assemblyName.Name.StartsWith("System.") + || assemblyName.Name.StartsWith("Microsoft.")) + { + return null; + } + + var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); + if (assemblyPath != null) + { + Console.WriteLine($"[PluginLoadContext] Loading {assemblyName} from {assemblyPath}"); + return LoadFromAssemblyPath(assemblyPath); + } + + return null; + } + + + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + string? libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); + if (libraryPath != null) + { + return LoadUnmanagedDllFromPath(libraryPath); + } + + return IntPtr.Zero; + } +} \ No newline at end of file diff --git a/Shell/Plugin/PluginLoader.cs b/Shell/Plugin/PluginLoader.cs index a02d315..6db9af8 100644 --- a/Shell/Plugin/PluginLoader.cs +++ b/Shell/Plugin/PluginLoader.cs @@ -1,4 +1,3 @@ - using Spectre.Console; using System.Reflection; using NShell.Shell.Commands; @@ -6,11 +5,11 @@ namespace NShell.Shell.Plugins; /// -/// PluginLoader manage all about loading, parse, execute plugins. +/// Manages loading and registration of plugins implementing ICustomCommand. /// public class PluginLoader { - private string PluginFolderPath { get; set; } = $"/home/{Environment.UserName}/.nshell/plugins"; + private readonly string PluginFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nshell", "plugins"); private readonly List _loadedPlugins = new(); public string[] PluginList { get; private set; } = []; @@ -18,15 +17,12 @@ public class PluginLoader public static PluginLoader Instance { get; private set; } = new(); - /// - /// Load all plugins into ~/.nshell/plugins folder. - /// public void LoadPlugins() { if (!Directory.Exists(PluginFolderPath)) { - AnsiConsole.MarkupLine($"\t[[[red]-[/]]] - Plugin directory doesn't exist."); - AnsiConsole.MarkupLine($"\t[[[yellow]*[/]]] - Creating plugins directory."); + AnsiConsole.MarkupLine("\t[[[red]-[/]]] - Plugin directory doesn't exist."); + AnsiConsole.MarkupLine("\t[[[yellow]*[/]]] - Creating plugins directory."); Directory.CreateDirectory(PluginFolderPath); } @@ -38,56 +34,64 @@ public void LoadPlugins() { try { - var assembly = Assembly.LoadFrom(pluginPath); + var context = new PluginLoadContext(pluginPath); + var assembly = context.LoadFromAssemblyPath(pluginPath); + _loadedPlugins.Add(assembly); - AnsiConsole.MarkupLine($"\t[[[green]+[/]]] - Loading plugin: [yellow]{pluginPath}[/]"); + AnsiConsole.MarkupLine($"\t[[[green]+[/]]] - Loading plugin: [yellow]{Path.GetFileName(pluginPath)}[/]"); } catch (Exception ex) { - AnsiConsole.MarkupLine($"\t[[[red]-[/]]] - Failed to load plugin: {pluginPath} - {ex.Message}"); + AnsiConsole.MarkupLine($"\t[[[red]-[/]]] - Failed to load plugin: {Path.GetFileName(pluginPath)}"); + AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything | ExceptionFormats.ShowLinks); } } NumberOfPlugins = _loadedPlugins.Count; AnsiConsole.MarkupLine($"[bold grey]→ Total plugins loaded:[/] [bold green]{NumberOfPlugins}[/]"); - - var pluginCommands = Instance.GetPluginCommands(); - foreach (var cmd in pluginCommands) - { - CommandRegistry.Register(cmd); - } + + LoadPluginCommands(); } else { NumberOfPlugins = 0; - AnsiConsole.MarkupLine($"\t[[[yellow]*[/]]] - No plugins found."); + AnsiConsole.MarkupLine("\t[[[yellow]*[/]]] - No plugins found."); AnsiConsole.MarkupLine($"[bold grey]→ Total plugins loaded:[/] [bold yellow]0[/]"); } } - + /// - /// Extracts and returns all plugin commands that implement ICustomCommand. + /// Extracts and registers all valid types implementing ICustomCommand. /// - public List GetPluginCommands() + private void LoadPluginCommands() { - var commands = new List(); - foreach (var plugin in _loadedPlugins) { - var types = plugin.GetTypes() - .Where(t => typeof(ICustomCommand).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract); + try + { + var commandTypes = plugin.GetTypes() + .Where(t => typeof(ICustomCommand).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract); - foreach (var type in types) + foreach (var type in commandTypes) + { + if (Activator.CreateInstance(type) is ICustomCommand command) + { + CommandRegistry.Register(command); + AnsiConsole.MarkupLine($"[[[green]+[/]]] - Loaded plugin command: [yellow]{command.Name}[/]"); + } + } + } + catch (ReflectionTypeLoadException rtle) { - if (Activator.CreateInstance(type) is ICustomCommand command) + foreach (var loaderEx in rtle.LoaderExceptions) { - commands.Add(command); - CommandRegistry.Register(command); - AnsiConsole.MarkupLine($"[green][+] - Loaded plugin command: {command.Name}[/]"); + AnsiConsole.MarkupLine($"[[[red]-[/]]] - Error loading plugin type: [red]{loaderEx?.Message}[/]"); } } + catch (Exception ex) + { + AnsiConsole.MarkupLine($"[[[red]-[/]]] - Error processing plugin: [red]{ex.Message}[/]"); + } } - - return commands; } }