diff --git a/devTools/scripts/advancedCompiler.js b/devTools/scripts/advancedCompiler.js
index 3bfdbe3cd644d639303c97df2c0069d2543c0639..19b0c0ad59bc4bdcbc59c74012e729d468d6bc9b 100644
--- a/devTools/scripts/advancedCompiler.js
+++ b/devTools/scripts/advancedCompiler.js
@@ -7,10 +7,28 @@
 import {execSync} from "child_process";
 // @ts-ignore
 import jetpack from "fs-jetpack";
+import yargs from "yargs";
+import {hideBin} from "yargs/helpers";
+
+const args = yargs(hideBin(process.argv))
+	.showHelpOnFail(true)
+	.option('interaction', {
+		type: 'boolean',
+		description: 'Used by scripts to let the compiler know that user interaction is not possible.',
+		default: true,
+	})
+	.option('filename', {
+		type: 'string',
+		description: 'The filename to save the compiled HTML file as',
+		default: "FC_pregmod.html"
+	})
+	.parse();
 
 const batSh = (process.platform === "win32") ? "bat" : "sh";
 
-console.log(`Using the advanced compiler, run 'simple-compiler.${batSh}' instead to use the simple compiler.`);
+if (args.interaction === true) {
+	console.log(`Using the advanced compiler, run 'simple-compiler.${batSh}' instead to use the simple compiler.`);
+}
 
 // make sure settings.json exists and has all the required properties
 execSync("node devTools/scripts/setup.js --settings");
