diff --git a/tools/main.ts b/tools/main.ts
new file mode 100644
index 0000000..4fb83c7
--- /dev/null
+++ b/tools/main.ts
@@ -0,0 +1,192 @@
+import { parse, type RootNode, type ElementNode } from "svg-parser";
+import fs from "fs";
+import colors from "colors";
+import * as path from "path";
+import { Command } from "commander";
+const program = new Command();
+import { formatISO } from "date-fns";
+import { fileTypeFromFile } from "file-type";
+import klaw from "klaw";
+import sharp from "sharp";
+import cliProgress from "cli-progress";
+import os, { loadavg } from "os";
+import async from "async";
+
+const packageJSON = JSON.parse(
+ fs.readFileSync("package.json", { encoding: "utf-8" })
+);
+
+program
+ .name(packageJSON.name)
+ .description(packageJSON.description)
+ .version(packageJSON.version);
+
+function log(msg: string) {
+ console.log(`[${formatISO(new Date())}] ${msg}`);
+}
+
+function warn(msg: string) {
+ console.error(`[${formatISO(new Date())}] ${colors.yellow(msg)}`);
+}
+
+function err(msg: string) {
+ console.error(`[${formatISO(new Date())}] ${colors.red(msg)}`);
+}
+
+program
+ .command("check")
+ .description("Check a SVG file for common errors.")
+ .argument(
+ "[path to SVG file]",
+ "Path to the LaS SVG file.",
+ "../public/content/intro.svg"
+ )
+ .action((fileName: string) => {
+ log(`Loading "${fileName}"`);
+ const fileContents = fs.readFileSync(fileName, { encoding: "utf8" });
+ const root = parse(fileContents);
+
+ const TAGS = ["image", "rect", "circle", "ellipse", "a"];
+
+ const elements: { [key: string]: ElementNode[] } = {};
+ TAGS.forEach((tag) => (elements[tag] = []));
+ function walkAndSort(element: RootNode | ElementNode) {
+ if ("children" in element) {
+ element.children.forEach((child) => {
+ if (typeof child !== "string" && "tagName" in child) {
+ const tagName = child.tagName || "";
+ if (TAGS.includes(tagName)) {
+ elements[tagName].push(child);
+ }
+ walkAndSort(child);
+ }
+ });
+ }
+ }
+ walkAndSort(root);
+
+ const anchorLinks: ElementNode[] = elements["a"].filter((el) => {
+ const href = String((el.properties || {})["xlink:href"] || "");
+ return !href.startsWith("http") && href.length > 0;
+ });
+
+ log(`Found ${anchorLinks.length} links to anchors.`);
+
+ const validTargets = elements["rect"].map(
+ (el) => el.properties!.id as string
+ );
+ anchorLinks.forEach((el) => {
+ const href = String((el.properties || {})["xlink:href"] || "");
+ if (!validTargets.includes(href)) {
+ const child = el.children.find((el) => typeof el !== "string") as
+ | ElementNode
+ | undefined;
+ const childProps = child?.properties || {};
+ const cx = childProps["x"] || childProps["cx"] || "???";
+ const cy = childProps["y"] || childProps["cy"] || "???";
+ warn(
+ ` - Link "${href}" (cx: ${cx}, cy: ${cy}) has no corresponding target object!`
+ );
+ }
+ });
+
+ // const badAudios = elements["ellipse"].filter((el) => {
+ // if
+ // })
+ // TODO
+ });
+
+program
+ .command("process")
+ .description("Process a directory of media content.")
+ .argument("", "Path to the input directory.")
+ .argument("