Ready-to-run starter scripts for the three most common CLI tasks. Copy any script, run it immediately, then extend it for your own use case.
Sort every file in a directory into a subfolder named after its extension. Files with no extension land in no_ext/.
Usage:
minks file_organizer.ks <directory>
Sample output:
Scanning 4 entries in './downloads'...
report.pdf → pdf/
photo.jpg → jpg/
notes.txt → txt/
resume.pdf → pdf/
Done. 4 file(s) organised.
Script:
// file_organizer.ks - Sort files in a directory into extension-based subfolders
// Usage: minks file_organizer.ks <directory>
//
// Example:
// minks file_organizer.ks ./downloads
//
// Before:
// downloads/report.pdf
// downloads/photo.jpg
// downloads/notes.txt
// downloads/resume.pdf
//
// After:
// downloads/pdf/report.pdf
// downloads/pdf/resume.pdf
// downloads/jpg/photo.jpg
// downloads/txt/notes.txt
//
// Modules used: OS, Path, File, String, List
// ── 1. Read the target directory from the command line ────────────────────────
// OS:Args fills a pre-declared List:String with the positional script arguments.
List:New args:String;
OS:Args(args);
Var:Number argc;
List:Size(args, argc);
if (argc < 1) {
Konsol:Print("Usage: minks file_organizer.ks <directory>");
Konsol:Exit(1);
}
Var:String dir;
List:Get(0, args, dir);
// ── 2. Validate that the path is a directory ──────────────────────────────────
Var:Boolean isDir;
Path:IsDirectory(dir, isDir);
if (isDir == false) {
Konsol:Print("Error: '${dir}' is not a directory.");
Konsol:Exit(1);
}
// ── 3. List all entries in the directory ──────────────────────────────────────
// OS:ListDirectory fills the list with filenames (sorted); it does not include paths.
List:New entries:String;
OS:ListDirectory(dir, entries);
Var:Number total;
List:Size(entries, total);
Konsol:Print("Scanning ${total} entries in '${dir}'...");
// ── 4. Move each file into its extension subfolder ────────────────────────────
// Declare all working variables up front (KonsolScript scoping convention).
Var:Number i = 0;
Var:Number moved = 0;
Var:String name; // filename from the listing
Var:String src; // full source path
Var:String ext; // raw extension, e.g. ".pdf"
Var:String folder; // clean folder name, e.g. "pdf"
Var:String subdir; // full path to the target subfolder
Var:String dest; // full destination path
Var:Boolean isFile;
Var:Boolean ok;
while (i < total) {
// Build the full source path from the directory and filename.
List:Get(i, entries, name);
Path:Join(dir, name, src);
// Skip subdirectories - only move regular files.
Path:IsFile(src, isFile);
if (isFile) {
// Path:Extension returns the extension with its leading dot, e.g. ".txt".
Path:Extension(name, ext);
if (ext == "") {
// Files with no extension go into a catch-all folder.
folder = "no_ext";
} else {
// Strip the dot so ".pdf" becomes the folder name "pdf".
String:Replace(ext, ".", "", folder);
}
// Create the subfolder only if it does not exist yet.
Path:Join(dir, folder, subdir);
Path:IsDirectory(subdir, isDir);
if (isDir == false) {
OS:MakeDirectory(subdir, ok);
}
// Move the file by renaming its path.
Path:Join(subdir, name, dest);
OS:Rename(src, dest);
Konsol:Print(" ${name} → ${folder}/");
moved = moved + 1;
}
i = i + 1;
}
Konsol:Print("Done. ${moved} file(s) organised.");
Read a task file line by line and execute echo, check, and run tasks. Exits with code 1 if any task fails - suitable for CI scripts.
Modules: File OS Path Regex List String
Usage:
minks build_runner.ks <tasks.txt>
Task file format (tasks.txt):
# Lines starting with # are comments and are skipped.
echo: <message> - print a section header (no pass/fail)
check: <path> - verify a file or directory exists
run: <shell command> - run a command; non-zero exit code = failure
Sample tasks.txt:
# tasks.txt - sample task file for build_runner.ks
# Run with: minks build_runner.ks tasks.txt
# (from inside the cli-tools-and-automation/ folder)
#
# Format:
# echo: <message> - section header (no pass/fail)
# check: <path> - verify a file or directory exists
# run: <shell command> - run a command; non-zero exit = failure
echo: Checking sample scripts are present
check: file_organizer.ks
check: build_runner.ks
check: log_analyzer.ks
check: sample.log
echo: Running a quick shell sanity check
run: echo hello from build_runner
echo: Done
Sample output:
── Checking sample scripts are present
[OK] check: file_organizer.ks
[OK] check: build_runner.ks
[OK] check: log_analyzer.ks
[OK] check: sample.log
── Running a quick shell sanity check
[OK] run: echo hello from build_runner
── Done
4/4 tasks passed.
Script:
// build_runner.ks - Lightweight task runner that reads a task file
// Usage: minks build_runner.ks <tasks.txt>
//
// Task file format (see tasks.txt for a ready-to-run example):
// # Lines starting with # are comments and are skipped.
// echo: <message> - print a section header (no pass/fail)
// check: <path> - verify a file or directory exists
// run: <shell command> - run a command; non-zero exit code = failure
//
// The runner prints [OK] or [FAIL] for every check/run task and exits
// with code 1 if any task failed.
//
// Modules used: File, OS, Path, Regex, List, String
// ── 1. Read the task file path from the command line ─────────────────────────
List:New args:String;
OS:Args(args);
Var:Number argc;
List:Size(args, argc);
if (argc < 1) {
Konsol:Print("Usage: minks build_runner.ks <tasks.txt>");
Konsol:Exit(1);
}
Var:String taskFile;
List:Get(0, args, taskFile);
// ── 2. Verify the task file exists ───────────────────────────────────────────
Var:Boolean fileExists;
File:Exists(taskFile, fileExists);
if (fileExists == false) {
Konsol:Print("Task file not found: ${taskFile}");
Konsol:Exit(1);
}
// ── 3. Open the file and process each line ───────────────────────────────────
// Declare all working variables before the loop.
Var:Number fh;
File:Open(taskFile, "r", fh);
Var:Number passed = 0;
Var:Number failed = 0;
Var:String line;
Var:String trimmed;
Var:Boolean eof;
Var:Boolean isComment;
Var:Boolean pathOk;
Var:Number exitCode;
Var:Number gc;
Var:String verb;
Var:String value;
// Regex:Groups writes its captures into a pre-declared List:String.
// We call List:Clear before each use so stale results never bleed across lines.
List:New groups:String;
File:EOF(fh, eof);
while (eof == false) {
File:ReadLine(fh, line);
// Trim whitespace; skip blank lines entirely.
String:Trim(line, trimmed);
if (trimmed != "") {
// Skip comment lines (any line whose first non-space character is #).
Regex:Test("^#", trimmed, isComment);
if (isComment == false) {
// Parse "verb: value" with one regex.
// Group 1 - the verb: echo, check, or run
// Group 2 - everything after the colon and optional whitespace
List:Clear(groups);
Regex:Groups("^(echo|check|run):\\s*(.+)$", trimmed, groups);
List:Size(groups, gc);
// gc == 3 means: [full-match, group1, group2]
if (gc >= 3) {
List:Get(1, groups, verb);
List:Get(2, groups, value);
String:Lower(verb, verb);
if (verb == "echo") {
// Section header - print it but do not score it.
Konsol:Print("");
Konsol:Print("── ${value}");
} else {
if (verb == "check") {
// Path:Exists returns true for both files and directories.
Path:Exists(value, pathOk);
if (pathOk) {
Konsol:Print(" [OK] check: ${value}");
passed = passed + 1;
} else {
Konsol:Print(" [FAIL] check: ${value} (not found)");
failed = failed + 1;
}
} else {
// verb == "run" - OS:System runs the command and writes
// the exit code into the last argument.
OS:System(value, exitCode);
if (exitCode == 0) {
Konsol:Print(" [OK] run: ${value}");
passed = passed + 1;
} else {
Konsol:Print(" [FAIL] run: ${value} (exit ${exitCode})");
failed = failed + 1;
}
}
}
}
}
}
File:EOF(fh, eof);
}
File:Close(fh);
// ── 4. Summary ───────────────────────────────────────────────────────────────
Var:Number total2;
total2 = passed + failed;
Konsol:Print("");
Konsol:Print("${passed}/${total2} tasks passed.");
if (failed > 0) {
Konsol:Exit(1);
}
Parse a structured log file, count lines by severity level, and print the last five error messages with their timestamps.
Modules: File Regex String List Konsol
Usage:
minks log_analyzer.ks <logfile>
Expected log format:
2025-01-15 10:23:45 [INFO] server started on port 8080
2025-01-15 10:24:12 [WARN] memory usage above 80%
2025-01-15 10:24:45 [ERROR] database connection refused
Sample sample.log:
2025-01-15 10:23:40 [INFO] application starting
2025-01-15 10:23:41 [INFO] loading configuration from config.json
2025-01-15 10:23:42 [INFO] connected to database
2025-01-15 10:23:45 [INFO] server started on port 8080
2025-01-15 10:24:12 [WARN] memory usage above 80%
2025-01-15 10:24:30 [INFO] request GET /api/users 200
2025-01-15 10:24:45 [ERROR] database connection refused: timeout after 30s
2025-01-15 10:24:46 [INFO] retrying connection (attempt 1)
2025-01-15 10:24:50 [INFO] retrying connection (attempt 2)
2025-01-15 10:24:55 [ERROR] database connection refused: max retries exceeded
2025-01-15 10:25:00 [WARN] falling back to read-only mode
2025-01-15 10:25:05 [INFO] request GET /api/status 200
2025-01-15 10:25:30 [ERROR] failed to write session data: disk quota exceeded
2025-01-15 10:25:45 [INFO] request GET /api/health 200
2025-01-15 10:26:00 [WARN] disk usage above 90%
2025-01-15 10:26:10 [ERROR] unable to create temp file: /tmp/app_cache_001
2025-01-15 10:26:15 [INFO] cleanup task started
2025-01-15 10:26:20 [INFO] cleanup task finished - freed 2.1 GB
2025-01-15 10:26:25 [ERROR] audit log write failed: permission denied
2025-01-15 10:26:30 [INFO] graceful shutdown initiated
Sample output:
Log summary: sample.log
Total lines : 20
INFO : 12
WARN : 3
ERROR : 5
Recent errors:
2025-01-15 10:24:55 database connection refused: max retries exceeded
2025-01-15 10:25:30 failed to write session data: disk quota exceeded
2025-01-15 10:26:10 unable to create temp file: /tmp/app_cache_001
2025-01-15 10:26:25 audit log write failed: permission denied
Script:
// log_analyzer.ks - Parse a structured log file; report level counts + recent errors
// Usage: minks log_analyzer.ks <logfile>
//
// Expected log format (one entry per line):
// 2025-01-15 10:23:45 [INFO] server started on port 8080
// 2025-01-15 10:24:12 [WARN] memory usage above 80%
// 2025-01-15 10:24:45 [ERROR] database connection refused
//
// Try it with the bundled sample:
// minks log_analyzer.ks sample.log
//
// Output:
// - Total lines parsed
// - Count per level (INFO / WARN / ERROR)
// - Last 5 error messages with their timestamps
//
// Modules used: File, Regex, String, List, Konsol
// ── 1. Read the log file path from the command line ───────────────────────────
List:New args:String;
OS:Args(args);
Var:Number argc;
List:Size(args, argc);
if (argc < 1) {
Konsol:Print("Usage: minks log_analyzer.ks <logfile>");
Konsol:Exit(1);
}
Var:String logFile;
List:Get(0, args, logFile);
// ── 2. Verify the file exists ─────────────────────────────────────────────────
Var:Boolean fileExists;
File:Exists(logFile, fileExists);
if (fileExists == false) {
Konsol:Print("File not found: ${logFile}");
Konsol:Exit(1);
}
// ── 3. Open and parse the log line by line ────────────────────────────────────
// Pre-declare all working variables before the loop (KonsolScript convention).
Var:Number fh;
File:Open(logFile, "r", fh);
Var:Number totalLines = 0;
Var:Number infoCount = 0;
Var:Number warnCount = 0;
Var:Number errorCount = 0;
Var:String line;
Var:String trimmed;
Var:Boolean eof;
Var:Number gc;
Var:String ts;
Var:String level;
Var:String msg;
Var:String entry;
// Collect error messages so we can show the most recent ones at the end.
List:New recentErrors:String;
// Regex:Groups output list - always clear before calling Regex:Groups
// so results from the previous line do not bleed into the current one.
List:New groups:String;
File:EOF(fh, eof);
while (eof == false) {
File:ReadLine(fh, line);
String:Trim(line, trimmed);
if (trimmed != "") {
totalLines = totalLines + 1;
// Match the log line and extract its three fields with a single regex.
//
// Pattern:
// ^ - start of line
// (\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) - timestamp (group 1)
// \s\[(INFO|WARN|ERROR)\]\s* - level in brackets (group 2)
// (.+)$ - message (group 3)
//
// Regex:Groups returns: index 0 = full match, 1+ = capture groups.
// We check gc >= 4 (full match + 3 groups) before reading the groups.
List:Clear(groups);
Regex:Groups("^(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}) \\[(INFO|WARN|ERROR)\\]\\s*(.+)$", trimmed, groups);
List:Size(groups, gc);
if (gc >= 4) {
List:Get(1, groups, ts);
List:Get(2, groups, level);
List:Get(3, groups, msg);
if (level == "INFO") {
infoCount = infoCount + 1;
} else {
if (level == "WARN") {
warnCount = warnCount + 1;
} else {
// ERROR - count it and save the message for the summary.
errorCount = errorCount + 1;
List:Push("${ts} ${msg}", recentErrors);
}
}
}
}
File:EOF(fh, eof);
}
File:Close(fh);
// ── 4. Print the summary ──────────────────────────────────────────────────────
Konsol:Print("Log summary: ${logFile}");
Konsol:Print(" Total lines : ${totalLines}");
Konsol:Print(" INFO : ${infoCount}");
Konsol:Print(" WARN : ${warnCount}");
Konsol:Print(" ERROR : ${errorCount}");
// Print the last 5 errors (or fewer if there aren't that many).
Var:Number errTotal;
List:Size(recentErrors, errTotal);
if (errTotal > 0) {
Konsol:Print("");
Konsol:Print("Recent errors:");
// Clamp showFrom so we never read a negative index.
Var:Number showFrom;
showFrom = errTotal - 5;
if (showFrom < 0) { showFrom = 0; }
Var:Number j = showFrom;
while (j < errTotal) {
List:Get(j, recentErrors, entry);
Konsol:Print(" ${entry}");
j = j + 1;
}
}