--- /dev/null
+package eu.svjatoslav.alyverkko_cli.commands;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import eu.svjatoslav.alyverkko_cli.Command;
+import eu.svjatoslav.alyverkko_cli.configuration.Configuration;
+import eu.svjatoslav.alyverkko_cli.configuration.ConfigurationModel;
+import eu.svjatoslav.commons.cli_helper.parameter_parser.Parser;
+import eu.svjatoslav.commons.cli_helper.parameter_parser.parameter.NullOption;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+
+import static eu.svjatoslav.alyverkko_cli.configuration.Configuration.DEFAULT_CONFIG_FILE_PATH;
+
+public class WizardCommand implements Command {
+
+ private final Parser parser = new Parser();
+ private final NullOption forceOption = parser.add(new NullOption("Force overwrite existing configuration"))
+ .addAliases("--force", "-f");
+
+ @Override
+ public String getName() {
+ return "wizard";
+ }
+
+ @Override
+ public void execute(String[] cliArguments) throws IOException {
+ if (!parser.parse(cliArguments)) {
+ System.out.println("Failed to parse commandline arguments");
+ parser.showHelp();
+ return;
+ }
+
+ Configuration config = new Configuration();
+ Scanner scanner = new Scanner(System.in);
+
+ System.out.println("\nStarting Älyverkko CLI configuration wizard.\n");
+
+ // Mail Directory
+ String mailDir = getInput("Enter the mail directory for AI tasks",
+ "~/.config/alyverkko-cli/mail");
+ config.setMailDirectory(new File(mailDir));
+
+ // Models Directory
+ String modelsDir = getInput("Enter the directory for AI models",
+ "~/.config/alyverkko-cli/models");
+ config.setModelsDirectory(new File(modelsDir));
+
+ // Prompts Directory
+ String promptsDir = getInput("Enter the directory for prompts",
+ "~/.config/alyverkko-cli/prompts");
+ config.setPromptsDirectory(new File(promptsDir));
+
+ // Llama CLI Path
+ String llamaCliPath = getInput("Enter the path to llama-cli executable",
+ "/usr/local/bin/llama-cli");
+ config.setLlamaCliPath(new File(llamaCliPath));
+
+ // Default Temperature
+ float defaultTemp = getFloatInput("Enter default temperature", 0.7f, 0f, 1f);
+ config.setDefaultTemperature(defaultTemp);
+
+ // Thread Counts
+ int threadCount = getIntInput("Enter number of threads for AI processing", 6, 1, 16);
+ config.setThreadCount(threadCount);
+
+ int batchThreadCount = getIntInput("Enter number of batch threads", 10, 1, 32);
+ config.setBatchThreadCount(batchThreadCount);
+
+ // Models Setup
+ System.out.println("\nModel configuration:");
+ List<ConfigurationModel> models = new ArrayList<>();
+ while (true) {
+ System.out.println("\nEnter model details or leave alias empty to finish.");
+ String alias = getInput("Model alias: ");
+ if (StringUtils.isBlank(alias)) break;
+
+ String filePath = getInput("Filesystem path: ");
+ int contextSize = getIntInput("Context size (tokens): ", 64000, 1024, 128000);
+
+ ConfigurationModel model = new ConfigurationModel();
+ model.setAlias(alias);
+ model.setFilesystemPath(filePath);
+ model.setContextSizeTokens(contextSize);
+ models.add(model);
+ }
+ config.setModels(models);
+
+ // Write configuration
+ Path configPath = Path.of(DEFAULT_CONFIG_FILE_PATH);
+ if (Files.exists(configPath) && !forceOption.isPresent()) {
+ String confirm = getInput("Configuration file already exists. Overwrite? (y/N)", "N");
+ if (!confirm.equalsIgnoreCase("Y")) {
+ System.out.println("Configuration not saved. Run with --force to overwrite.");
+ return;
+ }
+ }
+
+ // Create parent directories
+ Files.createDirectories(configPath.getParent());
+
+ // Save configuration
+ try (var writer = Files.newBufferedWriter(configPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
+ new ObjectMapper(new YAMLFactory()).writeValue(writer, config);
+ }
+
+ System.out.println("\nConfiguration saved to: " + configPath);
+ System.out.println("You can now run 'alyverkko-cli selftest' to verify everything is set up correctly.");
+ }
+
+ private String getInput(String prompt, String defaultValue) {
+ Scanner scanner = new Scanner(System.in);
+
+ if (defaultValue == null) {
+ System.out.print(prompt + ": ");
+ } else {
+ System.out.print(prompt + " [" + defaultValue + "]: ");
+ }
+ String input = scanner.nextLine().trim();
+
+ return input.isEmpty() ? defaultValue : input;
+ }
+
+ private String getInput(String prompt) {
+ return getInput(prompt, null);
+ }
+
+ private float getFloatInput(String prompt, float defaultValue, float min, float max) {
+ while (true) {
+ String input = getInput(prompt, String.valueOf(defaultValue));
+ try {
+ float value = Float.parseFloat(input);
+ if (value >= min && value <= max) return value;
+ System.out.println("Value must be between " + min + " and " + max);
+ } catch (NumberFormatException e) {
+ System.out.println("Invalid number format. Try again.");
+ }
+ }
+ }
+
+ private int getIntInput(String prompt, int defaultValue, int min, int max) {
+ while (true) {
+ String input = getInput(prompt, String.valueOf(defaultValue));
+ try {
+ int value = Integer.parseInt(input);
+ if (value >= min && value <= max) return value;
+ System.out.println("Value must be between " + min + " and " + max);
+ } catch (NumberFormatException e) {
+ System.out.println("Invalid number format. Try again.");
+ }
+ }
+ }
+
+}