diff --git a/exec/fileman.js b/exec/fileman.js
index dc5bd6765aef578499c5f973a6908fbf3d0873bc..325419c4053e4e858d26738534e30a37b92546c8 100755
--- a/exec/fileman.js
+++ b/exec/fileman.js
@@ -14,6 +14,10 @@ if(!uifc.init("Synchronet File Manager")) {
 }
 js.on_exit("uifc.bail()");
 
+// TODO: make these configurable
+var uploader = system.operator;
+var use_diz = false;
+
 const main_ctx = new uifc.list.CTX;
 while(!js.terminated) {
 	const items = [
@@ -112,6 +116,8 @@ function search(prompt, func)
 	uifc.pop();
 	if(found.length)
 		list_files(found.length + " " + (pattern || "offline") + " files found", found);
+	else
+		uifc.msg("No files found");
 }
 
 function find_filename(dircode, pattern)
@@ -226,12 +232,16 @@ function browse_dir(dircode)
 	list_files(file_area.dir[dircode].name + format(" Files (%u)", list.length), list, dircode);
 }
 
+function find_file(fname, list)
+{
+	for(var i = 0; i < list.length; ++i)
+		if(list[i].name == fname)
+			return i;
+	return -1;
+}
+
 function list_files(title, list, dircode)
 {
-	if(!list || !list.length) {
-		uifc.msg("No files in " + title);
-		return;
-	}
 	const wide_screen = uifc.screen_width >= 100;
 	const ctx = new uifc.list.CTX;
 	while(!js.terminated) {
@@ -249,9 +259,13 @@ function list_files(title, list, dircode)
 				,tagged ? 2:0
 				,list[i].tagged ? ascii(251):""
 				,namelen, wide_screen ? list[i].name : FileBase().format_name(list[i].name)
-				,list[i].desc || list[i].extdesc || ""));
-		var result = uifc.list(WIN_SAV | WIN_RHT | WIN_ACT | WIN_DEL | WIN_DELACT | WIN_TAG
-			,title, items, ctx);
+				,list[i].desc || ""));
+		var win_mode = WIN_SAV | WIN_RHT | WIN_ACT | WIN_DEL | WIN_DELACT | WIN_TAG;
+		if(dircode)
+			win_mode |= WIN_INS | WIN_INSACT;
+		if(!tagged)
+			win_mode |= WIN_EDIT;
+		var result = uifc.list(win_mode, title, items, ctx);
 		if(result == -1)
 			break;
 		if(result == MSK_TAGALL) {
@@ -270,6 +284,9 @@ function list_files(title, list, dircode)
 					++ctx.cur;
 					++ctx.bar;
 					break;
+				case MSK_INS:
+					add_files(list, dircode);
+					break;
 				case MSK_DEL:
 					var opts = [ "No", "Yes" ];
 					if(tagged > 0) {
@@ -296,19 +313,80 @@ function list_files(title, list, dircode)
 							list.splice(result, /* deleteCount: */1);
 					}
 					break;
+				case MSK_EDIT:
+					var orig_name = file.name;
+					if(edit_filename(file))
+						save(file, dircode, orig_name);
+					break;
 			}
 			continue;
 		}
 		if(tagged > 0) {
 			multi(list, dircode, tagged);
 		} else { // Single-file selected
-			var file = list[result];
-			if(edit(file, dircode)) // file (re)moved?
+			if(edit(list[result], dircode)) // file (re)moved?
 				list.splice(result, /* deleteCount: */1);
 		}
 	}
 }
 
