aboutsummaryrefslogtreecommitdiff
path: root/cmnext-cli/src/Commands
diff options
context:
space:
mode:
Diffstat (limited to 'cmnext-cli/src/Commands')
-rw-r--r--cmnext-cli/src/Commands/AuthnticationCommands.cs166
-rw-r--r--cmnext-cli/src/Commands/ChannelCommands.cs371
-rw-r--r--cmnext-cli/src/Commands/ConfigCommands.cs176
3 files changed, 713 insertions, 0 deletions
diff --git a/cmnext-cli/src/Commands/AuthnticationCommands.cs b/cmnext-cli/src/Commands/AuthnticationCommands.cs
new file mode 100644
index 0000000..5e67594
--- /dev/null
+++ b/cmnext-cli/src/Commands/AuthnticationCommands.cs
@@ -0,0 +1,166 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Package: CMNext.Cli
+* File: Program.cs
+*
+* CMNext.Cli is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* CMNext.Cli is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with CMNext.Cli. If not, see http://www.gnu.org/licenses/.
+*/
+
+using CMNext.Cli.Security;
+using CMNext.Cli.Site;
+
+using System;
+using System.Security.Authentication;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Typin.Attributes;
+using Typin.Console;
+using Typin.Exceptions;
+
+
+namespace CMNext.Cli.Commands
+{
+ public sealed class AuthnticationCommands
+ {
+
+ [Command("auth", Description = "Manages your local authentication data")]
+ public sealed class AuthCommand : BaseCommand
+ { }
+
+ [Command("auth login", Description = "Authenticates this client against your CMNext server")]
+ public sealed class AuthLoginCommand(SiteManager siteManager, VauthRunner vauth, ConsoleLogProvider logger) : BaseCommand
+ {
+ [CommandOption("stdin", 's', Description = "Reads the token from stdin instead of using vauth")]
+ public bool FromStdin { get; set; }
+
+ [CommandOption("force", 'f', Description = "Forces a login even if you already have a valid authorization")]
+ public bool Force { get; set; }
+
+ ///<inheritdoc/>
+ public override async ValueTask ExecuteAsync(IConsole console)
+ {
+ logger.SetVerbose(Verbose);
+
+ //global cancel
+ CancellationToken cancellation = console.GetCancellationToken();
+
+ IPkiCredential token;
+
+ //See if current auth is valid
+ if(await siteManager.HasValidAuth())
+ {
+ if (!Force)
+ {
+ console.WithForegroundColor(ConsoleColor.Green, c => c.Output.WriteLine("You already have a valid authorization!"));
+ return;
+ }
+ }
+
+ if (FromStdin)
+ {
+ console.Output.WriteLine("Please enter your PKI token:");
+
+ //Read the token in from stdin
+ string? otp = await console.Input.ReadLineAsync(cancellation);
+
+ if (string.IsNullOrWhiteSpace(otp))
+ {
+ throw new CommandException("You must enter a one time login token to continue");
+ }
+
+ token = new PkiToken(otp);
+ }
+ else
+ {
+ console.Output.WriteLine("Getting token from vauth");
+ //Get the token from vauth
+ token = await vauth.GetOptTokenAsync();
+ }
+
+
+ console.Output.WriteLine("Logging in...");
+
+ try
+ {
+ await siteManager.AuthenticateAsync(token);
+ console.WithForegroundColor(ConsoleColor.Green, c => c.Output.WriteLine("Login successful!"));
+ }
+ catch (AuthenticationException ae)
+ {
+ console.WithForegroundColor(ConsoleColor.Red, c => c.Output.WriteLine($"Authentication failed: {ae.Message}"));
+ }
+ finally
+ {
+ await siteManager.SaveStateAsync();
+ }
+ }
+
+ sealed record class PkiToken(string Token) : IPkiCredential
+ {
+ public string GetToken() => Token;
+ }
+ }
+
+ [Command("auth logout", Description = "Destroys any local previous login state")]
+ public sealed class AuthLogoutCommand(SiteManager siteManager, ConsoleLogProvider logger) : BaseCommand
+ {
+ ///<inheritdoc/>
+ public override async ValueTask ExecuteAsync(IConsole console)
+ {
+ logger.SetVerbose(Verbose);
+
+ console.Output.WriteLine("Logging out...");
+
+ try
+ {
+ //See if current auth is valid
+ await siteManager.LogoutAsync();
+ console.WithForegroundColor(ConsoleColor.Green, c => c.Output.WriteLine("Logout successful!"));
+ }
+ finally
+ {
+ await siteManager.SaveStateAsync();
+ }
+ }
+
+ sealed record class PkiToken(string Token) : IPkiCredential
+ {
+ public string GetToken() => Token;
+ }
+ }
+
+ [Command("auth status", Description = "Gets you client's current authorization status")]
+ public sealed class AuthStatusCommand(SiteManager siteManager) : BaseCommand
+ {
+ public override async ValueTask ExecuteAsync(IConsole console)
+ {
+ //global cancel
+ CancellationToken cancellation = console.GetCancellationToken();
+
+ console.Output.WriteLine("Checking login status...");
+
+ if (await siteManager.HasValidAuth())
+ {
+ console.WithForegroundColor(ConsoleColor.Green, c => c.Output.WriteLine("You have a valid authorization"));
+ }
+ else
+ {
+ console.WithForegroundColor(ConsoleColor.Red, c => c.Output.WriteLine("You do not have a valid authorization"));
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/cmnext-cli/src/Commands/ChannelCommands.cs b/cmnext-cli/src/Commands/ChannelCommands.cs
new file mode 100644
index 0000000..f184b0d
--- /dev/null
+++ b/cmnext-cli/src/Commands/ChannelCommands.cs
@@ -0,0 +1,371 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Package: CMNext.Cli
+* File: Program.cs
+*
+* CMNext.Cli is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* CMNext.Cli is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with CMNext.Cli. If not, see http://www.gnu.org/licenses/.
+*/
+
+using CMNext.Cli.Exceptions;
+using CMNext.Cli.Model.Channels;
+
+using System;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Typin.Attributes;
+using Typin.Console;
+using Typin.Utilities;
+
+
+namespace CMNext.Cli.Commands
+{
+ public sealed class ChannelCommands
+ {
+ [Command("channels", Description = "Performs operations against blog channels within your cms")]
+ public sealed class ChannelCommand : BaseCommand
+ { }
+
+ [Command("channels list", Description = "Lists all blog channels within your cms")]
+ public sealed class ChannelList(ChannelStore store, ConsoleLogProvider logger) : ListCommand
+ {
+ public override string Id { get; set; } = string.Empty;
+
+ [CommandOption("search", 's', Description = "Show only results for a channel by it's name")]
+ public string Search { get; set; } = string.Empty;
+
+ public override async ValueTask ExecuteAsync(IConsole console)
+ {
+ CancellationToken cancellation = console.GetCancellationToken();
+ logger.SetVerbose(Verbose);
+
+ ChannelMeta[]? channels = await store.ListAsync(cancellation);
+
+ if (channels is null)
+ {
+ console.Error.WriteLine("No channels found");
+ return;
+ }
+
+ if(!string.IsNullOrWhiteSpace(Search))
+ {
+ channels = channels.Where(c => c.Name.Contains(Search, StringComparison.OrdinalIgnoreCase)).ToArray();
+ }
+
+ console.WithForegroundColor(ConsoleColor.Green, c => c.Output.WriteLine($"Found {channels.Length} channels"));
+
+ TableUtils.Write(
+ console.Output,
+ channels,
+ ["Id", "Name", "Path", "Index", "Content", "Feed"],
+ null,
+ c => c.Id,
+ c => c.Name,
+ c => c.Path,
+ c => $"/{c.IndexFileName}",
+ c => $"/{c.ContentDir}",
+ c => (c.Feed != null) ? "*" : ""
+ );
+ }
+ }
+
+ [Command("channels delete", Description = "Deletes a channel by it's id")]
+ public sealed class ChannelDelete(ChannelStore store, ConsoleLogProvider logger) : DeleteCommand
+ {
+ [CommandOption("channel", 'c', Description = "Specifies the channel to delete", IsRequired = true)]
+ public override string Id { get; set; } = string.Empty;
+
+ public override async ValueTask ExecuteAsync(IConsole console)
+ {
+ CancellationToken cancellation = console.GetCancellationToken();
+ logger.SetVerbose(Verbose);
+ try
+ {
+ await store.DeleteAsync(Id, cancellation);
+ console.WithForegroundColor(ConsoleColor.Green, c => c.Output.WriteLine($"Deleted channel {Id}"));
+ }
+ catch (EntityNotFoundException)
+ {
+ console.WithForegroundColor(ConsoleColor.Red, c => c.Error.WriteLine($"Channel {Id} does not exist"));
+ return;
+ }
+ }
+ }
+
+ [Command("channels create", Description = "Creates a new channel")]
+ public sealed class ChannelCreateCommand(ChannelStore store, ConsoleLogProvider logger) : BaseCommand
+ {
+ [CommandOption("name", 'n', Description = "Specifies the name of the channel", IsRequired = true)]
+ public string Name { get; set; } = string.Empty;
+
+ [CommandOption("path", 'p', Description = "Specifies the path of the channel", IsRequired = true)]
+ public string Path { get; set; } = string.Empty;
+
+ [CommandOption("index", 'i', Description = "Specifies the index file name of the channel")]
+ public string Index { get; set; } = "index.json";
+
+ [CommandOption("content", 'c', Description = "Specifies the content directory of the channel", IsRequired = true)]
+ public string Content { get; set; } = string.Empty;
+
+ public async override ValueTask ExecuteAsync(IConsole console)
+ {
+ CancellationToken cancellation = console.GetCancellationToken();
+ logger.SetVerbose(Verbose);
+
+ ChannelMeta channel = new ()
+ {
+ Name = Name,
+ Path = Path,
+ IndexFileName = Index,
+ ContentDir = Content,
+ };
+
+ await store.CreateAsync(channel, cancellation);
+ console.WithForegroundColor(ConsoleColor.Green, c => c.Output.WriteLine("New channel created successfully"));
+ }
+ }
+
+ [Command("channels set", Description = "Updates a channel")]
+ public sealed class ChannelUpdateCommand(ChannelStore store, ConsoleLogProvider logger) : BaseCommand
+ {
+ [CommandOption("channel", 'c', Description = "Specifies the channel to update", IsRequired = true)]
+ public string Channel { get; set; } = string.Empty;
+
+ [CommandOption("name", 'n', Description = "Specifies the name of the channel")]
+ public string Name { get; set; } = string.Empty;
+
+ [CommandOption("index", 'i', Description = "Specifies the index file name of the channel")]
+ public string Index { get; set; } = string.Empty;
+
+ [CommandOption("content", Description = "Specifies the content directory of the channel")]
+ public string Content { get; set; } = string.Empty;
+
+ public async override ValueTask ExecuteAsync(IConsole console)
+ {
+ CancellationToken cancellation = console.GetCancellationToken();
+ logger.SetVerbose(Verbose);
+
+ ChannelMeta? channel = await store.GetAsync(Channel, cancellation);
+
+ if (channel is null)
+ {
+ console.WithForegroundColor(ConsoleColor.Red, c => c.Error.WriteLine($"Channel {Channel} does not exist"));
+ return;
+ }
+
+ console.Output.WriteLine($"Found channel {Channel}");
+
+ if (!string.IsNullOrWhiteSpace(Name))
+ {
+ channel.Name = Name;
+ console.Output.WriteLine($"Setting channel name to {Name}");
+ }
+
+ if (!string.IsNullOrWhiteSpace(Index))
+ {
+ channel.IndexFileName = Index;
+ console.Output.WriteLine($"Setting channel index file name to {Index}");
+ }
+
+ if (!string.IsNullOrWhiteSpace(Content))
+ {
+ channel.ContentDir = Content;
+ console.Output.WriteLine($"Setting channel content directory to {Content}");
+ }
+
+ await store.UpdateAsync(channel, cancellation);
+ console.WithForegroundColor(ConsoleColor.Green, c => c.Output.WriteLine("Channel updated successfully"));
+ }
+ }
+
+ [Command("channels feed get", Description = "Gets the RSS feed data for a channel if its set")]
+ public sealed class ChannelFeedGetCommand(ChannelStore store, ConsoleLogProvider logger) : BaseCommand
+ {
+ [CommandOption("channel", 'c', Description = "Specifies the channel to get the feed for", IsRequired = true)]
+ public string Channel { get; set; } = string.Empty;
+
+ public async override ValueTask ExecuteAsync(IConsole console)
+ {
+ CancellationToken cancellation = console.GetCancellationToken();
+ logger.SetVerbose(Verbose);
+
+ ChannelMeta? channel = await store.GetAsync(Channel, cancellation);
+
+ if (channel is null)
+ {
+ console.WithForegroundColor(ConsoleColor.Red, c => c.Error.WriteLine($"Channel {Channel} does not exist"));
+ return;
+ }
+
+ console.Output.WriteLine($"Found channel {Channel}");
+
+ if (channel.Feed is null)
+ {
+ console.WithForegroundColor(ConsoleColor.Red, c => c.Error.WriteLine($"Channel {Channel} does not have a feed"));
+ return;
+ }
+
+ console.Output.WriteLine($"Found feed for channel {Channel}");
+
+ console.ForegroundColor = ConsoleColor.Gray;
+ console.Output.WriteLine($" Path: {channel.Feed.Path}");
+ console.Output.WriteLine($" Description: {channel.Feed.Description}");
+ console.Output.WriteLine($" Author: {channel.Feed.Author}");
+ console.Output.WriteLine($" Contact: {channel.Feed.Contact}");
+ console.Output.WriteLine($" Image: {channel.Feed.ImagePath}");
+ console.Output.WriteLine($" Link: {channel.Feed.Url}");
+
+ console.ResetColor();
+ }
+ }
+
+ [Command("channels feed set", Description = "Sets the RSS feed data for a channel")]
+ public sealed class ChannelFeedSetCommand(ChannelStore store, ConsoleLogProvider logger) : BaseCommand
+ {
+ [CommandOption("channel", 'c', Description = "Specifies the channel to set the feed for", IsRequired = true)]
+ public string Channel { get; set; } = string.Empty;
+
+ [CommandOption("path", 'p', Description = "Specifies the path of the feed")]
+ public string Path { get; set; } = string.Empty;
+
+ [CommandOption("description", 'd', Description = "Specifies the description of the feed")]
+ public string Description { get; set; } = string.Empty;
+
+ [CommandOption("author", 'a', Description = "Specifies the author of the feed")]
+ public string Author { get; set; } = string.Empty;
+
+ [CommandOption("contact", Description = "Specifies the contact email address of the feed")]
+ public string Contact { get; set; } = string.Empty;
+
+ [CommandOption("image", 'i', Description = "Specifies the image of the feed")]
+ public string Image { get; set; } = string.Empty;
+
+ [CommandOption("link", 'l', Description = "Specifies the link of the feed")]
+ public string Link { get; set; } = string.Empty;
+
+ public async override ValueTask ExecuteAsync(IConsole console)
+ {
+ CancellationToken cancellation = console.GetCancellationToken();
+ logger.SetVerbose(Verbose);
+
+ ChannelMeta? channel = await store.GetAsync(Channel, cancellation);
+
+ if (channel is null)
+ {
+ console.WithForegroundColor(ConsoleColor.Red, c => c.Error.WriteLine($"Channel {Channel} does not exist"));
+ return;
+ }
+
+ console.Output.WriteLine($"Found channel {Channel}");
+
+ bool modified = false;
+
+ //Allow creating a new feed if the channel doesn't have one
+ channel.Feed ??= new ChannelFeed();
+
+ if (!string.IsNullOrWhiteSpace(Path))
+ {
+ channel.Feed.Path = Path;
+ console.Output.WriteLine($"Setting feed path to {Path}");
+ modified = true;
+ }
+ if (!string.IsNullOrWhiteSpace(Contact))
+ {
+ channel.Feed.Contact = Contact;
+ console.Output.WriteLine($"Setting feed contact to {Contact}");
+ modified = true;
+ }
+
+ if (!string.IsNullOrWhiteSpace(Description))
+ {
+ channel.Feed.Description = Description;
+ console.Output.WriteLine($"Setting feed description to {Description}");
+ modified = true;
+ }
+
+ if (!string.IsNullOrWhiteSpace(Author))
+ {
+ channel.Feed.Author = Author;
+ console.Output.WriteLine($"Setting feed author to {Author}");
+ modified = true;
+ }
+
+ if (!string.IsNullOrWhiteSpace(Image))
+ {
+ channel.Feed.ImagePath = Image;
+ console.Output.WriteLine($"Setting feed image to {Image}");
+ modified = true;
+ }
+
+ if (!string.IsNullOrWhiteSpace(Link))
+ {
+ channel.Feed.Url = Link;
+ console.Output.WriteLine($"Setting feed link to {Link}");
+ modified = true;
+ }
+
+ if (!modified)
+ {
+ console.WithForegroundColor(ConsoleColor.Yellow, c => c.Error.WriteLine($"No changes made to channel {Channel}"));
+ return;
+ }
+
+ await store.UpdateAsync(channel, cancellation);
+ console.WithForegroundColor(ConsoleColor.Green, c => c.Output.WriteLine("Channel feed updated successfully"));
+ }
+
+ [Command("channels feed delete", Description = "Deletes the RSS feed data for a channel")]
+ public sealed class ChannelFeedDeleteCommand(ChannelStore store, ConsoleLogProvider logger) : BaseCommand
+ {
+ [CommandOption("channel", 'c', Description = "Specifies the channel to delete the feed for", IsRequired = true)]
+ public string Channel { get; set; } = string.Empty;
+
+ public async override ValueTask ExecuteAsync(IConsole console)
+ {
+ CancellationToken cancellation = console.GetCancellationToken();
+ logger.SetVerbose(Verbose);
+
+ ChannelMeta? channel = await store.GetAsync(Channel, cancellation);
+
+ if (channel is null)
+ {
+ console.WithForegroundColor(ConsoleColor.Red, c => c.Error.WriteLine($"Channel {Channel} does not exist"));
+ return;
+ }
+
+ console.Output.WriteLine($"Found channel {Channel}");
+
+ if (channel.Feed is null)
+ {
+ console.WithForegroundColor(ConsoleColor.Red, c => c.Error.WriteLine($"Channel {Channel} does not have a feed"));
+ return;
+ }
+
+ console.Output.WriteLine($"Found feed for channel {Channel}");
+
+ channel.Feed = null;
+
+ await store.UpdateAsync(channel, cancellation);
+ console.WithForegroundColor(ConsoleColor.Green, c => c.Output.WriteLine("Channel feed deleted successfully"));
+ }
+ }
+
+ }
+ }
+
+
+
+} \ No newline at end of file
diff --git a/cmnext-cli/src/Commands/ConfigCommands.cs b/cmnext-cli/src/Commands/ConfigCommands.cs
new file mode 100644
index 0000000..ff5c439
--- /dev/null
+++ b/cmnext-cli/src/Commands/ConfigCommands.cs
@@ -0,0 +1,176 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Package: CMNext.Cli
+* File: Program.cs
+*
+* CMNext.Cli is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* CMNext.Cli is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with CMNext.Cli. If not, see http://www.gnu.org/licenses/.
+*/
+
+using CMNext.Cli.Settings;
+
+using System;
+using System.Globalization;
+using System.Threading.Tasks;
+
+using Typin.Attributes;
+using Typin.Console;
+
+
+namespace CMNext.Cli.Commands
+{
+ public sealed class ConfigCommands
+ {
+
+ [Command("config", Description = "Manages this client's configuration data")]
+ public class ConfigBase : BaseCommand
+ { }
+
+ [Command("config set", Description = "Sets the configuration of this client")]
+ public sealed class SetConfigCommand(AppSettings state) : BaseCommand
+ {
+ [CommandOption("url", 'u', Description = "The url of your cmnext server")]
+ public string? Url { get; set; }
+
+
+ [CommandOption("channels", 'c', Description = "The path from the base-url to the channels endpoint")]
+ public string? Channels { get; set; }
+
+ [CommandOption("posts", Description = "The path from the base-url to the posts endpoint")]
+ public string? Posts { get; set; }
+
+ [CommandOption("content", Description = "The path from the base-url to the auth endpoint")]
+ public string? Content { get; set; }
+
+ [CommandOption("login", Description = "The path from the base-url to the pki login endpoint")]
+ public string? Login { get; set; }
+
+ [CommandOption("vauth", Description = "The command to use to execute vauth to generate login passcode")]
+ public string? Vauth { get; set; }
+
+ ///<inheritdoc/>
+ public override async ValueTask ExecuteAsync(IConsole console)
+ {
+ bool modified = false;
+
+ //get site config
+ AppConfig siteConfig = await state.GetConfigAsync();
+
+ if (!string.IsNullOrWhiteSpace(Url))
+ {
+ console.Output.WriteLine($"Setting url to {Url}...");
+ siteConfig.BaseAddress = Url;
+ modified = true;
+ }
+
+ if (!string.IsNullOrWhiteSpace(Channels))
+ {
+ console.Output.WriteLine($"Setting channel path to {Channels}...");
+ siteConfig.Endpoints.ChannelPath = Channels;
+ modified = true;
+ }
+
+ if (!string.IsNullOrWhiteSpace(Posts))
+ {
+ console.Output.WriteLine($"Setting post path to {Posts}...");
+ siteConfig.Endpoints.PostPath = Posts;
+ modified = true;
+ }
+
+ if (!string.IsNullOrWhiteSpace(Content))
+ {
+ console.Output.WriteLine($"Setting content path to {Content}...");
+ siteConfig.Endpoints.ContentPath = Content;
+ modified = true;
+ }
+
+ if (!string.IsNullOrWhiteSpace(Login))
+ {
+ console.Output.WriteLine($"Setting pki endpoint path to {Login}...");
+ siteConfig.Endpoints.LoginPath = Login;
+ modified = true;
+ }
+
+ if(!string.IsNullOrWhiteSpace(Vauth))
+ {
+ console.Output.WriteLine($"Setting vauth command to {Vauth}...");
+ siteConfig.VauthCommands = Vauth;
+ modified = true;
+ }
+
+ //Save config if modified
+ if (modified)
+ {
+ console.WithForegroundColor(ConsoleColor.Gray, io => io.Output.WriteLine("Writing configuration..."));
+ await state.SaveConfigAsync(siteConfig);
+ console.WithForegroundColor(ConsoleColor.Green, io => io.Output.WriteLine("Configuration saved."));
+ }
+ else
+ {
+ console.Output.WriteLine("No changes to save.");
+ }
+ }
+ }
+
+ [Command("config set endpoints", Description = "Sets the configuration of this client")]
+ public sealed class SetConfigEndpointsCommand(AppSettings state) : BaseCommand
+ {
+
+ ///<inheritdoc/>
+ public override async ValueTask ExecuteAsync(IConsole console)
+ {
+ bool modified = false;
+
+ //get site config
+ AppConfig siteConfig = await state.GetConfigAsync();
+
+
+
+ //Save config if modified
+ if (modified)
+ {
+ console.Output.WriteLine("Saving configuration...");
+ await state.SaveConfigAsync(siteConfig);
+ console.Output.WriteLine("Configuration saved.");
+ }
+ else
+ {
+ console.Output.WriteLine("No changes to save.");
+ }
+ }
+ }
+
+ [Command("config get", Description = "Gets the configuration of this client")]
+ public sealed class GetConfigCommand(AppSettings state) : BaseCommand
+ {
+ ///<inheritdoc/>
+ public override async ValueTask ExecuteAsync(IConsole console)
+ {
+ //get site config
+ AppConfig siteConfig = await state.GetConfigAsync();
+
+ console.WithForegroundColor(ConsoleColor.Gray, c => c.Output.WriteLine("Current configuration:"));
+
+ console.Output.WriteLine("vauth: {0}", siteConfig.VauthCommands);
+ console.Output.WriteLine($"Url: {siteConfig.BaseAddress}");
+
+ console.Output.WriteLine($"Channels: {siteConfig.Endpoints.ChannelPath}");
+ console.Output.WriteLine($"Posts: {siteConfig.Endpoints.PostPath}");
+ console.Output.WriteLine($"Content: {siteConfig.Endpoints.ContentPath}");
+ console.Output.WriteLine($"Login: {siteConfig.Endpoints.LoginPath}");
+
+ }
+ }
+ }
+} \ No newline at end of file