From bba1109b19fddf05b6d59804d8ae7d7726775381 Mon Sep 17 00:00:00 2001 From: Svjatoslav Agejenko Date: Sat, 17 Jan 2026 05:52:14 +0200 Subject: [PATCH] Add `AddTaskHeaderCommand` to support adding TOCOMPUTE headers to files --- .../eu/svjatoslav/alyverkko_cli/Main.java | 5 +- .../eu/svjatoslav/alyverkko_cli/Utils.java | 32 +++ .../commands/AddTaskHeaderCommand.java | 186 ++++++++++++++++++ .../alyverkko_cli/commands/package-info.java | 1 + .../task_processor/TaskProcessorCommand.java | 20 +- 5 files changed, 224 insertions(+), 20 deletions(-) create mode 100644 src/main/java/eu/svjatoslav/alyverkko_cli/commands/AddTaskHeaderCommand.java diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/Main.java b/src/main/java/eu/svjatoslav/alyverkko_cli/Main.java index 289ede4..daea211 100644 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/Main.java +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/Main.java @@ -12,7 +12,7 @@ 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". + * "process", etc... */ public class Main { @@ -23,7 +23,8 @@ public class Main { new ListModelsCommand(), new TaskProcessorCommand(), new JoinFilesCommand(), - new WizardCommand() + new WizardCommand(), + new AddTaskHeaderCommand() ); /** diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/Utils.java b/src/main/java/eu/svjatoslav/alyverkko_cli/Utils.java index 87e8531..c01c2a7 100644 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/Utils.java +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/Utils.java @@ -1,5 +1,10 @@ package eu.svjatoslav.alyverkko_cli; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; + /** *