+function add_files(list, dircode)
+{
+	var all_files = directory(file_area.dir[dircode].path + "*");
+	var new_files = [];
+	for(var i in all_files) {
+		if(!file_isdir(all_files[i])) {
+			var fname = file_getname(all_files[i]);
+			if(find_file(fname, list) < 0)
+				new_files.push(fname);
+		}
+	}
+	if(new_files.length < 1) {
+		uifc.msg("No new files to add in " + file_area.dir[dircode].path);
+		return;
+	}
+	const ctx = new uifc.list.CTX;
+	var tagged = [];
+	while(!js.terminated && new_files.length > 0) {
+		const new_title = new_files.length + " New Files in " + file_area.dir[dircode].path;
+		var opts = [];
+		for(var i in new_files)
+			opts[i] = format("%-*s%s", tagged.length ? 2:0, tagged.indexOf(new_files[i]) >= 0 ? ascii(251):"", new_files[i]);
+		var choice = uifc.list(WIN_SAV|WIN_ACT|WIN_TAG, new_title, opts, ctx);
+		if(choice < 0)
+			break;
+		if(choice == MSK_TAGALL) {
+			if(tagged.length)
+				tagged = [];
+			else
+				for(var i in new_files)
+					tagged[i] = new_files[i];
+			continue;
+		}
+		if((choice & MSK_ON) == MSK_TAG) {
+			choice &= MSK_OFF;
+			var i = tagged.indexOf(new_files[choice]);
+			if(i >= 0)
+				tagged.splice(i, /* deleteCount: */1);
+			else
+				tagged.push(new_files[choice]);
+			++ctx.cur;
+			++ctx.bar;
+			continue;
+		}
+		var files = tagged;
+		if(!files.length)
+			files = [new_files[choice]];
+		for(var i in files) {
+			var new_file = add_file(files[i], dircode);
+			if(!new_file)
+				break;
+			list.push(new_file);
+			new_files.splice(i, /* deleteCount: */1);
+		}
+	}
+}
+
 function fexists(file, dircode)
 {
 	if(!dircode)
@@ -342,7 +420,7 @@ function remove(file, del, dircode)
 	return result;
 }
 
-function dump(file, dircode)
+function view_details(file, dircode)
 {
 	if(!dircode)
 		dircode = file.dircode;
@@ -351,10 +429,10 @@ function dump(file, dircode)
 		uifc.msg("Unable to open base: " + dircode);
 		return;
 	}
-//	file = base.get(file, FileBase.DETAIL.AUXDATA);
+	var dir = file_area.dir[dircode];
 	var buf = [];
-	if(file.extdesc)
-		buf = buf.concat(truncsp(file.extdesc).split('\n'));
+	buf.push(format("Library          " + file_area.lib[dir.lib_name].description));
+	buf.push(format("Directory        " + dir.description));
 	buf.push(format("Size             " + file_size_float(base.get_size(file), 1, 1)));
 	buf.push(format("Time             " + system.timestr(base.get_time(file))));
 	uifc.showbuf(WIN_MID|WIN_HLP, base.get_path(file), buf.concat(base.dump(file.name)).join('\n'));
@@ -396,12 +474,49 @@ function confirm(prompt)
 	return choice;
 }
 