@@ -33,7 +51,10 @@ if (settings.compilerMode === "simple") {
 } else {
 	if (settings.compilerRunSanityChecks === 1) {
 		try {
-			execSync("node devTools/scripts/sanityCheck.js", {stdio: "inherit"});
+			execSync(
+				`node devTools/scripts/sanityCheck.js${args.interaction ? "" : " --no-interaction"}`,
+				{stdio: "inherit"}
+			);
 		} catch {
 			console.log("Sanity checks failed! See above for details");
 		}
@@ -62,6 +83,7 @@ if (settings.compilerMode === "simple") {
 	if (settings.compilerFilenamePmodVersion === true) {
 		command += ` --pmodversion`;
 	}
+	command += ` --filename=${args.filename}`
 }
 
 console.log(`Executing "${command}"`);
@@ -70,14 +92,17 @@ execSync(command, {stdio: "inherit"});
 
 if (settings.compilerMode === "advanced" && settings.compilerRunSanityChecks === 2) {
 	try {
-		execSync("node devTools/scripts/sanityCheck.js", {stdio: "inherit"});
+		execSync(
+			`node devTools/scripts/sanityCheck.js${args.interaction ? "" : " --no-interaction"}`,
+			{stdio: "inherit"}
+		);
 	} catch {
 		console.log("Sanity checks failed! See above for details");
 	}
 }
 
 console.log(`Run 'setup.${batSh}' to change compiler settings`);
-if (settings.compilerWaitOnWindows && process.platform === "win32") {
+if (settings.compilerWaitOnWindows && process.platform === "win32" && args.interaction === true) {
 	console.log('Press enter to exit.');
 	process.stdin.once('data', function() {
 		process.exit();
diff --git a/devTools/scripts/detectChanges.js b/devTools/scripts/detectChanges.js
index ea900f691f0e4c952fa8755eeb86371cd97b7f23..600bd1098fd9a341e023688d32fbab0b34f91400 100644
--- a/devTools/scripts/detectChanges.js
+++ b/devTools/scripts/detectChanges.js
@@ -51,7 +51,13 @@ const settings = jetpack.read("settings.json", "json");
  * @class
  */
 class ChangeParser {
-	async init() {
+	async init(interactive = true) {
+		if (interactive === false && settings.fetchUpstreamBranch !== -1 && settings.fetchUpstreamBranch !== 1) {
+			const batSh = (process.platform === "win32") ? "bat" : "sh";
+			console.log("This feature requires fetchUpstreamBranch to be specified.");
+			console.log(`Please run "setup.${batSh}" and change 'Edit Miscellaneous Settings' -> 'Asking before fetching upstream pregmod-master branch'`);
+			process.exit(1);
+		}
 		await this.requestPermission();
 		if (this.hasPermission === true) {
 			this.fetchOrigin();
diff --git a/devTools/scripts/sanityCheck.js b/devTools/scripts/sanityCheck.js
index 222e4cfa06d76122861bad81104bfa503403e0f6..314fa0c1a9932cfce27e355ace81645cdc775c81 100644
--- a/devTools/scripts/sanityCheck.js
+++ b/devTools/scripts/sanityCheck.js
@@ -23,6 +23,11 @@ const args = yargs(hideBin(process.argv))
 		description: 'Only check staged files',
 		default: false,
 	})
+	.option('interaction', {
+		type: 'boolean',
+		description: 'Used by scripts to let the sanity checker know that user interaction is not possible.',
+		default: true,
+	})
 	.parse();
 
 // make sure settings.json exists and has all the required properties
@@ -84,7 +89,7 @@ if (args.staged === true) {
 	}
 } else {
 	// @ts-ignore
-	await parser.init();
+	await parser.init(args.interaction);
 }
 
 if (stagedFiles !== undefined) {
diff --git a/devTools/scripts/setup.js b/devTools/scripts/setup.js
index 3ed43efa7b21ba9948ea7f1c80afc69bb803c903..8d4efa59adf2a15b0ec9f8e2d1228ee4dbaf2407 100644
--- a/devTools/scripts/setup.js
+++ b/devTools/scripts/setup.js
@@ -267,6 +267,7 @@ async function compilerSettings() {
 	) {
 		settings.compilerWaitOnWindows = !settings.compilerWaitOnWindows;
 	} else if (compilerMenuChoice === "Back") {
+		compilerMenuChoice = 0;
 		return;
 	}
 
@@ -414,6 +415,7 @@ async function sanityCheckSettings() {
 	} else if (
 		sanityCheckMenuChoice === "Back"
 	) {
+		sanityCheckMenuChoice = 0;
 		return;
 	}
 
diff --git a/devTools/scripts/watcher.js b/devTools/scripts/watcher.js
new file mode 100644
index 0000000000000000000000000000000000000000..3395f3429702edd74882dff2ddb47fecddeb09ae
--- /dev/null
+++ b/devTools/scripts/watcher.js
@@ -0,0 +1,90 @@
+/**
+ * @file Watches for changes in the project, when changes occur we run the advanced compiler
+ */
+
+// @ts-ignore
+import jetpack from "fs-jetpack";
+import watch from "node-watch";
+import {execSync, spawn} from "child_process";
+import * as path from "path";
+
+const batSh = (process.platform === "win32") ? "bat" : "sh";
+
+/** @type {import("child_process").ChildProcessWithoutNullStreams} */
+let buildProcess;
+
+// make sure settings.json exists and has all the required properties
+execSync("node devTools/scripts/setup.js --settings");
+
+// load settings.json
+/** @type {import("./setup.js").Settings} */
+const settings = jetpack.read("settings.json", "json");
+
+/**
+ * Builds FC using the advanced compiler
+ */
+function build() {
+	console.log("");
+
+	if (buildProcess !== undefined) {
+		buildProcess.kill();
+	}
+	buildProcess = spawn("node", ["devTools/scripts/advancedCompiler.js", "--filename=FC_pregmod.watcher.html", "--no-interaction"], {
+		stdio: ['inherit', 'inherit', 'inherit'],
+	  });
+
+	buildProcess.on('exit', function (code) {
+		if (code === null) {
+			return;
+		} else if (code === 0) {
+			console.log(`Saving changes in "setup.${batSh}" will toggle a rebuild`);
+		} else {
+			console.log('Compiler exited with code:', code);
+		}
+	});
+}
+
+const watcher = watch(".", {
+	recursive: true,
+	filter(f, skip) {
+		if (
+			f.startsWith("src") ||
+			f.startsWith("js") ||
+			f.startsWith("css") ||
+			f.startsWith("mods") ||
+			f.startsWith("themes") ||
+			f.startsWith("tests") ||
+			f.startsWith("resources") ||
+			f.startsWith("settings.json") ||
+			f.startsWith(`devTools${path.sep}scripts`) ||
+			f.startsWith("gulpfile.js")
+		) {
+			if (f.endsWith("fc-version.js.commitHash.js")) {
+				return false;
+			}
+			return true;
+		} else if (jetpack.exists(f) === "dir") {
+			return skip;
+		} else {
+			return false;
+		}
+	}
+});
+
+// TODO:@franklygeorge optional launching of a live reloading web server
+
+watcher.on("change", function(event, filename) {
+	filename = filename.toString();
+
+	if (event === "update" && filename && jetpack.exists(filename) === "file") {
+		console.log("");
+		console.log(filename + " changed");
+		build();
+	}
+});
+
+console.log("Watching for changes");
+console.log("");
+
+// run build when we first startup
+build()
diff --git a/gulpfile.js b/gulpfile.js
index 81811a0e168e6764ff526eea3c2c681a3094ca6c..d63dee4f59cae3a39c5f6e4fae46b33652186531 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -27,6 +27,10 @@ import path from "path";
 import os from "os";
 import {fileURLToPath} from "url";
 
+// load json without using "...assert {type: "json"}" which is still in to proposal stage
+// https://github.com/tc39/proposal-json-modules
+const cfg = jetpack.read(fileURLToPath(new URL('./build.config.json', import.meta.url)), "json");
+
 // run `npx gulp` to display help
 const args = yargs(hideBin(process.argv))
 	.showHelpOnFail(true)
@@ -83,6 +87,11 @@ const args = yargs(hideBin(process.argv))
 		description: 'Adds the current pregmod version number to the output filename',
 		default: false,
 	})
+	.option('filename', {
+		type: 'string',
+		description: 'The filename to save the compiled HTML file as',
+		default: cfg.output,
+	})
 	// commands should exist as exported gulp tasks
 	.command('html', "Build FC")
 	.command('themes', "Build themes")
@@ -91,11 +100,6 @@ const args = yargs(hideBin(process.argv))
 	.demandCommand()
 	.parse();
 
-// load json without using "...assert {type: "json"}" which is still in to proposal stage
-// https://github.com/tc39/proposal-json-modules
-
-const cfg = jetpack.read(fileURLToPath(new URL('./build.config.json', import.meta.url)), "json");
-
 /**
  * Options used to minify js code using terser
  * @type {import("terser").MinifyOptions}
@@ -497,7 +501,7 @@ function makeModCompilationTask(modName) {
  */
 function moveHTML(cb) {
 	if (jetpack.exists(path.join(cfg.dirs.intermediate, htmlOut)) === "file") {
-		let finalPath = path.join(cfg.dirs.output, cfg.output);
+		let finalPath = path.join(cfg.dirs.output, args.filename);
 		let extraString = "";
 
 		if (args.epoch) {
diff --git a/package.json b/package.json
index f9f288f5d4def6e32cf4ead7205a2f278edb1dd4..2f38d3d8c84b3d186a282014c581ad30a3dcf1a9 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,8 @@
 		"spell:all": "npx cspell --show-context --show-suggestions --gitignore --color \"**/*.{js,md,d.ts}\"",
 		"lint": "node devTools/scripts/eslintChecks.js --changed && node devTools/scripts/typescriptChecks.js --changed",
 		"lint:all": "node devTools/scripts/eslintChecks.js && node devTools/scripts/typescriptChecks.js",
-		"sanity": "node devTools/scripts/sanityCheck.js"
+		"sanity": "node devTools/scripts/sanityCheck.js",
+		"watch": "node devTools/scripts/watcher.js"
 	},
 	"license": "GPL-3.0-only",
 	"devDependencies": {
@@ -43,6 +44,7 @@
 		"http-server": "^14.1.1",
 		"indefinite": "^2.4.3",
 		"inquirer": "^9.2.15",
+		"node-watch": "^0.7.4",
 		"strip-ansi": "^7.1.0",
 		"ts-essentials": "^9.1.1",
 		"typescript": "^4.4.0"
diff --git a/watcher.bat b/watcher.bat
new file mode 100644
index 0000000000000000000000000000000000000000..cad03633d5f8687fcedb68ca34ee5e304890817e
--- /dev/null
+++ b/watcher.bat
@@ -0,0 +1,25 @@
+@ECHO off
+:: Free Cities File system watcher - Windows
+
+:: run dependencyCheck.bat
+CALL .\devTools\scripts\dependencyCheck.bat
+SET CODE=%ERRORLEVEL%
+
+IF %CODE% EQU 69 (
+	:: if exit code is 69, then we don't have all the dependencies we need
+	ECHO.
+	ECHO Dependencies not met.
+    ECHO.
+	EXIT /b 0
+) ELSE IF %CODE% EQU 0 (
+	:: if exit code is 0, run watcher.js
+	CALL node devTools\scripts\watcher.js
+	EXIT /b 0
+) ELSE (
+	:: if exit code is not 0, print error message
+	ECHO.
+	ECHO dependencyCheck.bat exited with code: %CODE%
+	ECHO Dependency check failed unexpectedly.
+    ECHO.
+    EXIT /b 0
+)
diff --git a/watcher.sh b/watcher.sh
new file mode 100755
index 0000000000000000000000000000000000000000..97d5f1f5b8f91b84c8bbaf8ea2c9406d86ae5b36
--- /dev/null
+++ b/watcher.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+# Free Cities File system watcher - Unix
+
+# run dependencyCheck.sh
+./devTools/scripts/dependencyCheck.sh
+exitCode=$?
+# exit code is now stored in $exitCode
+
+# if exit code is 69, then we don't have all the dependencies we need
+if [[ $exitCode -eq 69 ]]; then
+    echo "Dependencies not met."
+    echo ""
+   exit 0
+# if exit code is not 0, print error message
+elif [[ $exitCode -ne 0 ]]; then
+	echo "dependencyCheck.sh exited with code: $exitCode"
+    echo ""
+    exit 0
+# if exit code is 0, run watcher.js
+else
+    node devTools/scripts/watcher.js
+fi