General utility functions for the Älyverkko CLI application. Currently provides ANSI color output capabilities for * console messages. @@ -20,4 +25,31 @@ public class Utils { // reset output color System.out.print("\033[0m"); } + + /** + * Reads the first line of a file. + * + * @param file File to read + * @return First line of file or null if empty + * @throws IOException if file reading fails + */ + public static String getFirstLine(File file) throws IOException { + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + return reader.readLine(); + } + } + + /** + * Inspects the first line of the file to see if it starts with "TOCOMPUTE:". + * + * @param file the file to read. + * @return true if the file's first line starts with "TOCOMPUTE:". + * @throws IOException if file reading fails. + */ + public static boolean fileHasToComputeMarker(File file) throws IOException { + String firstLine = getFirstLine(file); + return firstLine != null && firstLine.startsWith("TOCOMPUTE:"); + } + + } diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/AddTaskHeaderCommand.java b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/AddTaskHeaderCommand.java new file mode 100644 index 0000000..d6dc0c7 --- /dev/null +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/AddTaskHeaderCommand.java @@ -0,0 +1,186 @@ +package eu.svjatoslav.alyverkko_cli.commands; +import eu.svjatoslav.alyverkko_cli.Command; +import eu.svjatoslav.alyverkko_cli.configuration.Configuration; +import eu.svjatoslav.alyverkko_cli.configuration.ConfigurationHelper; +import eu.svjatoslav.alyverkko_cli.configuration.Model; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.*; +import java.util.*; + +import static eu.svjatoslav.alyverkko_cli.Utils.fileHasToComputeMarker; + +/** + * This command recursively adds a TOCOMPUTE header to all non-hidden files in the current directory + * that do not already have one. It prompts the user for skill, model, and priority values, + * validates their existence in the configuration, and then processes the files accordingly. + *

+ * Usage: + *

+ *   alyverkko-cli addheader
+ * 
+ * The command will interactively prompt for: + * - Skill name (must exist in skills directory) + * - Model alias (must exist in configuration models) + * - Priority value (integer, defaults to 0) + *

+ * After validation, it will process all non-hidden files in the current directory and subdirectories, + * adding the TOCOMPUTE header at the beginning of files that don't already have one. + */ +public class AddTaskHeaderCommand implements Command { + + @Override + public String getCommandName() { + return "addheader"; + } + + /** + * Executes the addheader command. Loads configuration, prompts user for skill, model, and priority, + * validates them, and processes all files in the current directory recursively. + * + * @param cliArguments command-line arguments (unused in this command) + * @throws IOException if file operations fail + * @throws InterruptedException if interrupted during processing + */ + @Override + public void executeCommand(String[] cliArguments) throws IOException, InterruptedException { + // Load configuration to validate skills and models + Configuration config = ConfigurationHelper.loadConfiguration(ConfigurationHelper.getConfigurationFile(null)); + if (config == null) { + System.err.println("ERROR: Failed to load configuration file"); + return; + } + + Scanner scanner = new Scanner(System.in); + String skill = promptForSkill(scanner, config); + String model = promptForModel(scanner, config); + int priority = promptForPriority(scanner); + + System.out.println("\nProcessing files in current directory..."); + processDirectory(new File("."), skill, model, priority); + System.out.println("\nProcessing complete!"); + } + + /** + * Prompts user for skill name and validates it exists in skills directory. + * + * @param scanner Scanner for user input + * @param config Current configuration + * @return Validated skill name + */ + private String promptForSkill(Scanner scanner, Configuration config) { + while (true) { + System.out.print("Enter skill name: "); + String skill = scanner.nextLine().trim(); + File skillFile = new File(config.getSkillsDirectory(), skill + ".yaml"); + if (skillFile.exists() && skillFile.isFile()) { + return skill; + } + System.out.println("ERROR: Skill '" + skill + "' not found in " + config.getSkillsDirectory()); + System.out.println("Available skills: " + Arrays.toString(config.getSkillsDirectory().list((dir, name) -> name.endsWith(".yaml")))); + } + } + + /** + * Prompts user for model alias and validates it exists in configuration. + * + * @param scanner Scanner for user input + * @param config Current configuration + * @return Validated model alias + */ + private String promptForModel(Scanner scanner, Configuration config) { + List models = config.getModels(); + if (models == null || models.isEmpty()) { + System.err.println("ERROR: No models configured. Please check your configuration file."); + System.exit(1); + } + + Map modelMap = new HashMap<>(); + for (Model model : models) { + modelMap.put(model.getAlias(), model); + } + + while (true) { + System.out.print("Enter model alias: "); + String alias = scanner.nextLine().trim(); + if (modelMap.containsKey(alias)) { + return alias; + } + System.out.println("ERROR: Model '" + alias + "' not found. Available models: " + String.join(", ", modelMap.keySet())); + } + } + + /** + * Prompts user for priority value with validation. + * + * @param scanner Scanner for user input + * @return Validated priority integer + */ + private int promptForPriority(Scanner scanner) { + while (true) { + System.out.print("Enter priority (integer, default 0): "); + String input = scanner.nextLine().trim(); + if (input.isEmpty()) { + return 0; + } + try { + int priority = Integer.parseInt(input); + if (priority < 0) { + System.out.println("Priority must be non-negative. Using 0 as default."); + return 0; + } + return priority; + } catch (NumberFormatException e) { + System.out.println("ERROR: Invalid priority. Please enter an integer."); + } + } + } + + /** + * Recursively processes all files in a directory, skipping hidden files. + * + * @param dir Directory to process + * @param skill Skill name to include in TOCOMPUTE header + * @param model Model alias to include in TOCOMPUTE header + * @param priority Priority value to include in TOCOMPUTE header + * @throws IOException if file operations fail + */ + private void processDirectory(File dir, String skill, String model, int priority) throws IOException { + File[] files = dir.listFiles(); + if (files == null) return; + + for (File file : files) { + if (file.isDirectory()) { + processDirectory(file, skill, model, priority); + } else if (file.isFile() && !file.getName().startsWith(".")) { + processFile(file, skill, model, priority); + } + } + } + + /** + * Processes a single file by adding TOCOMPUTE header if not already present. + * + * @param file File to process + * @param skill Skill name for TOCOMPUTE header + * @param model Model alias for TOCOMPUTE header + * @param priority Priority value for TOCOMPUTE header + * @throws IOException if file operations fail + */ + private void processFile(File file, String skill, String model, int priority) throws IOException { + + if (fileHasToComputeMarker(file)) { + System.out.println("Skipped (already has header): " + file.getAbsolutePath()); + return; + } + + String content = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8); + String newContent = String.format("TOCOMPUTE: skill=%s model=%s priority=%d\n%s", skill, model, priority, content); + + Files.write(file.toPath(), newContent.getBytes(StandardCharsets.UTF_8)); + System.out.println("Added header: " + file.getAbsolutePath()); + } + + +} diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/package-info.java b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/package-info.java index 871a60b..906a20e 100644 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/package-info.java +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/package-info.java @@ -7,6 +7,7 @@ *

  • Model listing and management
  • *
  • File joining for multi-file processing
  • *
  • Mail-based AI task processing
  • + *
  • Adding TOCOMPUTE headers to files
  • * */ diff --git a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/task_processor/TaskProcessorCommand.java b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/task_processor/TaskProcessorCommand.java index c811509..7ac02c2 100644 --- a/src/main/java/eu/svjatoslav/alyverkko_cli/commands/task_processor/TaskProcessorCommand.java +++ b/src/main/java/eu/svjatoslav/alyverkko_cli/commands/task_processor/TaskProcessorCommand.java @@ -16,6 +16,8 @@ import java.nio.file.*; import java.util.*; import static eu.svjatoslav.alyverkko_cli.Main.configuration; +import static eu.svjatoslav.alyverkko_cli.Utils.fileHasToComputeMarker; +import static eu.svjatoslav.alyverkko_cli.Utils.getFirstLine; import static eu.svjatoslav.commons.file.IOHelper.getFileContentsAsString; import static eu.svjatoslav.commons.file.IOHelper.saveToFile; import static java.nio.file.StandardWatchEventKinds.*; @@ -181,24 +183,6 @@ public class TaskProcessorCommand implements Command { return fileHasToComputeMarker(file); } - /** - * Inspects the first line of the file to see if it starts with "TOCOMPUTE:". - * - * @param file the file to read. - * @return true if the file's first line starts with "TOCOMPUTE:". - * @throws IOException if file reading fails. - */ - private static boolean fileHasToComputeMarker(File file) throws IOException { - String firstLine = getFirstLine(file); - return firstLine != null && firstLine.startsWith("TOCOMPUTE:"); - } - - private static String getFirstLine(File file) throws IOException { - try (BufferedReader reader = new BufferedReader(new FileReader(file))) { - return reader.readLine(); - } - } - /** * Persists AI response to the file; deletes the original file */ -- 2.20.1