+function edit_filename(file)
+{
+	var name = uifc.input(WIN_MID|WIN_SAV, "Filename", file.name, 100, K_EDIT);
+	if(!name)
+		return false;
+	name = file_getname(name);
+	if(name == file.name)
+		return false;
+	file.name = name;
+	return true;
+}
+
+function edit_desc(file)
+{
+	var desc = uifc.input(WIN_MID|WIN_SAV, "Description", file.desc, LEN_FDESC, K_EDIT);
+	if(desc !== undefined)
+		file.desc = desc;
+}
+
+function edit_uploader(file)
+{
+	var from = uifc.input(WIN_MID|WIN_SAV, "Uploader", file.from, LEN_ALIAS, K_EDIT);
+	if(from !== undefined)
+		file.from = from;
+	return file.from;
+}
+
+function edit_cost(file)
+{
+	var cost = uifc.input(WIN_MID|WIN_SAV, "Download Cost (in credits)", String(file.cost), 15, K_NUMBER|K_EDIT);
+	if(cost !== undefined)
+		file.cost = cost;
+}
+
 function edit(file, dircode)
 {
+	file.extdesc = get_extdesc(file, dircode);
 	const ctx = new uifc.list.CTX;
 	var orig = JSON.parse(JSON.stringify(file));
 	while(!js.terminated) {
 		var opts = [
+			"Move File...",
+			"Remove File...",
 			"View Details...",
 			"View Archive...",
 			"Filename: " + file.name,
@@ -409,90 +524,131 @@ function edit(file, dircode)
 			"Uploader: " + (file.from || ""),
 			"Added: " + system.timestr(file.added).slice(4),
 			"Cost: " + file.cost,
-			"Move File...",
-			"Remove File..."
+			"|---------- Extended Description -----------|"
 			];
-		if(JSON.stringify(file) != JSON.stringify(orig))
-			opts.push("Save changes...");
+		if(file.extdesc) {
+			var extdesc = strip_ctrl_a(file.extdesc).split('\r\n');
+			opts = opts.concat(extdesc);
+		}
 		const title = (dircode || file.dircode) + "/" +  orig.name;
-		var choice = uifc.list(WIN_SAV|WIN_ACT, title, opts, ctx);
-		if(choice < 0)
+		var choice = uifc.list(WIN_SAV|WIN_ACT|WIN_ESC|WIN_XTR|WIN_INS|WIN_DEL, title, opts, ctx);
+		if(choice == -1) {
+			if(JSON.stringify(file) != JSON.stringify(orig)) {
+				var choice = confirm("Save Changes?");
+				if(choice < 0)
+					continue;
+				if(choice && save(file, dircode, orig.name))
+					return false;
+			}
 			break;
+		}
+		var mask = choice & MSK_ON;
+		choice &= MSK_OFF;
+		if(mask && choice < 10)
+			continue;
 		switch(choice) {
 			case 0:
-				dump(file, dircode);
+				var dest = select_dir();
+				if(!dest)
+					break;
+				if(dest == dircode) {
+					uifc.msg("Destination directory must be different");
+					break;
+				}
+				if(move(file, dircode, dest))
+					return true;
 				break;
 			case 1:
-				view_archive(file, dircode);
+				var del = confirm("Delete File Also");
+				if(del < 0)
+					break;
+				if(remove(file, del, dircode))
+					return true;
 				break;
 			case 2:
-				var name = uifc.input(WIN_MID|WIN_SAV, "Filename", file.name, 100, K_EDIT);
-				if(!name)
-					break;
-				file.name = name;
+				view_details(file, dircode);
 				break;
 			case 3:
-				var desc = uifc.input(WIN_MID|WIN_SAV, "Description", file.desc, LEN_FDESC, K_EDIT);
-				if(!desc)
-					break;
-				file.desc = desc;
+				view_archive(file, dircode);
 				break;
 			case 4:
-				var from = uifc.input(WIN_MID|WIN_SAV, "Uploader", file.from, LEN_ALIAS, K_EDIT);
-				if(!from)
-					break;
-				file.from = from;
+				edit_filename(file);
 				break;
 			case 5:
-				var date = uifc.input(WIN_MID|WIN_SAV, "Added", new Date(file.added * 1000).toString().slice(4), 20, K_EDIT);
-				if(date)
-					file.added = Date.parse(date) / 1000;
+				edit_desc(file);
 				break;
 			case 6:
-				var cost = uifc.input(WIN_MID|WIN_SAV, "Download Cost (in credits)", String(file.cost), 15, K_NUMBER|K_EDIT);
-				if(cost !== undefined)
-					file.cost = cost;
+				edit_uploader(file);
 				break;
 			case 7:
-				var dest = select_dir();
-				if(!dest)
-					break;
-				if(dest == dircode) {
-					uifc.msg("Destination directory must be different");
-					break;
-				}
-				if(move(file, dircode, dest))
-					return true;
+				var date = uifc.input(WIN_MID|WIN_SAV, "Added", new Date(file.added * 1000).toString().slice(4), 20, K_EDIT);
+				if(date)
+					file.added = Date.parse(date) / 1000;
 				break;
 			case 8:
-				var del = confirm("Delete File Also");
-				if(del < 0)
-					break;
-				if(remove(file, del, dircode))
-					return true;
+				edit_cost(file);
 				break;
 			case 9:
-				if(save(file, dircode, orig.name))
-					orig = JSON.parse(JSON.stringify(file));
+				break;
+			default: // Extended description
+				var extdesc = file.extdesc ? file.extdesc.split('\r\n') : [];
+				var index = choice - 10;
+				if(mask & MSK_DEL) {
+					extdesc.splice(index, /* deleteCount: */1);
+				}
+				else if(mask & MSK_INS) {
+					var str = uifc.input(WIN_SAV, 1, ctx.bar + 2, "", 79, K_MSG);
+					if(str !== undefined)
+						extdesc.splice(index, /* deleteCount: */0, str);
+				}
+				else if(extdesc.length <= index) {
+					var str = uifc.input(WIN_SAV, 1, ctx.bar + 2, "", 79, K_MSG);
+					if(str !== undefined)
+						extdesc.push(str);
+				}
+				else {
+					var str = uifc.input(WIN_SAV, 1, ctx.bar + 2, "", extdesc[index], 79, K_EDIT|K_MSG);
+					if(str !== undefined)
+						extdesc[index] = str;
+				}
+				file.extdesc = extdesc.join('\r\n');
 				break;
 		}
 	}
-	file.name = orig.name;
+	for(var i in file)
+		file[i] = orig[i];
+
 	return false;
 }
 
