From 451a982a01ab189614f0fec8bceb939c1ee09e16 Mon Sep 17 00:00:00 2001 From: Svjatoslav Agejenko Date: Mon, 24 Mar 2025 00:49:03 +0200 Subject: [PATCH] Improve code comments --- .../eu/svjatoslav/alyverkko_cli/Main.java | 36 ++++- .../alyverkko_cli/commands/MailQuery.java | 23 +++ .../commands/SelftestCommand.java | 133 ++++++++++-------- .../alyverkko_cli/commands/WizardCommand.java | 97 +++++++++---- 4 files changed, 200 insertions(+), 89 deletions(-) diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/Main.java b/src/main/java/eu/svjatoslav/alyverkko_cli/Main.java index 4bd8d57..f79f341 100644 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/Main.java +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/Main.java @@ -8,8 +8,16 @@ import java.util.Optional; import static java.util.Arrays.copyOfRange; +/** + * The main entry point for the Älyverkko CLI application. + * It processes subcommands such as "wizard", "selftest", "joinfiles", + * "mail", and "listmodels". + */ public class Main { + /** + * The list of all supported subcommands. + */ private final java.util.List commands = java.util.Arrays.asList( new ListModelsCommand(), new MailCorrespondentCommand(), @@ -18,12 +26,30 @@ public class Main { new WizardCommand() ); + /** + * The active, loaded configuration for the entire application. + * May be null if configuration is not loaded properly. + */ + public static Configuration configuration; + + /** + * Application entry point. Dispatches to a subcommand if one is + * specified; otherwise shows usage help. + * + * @param args command-line arguments; the first is the subcommand name. + */ public static void main(final String[] args) throws IOException, InterruptedException { new Main().handleCommand(args); } - public static Configuration configuration; - + /** + * Attempts to find and execute the subcommand specified in the given arguments, + * or prints a help message if no command is found. + * + * @param args the command-line arguments. + * @throws IOException if an I/O error occurs during command execution. + * @throws InterruptedException if the command is interrupted. + */ public void handleCommand(String[] args) throws IOException, InterruptedException { if (args.length == 0) { showHelp(); @@ -46,10 +72,12 @@ public class Main { command.execute(remainingArgs); } + /** + * Displays a basic help message, listing available commands. + */ private void showHelp() { System.out.println("Älyverkko CLI\n"); System.out.println("Available commands:"); commands.forEach(cmd -> System.out.println(" " + cmd.getName())); } - -} \ No newline at end of file +} diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/MailQuery.java b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/MailQuery.java index 2ca0823..f1a5627 100644 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/MailQuery.java +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/MailQuery.java @@ -2,11 +2,34 @@ package eu.svjatoslav.alyverkko_cli.commands; import eu.svjatoslav.alyverkko_cli.model.Model; +/** + * Represents the data needed to perform a single mail-based AI query, + * containing prompts and the specific AI model to use. + */ public class MailQuery { + + /** + * The system prompt text that sets the context or role for the AI. + * This is often used to establish rules or background instructions + * for how the assistant should behave. + */ public String systemPrompt; + + /** + * The user's prompt text (the main request or query). + */ public String userPrompt; + + /** + * The AI model to be used for processing this query. + */ public Model model; + /** + * Returns a string containing a summary of the {@link MailQuery} object. + * + * @return a string with the system prompt, user prompt, and model info. + */ @Override public String toString() { return "MailQuery{" + diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/SelftestCommand.java b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/SelftestCommand.java index 7a3ac3e..3a7bd0f 100644 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/SelftestCommand.java +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/SelftestCommand.java @@ -10,96 +10,109 @@ import java.io.IOException; import static eu.svjatoslav.alyverkko_cli.Main.configuration; import static eu.svjatoslav.alyverkko_cli.configuration.Configuration.loadConfiguration; +/** + * Performs a series of checks to ensure that the Älyverkko CLI is + * configured correctly, including verifying directories, executables, + * and model definitions. + */ public class SelftestCommand implements Command { + /** + * @return the name of this command, i.e., "selftest". + */ @Override public String getName() { return "selftest"; } + /** + * Executes the self-test, checking if various directories, files, and + * configurations exist and are valid. + * + * @param cliArguments the command-line arguments following the "selftest" subcommand. + * @throws IOException if there are issues loading the configuration file. + */ @Override public void execute(String[] cliArguments) throws IOException { // Perform selftest checks here System.out.println("Starting selftest..."); configuration = loadConfiguration(); - boolean allChecksPassed = true; - + boolean didAllChecksPass = true; // Check if the configuration is loaded if (Main.configuration == null) { System.err.println("Configuration not found or invalid."); - allChecksPassed = false; - } - - // Validate models directory - if (!Main.configuration.getModelsDirectory().exists() || !Main.configuration.getModelsDirectory().isDirectory()) { - System.err.println("Models directory does not exist or is not a directory: " + Main.configuration.getModelsDirectory()); - allChecksPassed = false; - } - - // Validate llama-cli executable path - if (!Main.configuration.getLlamaCliPath().exists() || !Main.configuration.getLlamaCliPath().isFile()) { - System.err.println("llama-cli executable path does not point to existing file: " + Main.configuration.getLlamaCliPath()); - allChecksPassed = false; - } + didAllChecksPass = false; + } else { + // Validate models directory + if (!Main.configuration.getModelsDirectory().exists() || !Main.configuration.getModelsDirectory().isDirectory()) { + System.err.println("Models directory does not exist or is not a directory: " + Main.configuration.getModelsDirectory()); + didAllChecksPass = false; + } - // Validate mail directory - if (!Main.configuration.getMailDirectory().exists() || !Main.configuration.getMailDirectory().isDirectory()) { - System.err.println("Mail directory does not exist or is not a directory: " + Main.configuration.getMailDirectory()); - allChecksPassed = false; - } + // Validate llama-cli executable path + if (!Main.configuration.getLlamaCliPath().exists() || !Main.configuration.getLlamaCliPath().isFile()) { + System.err.println("llama-cli executable path does not point to existing file: " + Main.configuration.getLlamaCliPath()); + didAllChecksPass = false; + } + // Validate mail directory + if (!Main.configuration.getMailDirectory().exists() || !Main.configuration.getMailDirectory().isDirectory()) { + System.err.println("Mail directory does not exist or is not a directory: " + Main.configuration.getMailDirectory()); + didAllChecksPass = false; + } - // Ensure that there is at least one prompt file - File promptsDirectory = Main.configuration.getPromptsDirectory(); - if (promptsDirectory == null) { - System.err.println("Prompts directory is not defined in the configuration."); - allChecksPassed = false; - } else { - // Validate prompts directory - if (!promptsDirectory.exists() || !promptsDirectory.isDirectory()) { - System.err.println("Prompts directory does not exist or is not a directory: " + promptsDirectory); - allChecksPassed = false; + // Ensure that there is at least one prompt file + File promptsDirectory = Main.configuration.getPromptsDirectory(); + if (promptsDirectory == null) { + System.err.println("Prompts directory is not defined in the configuration."); + didAllChecksPass = false; } else { - if (promptsDirectory.listFiles() == null || promptsDirectory.listFiles().length == 0) { - System.err.println("No prompt files found in the prompts directory: " + promptsDirectory); - allChecksPassed = false; + if (!promptsDirectory.exists() || !promptsDirectory.isDirectory()) { + System.err.println("Prompts directory does not exist or is not a directory: " + promptsDirectory); + didAllChecksPass = false; + } else { + if (promptsDirectory.listFiles() == null || promptsDirectory.listFiles().length == 0) { + System.err.println("No prompt files found in the prompts directory: " + promptsDirectory); + didAllChecksPass = false; + } } } - } - // Validate models - if (Main.configuration.getModels().isEmpty()) { - System.err.println("No models are defined in the configuration."); - allChecksPassed = false; - } + // Validate models + if (Main.configuration.getModels().isEmpty()) { + System.err.println("No models are defined in the configuration."); + didAllChecksPass = false; + } - // Validate default temperature - if (Main.configuration.getDefaultTemperature() < 0 || Main.configuration.getDefaultTemperature() > 3) { - System.err.println("Default temperature must be between 0 and 3."); - allChecksPassed = false; - } + // Validate default temperature + if (Main.configuration.getDefaultTemperature() < 0 || Main.configuration.getDefaultTemperature() > 3) { + System.err.println("Default temperature must be between 0 and 3."); + didAllChecksPass = false; + } - // Validate thread count - if (Main.configuration.getThreadCount() < 1) { - System.err.println("Thread count must be at least 1."); - allChecksPassed = false; - } + // Validate thread count + if (Main.configuration.getThreadCount() < 1) { + System.err.println("Thread count must be at least 1."); + didAllChecksPass = false; + } - // Validate batch thread count - if (Main.configuration.getBatchThreadCount() < 1) { - System.err.println("Batch thread count must be at least 1."); - allChecksPassed = false; - } + // Validate batch thread count + if (Main.configuration.getBatchThreadCount() < 1) { + System.err.println("Batch thread count must be at least 1."); + didAllChecksPass = false; + } - // Validate models - if (Main.configuration.getModels().isEmpty()) { - System.err.println("No models are defined in the configuration."); - allChecksPassed = false; + // Validate models again (redundant check but ensures clarity) + if (Main.configuration.getModels().isEmpty()) { + System.err.println("No models are defined in the configuration."); + didAllChecksPass = false; + } } - if (allChecksPassed) + if (didAllChecksPass) { System.out.println("Selftest completed successfully."); + } } } diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/WizardCommand.java b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/WizardCommand.java index 9ccbff6..00c3c74 100644 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/WizardCommand.java +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/WizardCommand.java @@ -22,23 +22,44 @@ import java.util.stream.Collectors; import static eu.svjatoslav.alyverkko_cli.configuration.Configuration.DEFAULT_CONFIG_FILE_PATH; import static eu.svjatoslav.commons.cli_helper.CLIHelper.*; +/** + * The WizardCommand provides an interactive, console-based + * configuration wizard for setting up the Älyverkko CLI application. + */ public class WizardCommand implements Command { private final Parser parser = new Parser(); - private final NullOption forceOption = parser.add(new NullOption("Force overwrite existing configuration")) + + /** + * Command-line option to force overwriting an existing configuration file. + */ + private final NullOption forceOverwriteOption = parser.add(new NullOption("Force overwrite existing configuration")) .addAliases("--force", "-f"); + /** + * An existing configuration, if one is found on disk. + */ private Configuration existingConfig; + /** + * @return the name of this command, i.e., "wizard". + */ @Override public String getName() { return "wizard"; } + /** + * Executes this command, launching an interactive configuration + * wizard in the console. + * + * @param cliArguments the command-line arguments following the "wizard" subcommand. + * @throws IOException in case of IO failures while reading/writing config files. + */ @Override public void execute(String[] cliArguments) throws IOException { if (!parser.parse(cliArguments)) { - System.out.println("Failed to parse commandline arguments"); + System.out.println("Failed to parse command-line arguments"); parser.showHelp(); return; } @@ -49,46 +70,45 @@ public class WizardCommand implements Command { System.out.println("Found existing configuration. Current values will be used as defaults."); } - Configuration config = new Configuration(); - Scanner scanner = new Scanner(System.in); + Configuration newConfig = new Configuration(); System.out.println("\nStarting Älyverkko CLI configuration wizard.\n"); // Mail Directory - String mailDir = getInputWithDefault( + String mailDirectory = getInputWithDefault( "\nEnter the mail directory for AI tasks. This is where your task files will be stored.", existingConfig != null ? existingConfig.getMailDirectory().getPath() : "~/.config/alyverkko-cli/mail" ); - config.setMailDirectory(new File(mailDir)); + newConfig.setMailDirectory(new File(mailDirectory)); // Models Directory - String modelsDir = getInputWithDefault( + String modelsDirectory = getInputWithDefault( "\nEnter the directory for AI models. This should contain your GGUF model files.", existingConfig != null ? existingConfig.getModelsDirectory().getPath() : "~/.config/alyverkko-cli/models" ); - config.setModelsDirectory(new File(modelsDir)); + newConfig.setModelsDirectory(new File(modelsDirectory)); // Prompts Directory - String promptsDir = getInputWithDefault( + String promptsDirectory = getInputWithDefault( "\nEnter the directory for prompts. This should contain text files with your AI prompts.", existingConfig != null ? existingConfig.getPromptsDirectory().getPath() : "~/.config/alyverkko-cli/prompts" ); - config.setPromptsDirectory(new File(promptsDir)); + newConfig.setPromptsDirectory(new File(promptsDirectory)); // Llama CLI Path - String llamaCliPath = getInputWithDefault( + String llamaCliExecutablePath = getInputWithDefault( "\nEnter the path to the llama-cli executable. This is the command-line interface for llama.cpp.", existingConfig != null ? existingConfig.getLlamaCliPath().getPath() : "/usr/local/bin/llama-cli" ); - config.setLlamaCliPath(new File(llamaCliPath)); + newConfig.setLlamaCliPath(new File(llamaCliExecutablePath)); // Default Temperature - float defaultTemp = askFloat( + float defaultTemperature = askFloat( "\nEnter default temperature (0-2). A lower value makes the AI more deterministic, while a higher value makes it more creative.", existingConfig != null ? existingConfig.getDefaultTemperature() : 0.7f, 0f, 2f ); - config.setDefaultTemperature(defaultTemp); + newConfig.setDefaultTemperature(defaultTemperature); // Thread Counts int threadCount = askInteger( @@ -101,7 +121,7 @@ public class WizardCommand implements Command { existingConfig != null ? existingConfig.getThreadCount() : 6, 1, null ); - config.setThreadCount(threadCount); + newConfig.setThreadCount(threadCount); int batchThreadCount = askInteger( "\nEnter number of CPU threads for input prompt processing. " + @@ -110,7 +130,7 @@ public class WizardCommand implements Command { existingConfig != null ? existingConfig.getBatchThreadCount() : 10, 1, null ); - config.setBatchThreadCount(batchThreadCount); + newConfig.setBatchThreadCount(batchThreadCount); // Models Setup System.out.println("\nModel configuration."); @@ -120,7 +140,7 @@ public class WizardCommand implements Command { } if (askBoolean("Would you like to try to autodiscover available undefined models ?", true)) { - discoverAndSuggestNewModels(config.getModelsDirectory(), models); + discoverAndSuggestNewModels(newConfig.getModelsDirectory(), models); } System.out.println("\nYou can now add models manually."); @@ -148,11 +168,11 @@ public class WizardCommand implements Command { models.add(model); } - config.setModels(models); + newConfig.setModels(models); // Write configuration Path configPath = Path.of(DEFAULT_CONFIG_FILE_PATH); - if (Files.exists(configPath) && !forceOption.isPresent()) { + if (Files.exists(configPath) && !forceOverwriteOption.isPresent()) { if (!askBoolean("Configuration file already exists. Overwrite?", false)) { System.out.println("Configuration not saved. Run with --force to overwrite."); return; @@ -164,13 +184,20 @@ public class WizardCommand implements Command { // Save configuration try (var writer = Files.newBufferedWriter(configPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { - new ObjectMapper(new YAMLFactory()).writeValue(writer, config); + new ObjectMapper(new YAMLFactory()).writeValue(writer, newConfig); } System.out.println("\nConfiguration saved to: " + configPath); System.out.println("You can now run 'alyverkko-cli selftest' to verify everything is set up correctly."); } + /** + * Prompts the user for an input value, showing a default value if provided. + * + * @param prompt the prompt to display to the user. + * @param defaultValue the default value to use if the user simply presses Enter. + * @return the user's input, or the default if no input is provided. + */ private String getInputWithDefault(String prompt, String defaultValue) { Scanner scanner = new Scanner(System.in); @@ -184,6 +211,13 @@ public class WizardCommand implements Command { return input.isEmpty() ? defaultValue : input; } + /** + * Attempts to discover new .gguf model files in the given models directory, and + * suggests adding them to the configuration if they aren't already defined. + * + * @param modelsDir the directory containing model files. + * @param existingModels existing model definitions to compare against. + */ private void discoverAndSuggestNewModels(File modelsDir, List existingModels) { if (!modelsDir.exists()) { System.out.println("Models directory does not exist. Please create it first."); @@ -207,14 +241,22 @@ public class WizardCommand implements Command { boolean alreadyExists = existingModels.stream() .anyMatch(m -> m.getFilesystemPath().equals(relativePath)); - if (!alreadyExists) suggestAddingModel(existingModels, relativePath, existingAliases); - + if (!alreadyExists) { + suggestAddingModel(existingModels, relativePath, existingAliases); + } } } catch (IOException e) { System.err.println("Error scanning models directory: " + e.getMessage()); } } + /** + * Suggests to the user adding a newly-discovered model to the configuration. + * + * @param existingModels the list of existing models to which we might add a new one. + * @param relativePath the model file's path relative to the models directory. + * @param existingAliases the aliases already defined for known models. + */ private void suggestAddingModel(List existingModels, String relativePath, List existingAliases) { String alias = suggestAlias(relativePath); System.out.println("\nFound new model: " + relativePath); @@ -234,11 +276,16 @@ public class WizardCommand implements Command { } } + /** + * Generates a suggested model alias by removing non-alphanumeric characters, + * converting to lower case, and normalizing multiple dashes. + * + * @param filePath path string from which to suggest an alias. + * @return a suggested alias, derived from the model file name. + */ private String suggestAlias(String filePath) { String fileName = new File(filePath).getName(); String alias = fileName.replaceAll("[^a-zA-Z0-9]", "-").toLowerCase(); return alias.replaceAll("-+", "-").replaceAll("^-|-$", ""); } - - -} \ No newline at end of file +} -- 2.20.1