-function move(file, dircode, dest)
+function get_extdesc(file, dircode)
 {
-	var result = false;
-	var base = new FileBase(dest);
+	if(file.extdesc)
+		return file.extdesc;
+	if(!dircode)
+		dircode = file.dircode;
+	var base = new FileBase(dircode);
 	if(!base.open()) {
-		uifc.msg("Unable to open base: " + dest);
+		uifc.msg("Unable to open base: " + dircode);
 		return false;
 	}
-	var dest_path = base.get_path(file);
+	var f = base.get(file, FileBase.DETAIL.EXTENDED);
+	if(f) file = f;
+	base.close();
+	return file.extdesc;
+}
+
+function move(file, dircode, dest)
+{
+	var dest_path = file_area.dir[dest].path + file.name;
 	if(file_exists(dest_path)) {
 		uifc.msg("File already exists: " + dest_path);
-		base.close();
+		return false;
+	}
+	var result = false;
+	var base = new FileBase(dest);
+	if(!base.open()) {
+		uifc.msg("Unable to open base: " + dest);
 		return false;
 	}
 	result = base.add(file, /* use_diz: */false);
@@ -510,6 +666,12 @@ function move(file, dircode, dest)
 	}
 	try {
 		var src_path = base.get_path(file);
+		if(!src_path) {
+			uifc.msg(format("Error %d (%s) getting source path for: %s"
+				,base.status, base.error, file.name));
+			base.close();
+			return false;
+		}
 		result = base.remove(file.name);
 		if(!result)
 			uifc.msg("remove failure " + base.status + ": " + file.name);
@@ -586,3 +748,57 @@ function save(file, dircode, filename)
 	base.close();
 	return result;
 }
+
+function add_file(filename, dircode)
+{
+	var base = new FileBase(dircode);
+	if(!base.open()) {
+		uifc.msg("Unable to open base: " + dircode);
+		return false;
+	}
+	var result = false;
+	var file = {
+		name: filename,
+		from: uploader,
+		cost: file_size(file_area.dir[dircode].path + filename)
+		};
+	const ctx = new uifc.list.CTX;
+	const title = "Adding " + filename + " to " + dircode;
+	while(!js.terminated) {
+		var opts = [
+			"Add File...",
+			"Description: " + (file.desc || ""),
+			"Uploader: " + (file.from || ""),
+			"Cost: " + file.cost,
+			"Extract/Import DIZ: " + ((file_area.dir[dircode].settings & DIR_DIZ) ? (use_diz ? "Yes" : "No") : "N/A"),
+			];
+		var choice = uifc.list(WIN_MID|WIN_SAV|WIN_ACT, title, opts, ctx);
+		if(choice < 0)
+			break;
+		switch(choice) {
+			case 1:
+				edit_desc(file);
+				continue;
+			case 2:
+				uploader = edit_uploader(file);
+				continue;
+			case 3:
+				edit_cost(file);
+				continue;
+			case 4:
+				if(file_area.dir[dircode].settings & DIR_DIZ)
+					use_diz = !use_diz;
+				continue;
+		}
+		uifc.pop(title);
+		result = base.add(file, use_diz);
+		uifc.pop();
+		if(result) {
+			file = base.get(filename);
+		} else
+			uifc.msg(format("Error %d (%s) adding file", base.status, base.error));
+		break;
+	}
+	base.close();
+	return result ? file : false;
+}