diff --git a/xtrn/oneliners/commands.js b/xtrn/oneliners/commands.js
new file mode 100644
index 0000000000000000000000000000000000000000..4b769a05c4b6eb65f604cd371166bf53bdefa5ab
--- /dev/null
+++ b/xtrn/oneliners/commands.js
@@ -0,0 +1,22 @@
+var root = argv[0];
+load(root + "/lib.js");
+var settings = initSettings(root);
+
+this.QUERY = function(client, packet) {
+	var openOpers = [
+		"SUBSCRIBE",
+		"UNSUBSCRIBE",
+		"READ",
+		"SLICE",
+		"PUSH"
+	];
+	if(openOpers.indexOf(packet.oper) >= 0 || admin.verify(client, packet, 90))
+		return false;
+	log(LOG_ERR,
+		format(
+			"Invalid command %s from client %s",
+			packet.oper, client.remote_ip_address
+		)
+	);
+	return true;
+}
\ No newline at end of file
diff --git a/xtrn/oneliners/framed.js b/xtrn/oneliners/framed.js
new file mode 100644
index 0000000000000000000000000000000000000000..a85d3550f431db798c3fe2bfc633ee0b87bde0f5
--- /dev/null
+++ b/xtrn/oneliners/framed.js
@@ -0,0 +1,210 @@
+load("frame.js");
+load("inputline.js");
+
+var frame,
+	onelinersFrame,
+	inputFrame,
+	inputLine,
+	oneliners,
+	inInput = false,
+	attr = console.attributes,
+	status = bbs.sys_status;
+
+var initFrames = function() {
+
+	console.clear(BG_BLACK|LIGHTGRAY);
+
+	frame = new Frame(
+		1,
+		1,
+		console.screen_columns,
+		console.screen_rows,
+		LIGHTGRAY
+	);
+
+	onelinersFrame = new Frame(
+		frame.x + 1,
+		frame.y + 2,
+		frame.width - 2,
+		frame.height - 4,
+		LIGHTGRAY,
+		frame
+	);
+
+	inputAliasFrame = new Frame(
+		onelinersFrame.x,
+		frame.height - 1,
+		user.alias.length + system.qwk_id.length + 3,
+		1,
+		LIGHTGRAY,
+		frame
+	);
+
+	inputFrame = new Frame(
+		onelinersFrame.x + user.alias.length + system.qwk_id.length + 3,
+		frame.height - 1,
+		onelinersFrame.width - user.alias.length - system.qwk_id.length - 3,
+		1,
+		BG_BLUE|LIGHTGRAY,
+		frame
+	);
+
+	promptFrame = new Frame(
+		onelinersFrame.x,
+		frame.height - 1,
+		onelinersFrame.width,
+		1,
+		LIGHTCYAN,
+		frame
+	);
+
+	inputAliasFrame.putmsg(user.alias + "\1n\1c@\1b" + system.qwk_id.toLowerCase() + "\1h\1k: ");
+	promptFrame.putmsg("Add a oneliner to the wall? [Y/\1h\1wN\1h\1c]");
+	frame.drawBorder([LIGHTBLUE, CYAN, LIGHTCYAN, WHITE]);
+	var header = format(
+		"\1h\1w%s synchronet oneliners %s",
+		ascii(180), ascii(195)
+	);
+	frame.gotoxy(frame.width - console.strlen(header) - 2, 1);
+	frame.putmsg(header);
+	frame.open();
+
+}
+
+var initInput = function() {
+	inputLine = new InputLine(inputFrame);
+	inputLine.max_buffer = inputFrame.width;
+	inputLine.auto_clear = false;
+	inputLine.attr = BG_BLUE|WHITE;
+	inputLine.cursor_attr = BG_BLUE|WHITE;
+}
+
+var initJSON = function() {
+	try {
+		oneliners = new Oneliners(settings.server, settings.port, putOneliner);
+		var count = oneliners.count;
+		if(count > onelinersFrame.height)
+			var lines = oneliners.read(count - onelinersFrame.height);
+		else
+			var lines = oneliners.read(0);
+		for(var line in lines)
+			putOneliner(lines[line]);
+		oneliners.callback = putOneliner;
+	} catch(err) {
+		log(LOG_ERR, "Oneliners error: " + err);
+		exit();
+	}
+}
+
+var init = function() {
+	bbs.sys_status |= SS_MOFF;
+	initFrames();
+	initInput();
+	initJSON();
+}
+
+var putOneliner = function(oneliner) {
+	onelinersFrame.putmsg(
+		format(
+			"\1n\1w%s\1n\1c@\1h\1b%s\1h\1k: \1n\1w%s\r\n",
+			oneliner.alias,
+			oneliner.qwkid,
+			pipeToCtrlA(
+				(	oneliner.oneliner.length
+					+
+					oneliner.alias.length
+					+
+					oneliner.qwkid.length
+					+
+					3
+					>
+					onelinersFrame.width
+				)
+				?
+				oneliner.oneliner.substr(
+					0,
+					onelinersFrame.width
+					-
+					(	oneliner.alias.length
+						+
+						oneliner.qwkid.length
+						+
+						3
+					)
+				)
+				:
+				oneliner.oneliner
+			)
+		)
+	);
+}
+
+var cycle = function() {
+	try {
+		oneliners.cycle();
+	} catch(err) {
+		log(LOG_ERR, "Oneliners error: " + err);
+	}
+	if(frame.cycle())
+		console.gotoxy(console.screen_columns, console.screen_rows);
+	return (inInput)?inputLine.getkey():console.inkey(K_NONE,5).toUpperCase();
+}
+
+var main = function() {
+	var userInput;
+	while(!js.terminated) {
+		userInput = cycle();
+		if(inInput) {
+			inputLine.attr =
+				(inputLine.buffer.length > (80 - user.alias.length - system.qwk_id.length - 3))
+				?
+				BG_BLUE|RED
+				:
+				BG_BLUE|WHITE;
+			if(typeof userInput != "undefined") {
+				inInput = false;
+				inputLine.clear();
+				promptFrame.open();
+				promptFrame.top();
+				userInput = userInput.replace(/\\1/g, ascii(1));
+				if(console.strlen(userInput) < 1)
+					continue;
+				postOneliner(user.alias, userInput);
+			}
+			continue;
+		}
+		if(!inInput) {
+			userInput = userInput.toUpperCase();
+			if(userInput == "Y") {
+				promptFrame.close();
+				inputAliasFrame.top();
+				inputFrame.top();
+				inInput = true;
+				// A hack to get the cursor (near) to the correct spot
+				console.ungetstr("*");
+				inputLine.getkey();
+				console.ungetstr("\x08");
+				// So ends the hack.
+				continue;
+			} else if(userInput == "N" || ascii(userInput) == 13) {
+				break;
+			}
+			continue;
+		}
+	}
+}
+
+var cleanUp = function() {
+	try {
+		oneliners.close();
+	} catch(err) {
+		log(LOG_ERR, "Oneliners error: " + err);
+	}
+	frame.close();
+	console.clear(attr);
+	bbs.sys_status = status;
+}
+
+init();
+main();
+cleanUp();
\ No newline at end of file
diff --git a/xtrn/oneliners/lib.js b/xtrn/oneliners/lib.js
new file mode 100644
index 0000000000000000000000000000000000000000..b850ddf883a77271e82fc5daa40ae69e8661b1b4
--- /dev/null
+++ b/xtrn/oneliners/lib.js
@@ -0,0 +1,78 @@
+load("json-client.js");
+
+var initSettings = function(path) {
+	var f = new File(path + "settings.ini");
+	f.open("r");
+	var o = f.iniGetObject();
+	f.close();
+	return o;
+}
+
+var postOneliner = function(alias, userInput) {
+	try {
+		oneliners.post(alias, userInput);
+	} catch(err) {
+		log(LOG_ERR, "JSON client error: " + err);
+		exit();
+	}
+}
+
+var Oneliners = function(server, port, callback) {
+
+	var jsonClient = new JSONClient(server, port);
+
+	this.__defineGetter__(
+		'count',
+		function() {
+			return jsonClient.read("ONELINERS", "ONELINERS.length", 1);
+		}
+	);
+	this.__defineSetter__('count', function() {});
+
+	this.read = function(start, end) {
+		return jsonClient.slice(
+			"ONELINERS",
+			"ONELINERS",
+			start,
+			(typeof end == "undefined") ? undefined : end,
+			1
+		);
+	}
+
+	this.post = function(alias, oneliner) {
+		var obj = {
+			'time' : time(),
+			'client' : (typeof client != "undefined") ? client.ip_address : system.inet_addr,
+			'alias' : alias,
+			'systemName' : system.name,
+			'systemHost' : system.inet_addr,
+			'qwkid' : system.qwk_id.toLowerCase(),
+			'oneliner' : oneliner
+		};
+		jsonClient.push(
+			"ONELINERS",
+			"ONELINERS",
+			obj,
+			2
+		);
+	}
+
+	this.cycle = function() {
+		jsonClient.cycle();
+	}
+
+	this.close = function() {
+		jsonClient.disconnect();
+	}
+
+	if(typeof callback == "function") {
+		var handler = function(update) {
+			if(update.location != "LATEST" || update.oper != "WRITE")
+				return;
+			callback(update.data);
+		}
+		jsonClient.callback = handler;
+		jsonClient.subscribe("ONELINERS", "LATEST");
+	}
+
+}
diff --git a/xtrn/oneliners/oneliners.js b/xtrn/oneliners/oneliners.js
new file mode 100644
index 0000000000000000000000000000000000000000..e1c7e33a902cf53a5157b7d4412ee62c28b646c7
--- /dev/null
+++ b/xtrn/oneliners/oneliners.js
@@ -0,0 +1,9 @@
+load("sbbsdefs.js");
+load(js.exec_dir + "lib.js");
+
+var settings = initSettings(js.exec_dir);
+
+if(console.autoterm&USER_ANSI)
+	load(js.exec_dir + "framed.js");
+else
+	load(js.exec_dir + "plain.js");
\ No newline at end of file
diff --git a/xtrn/oneliners/plain.js b/xtrn/oneliners/plain.js
new file mode 100644
index 0000000000000000000000000000000000000000..0cfb3715ac67aad22f5dea850fb9522ba0399fd5
--- /dev/null
+++ b/xtrn/oneliners/plain.js
@@ -0,0 +1,86 @@
+var settings, oneliners, attr;
+
+var init = function() {
+	oneliners = new Oneliners(settings.server, settings.port);
+	attr = console.attributes;
+	console.clear(BG_BLACK|LIGHTGRAY);
+	console.putmsg("\1h\1wsynchronet oneliners");
+	console.crlf(); console.crlf();
+}
+
+var getOneliners = function() {
+	var count = oneliners.count;
+	if(count > console.screen_rows - 4)
+		var lines = oneliners.read(count - console.screen_rows - 4);
+	else
+		var lines = oneliners.read(0);
+	for(var line in lines) {
+		console.putmsg(
+			format(
+				"\1n\1w%s\1n\1c@\1h\1b%s\1h\1k: \1n\1w%s\r\n",
+				lines[line].alias,
+				lines[line].qwkid,
+				pipeToCtrlA(
+					(	lines[line].oneliner.length
+						+
+						lines[line].alias.length
+						+
+						lines[line].qwkid.length
+						+
+						3
+						>
+						console.screen_columns
+					)
+					?
+					lines[line].oneliner.substr(
+						0,
+						console.screen_columns
+						-
+						(	lines[line].alias.length
+							+
+							lines[line].qwkid.length
+							+
+							3
+						)
+					)
+					:
+					lines[line].oneliner
+				)
+			)
+		);
+	}
+}
+
+var prompt = function() {
+	console.crlf();
+	return console.noyes("Post a oneliner to the wall? ");
+}
+
+var postOneliner = function() {
+	console.clearline();
+	console.putmsg(user.alias + "@" + system.qwk_id + ": ");
+	var userInput = console.getstr(
+		"",
+		console.screen_columns - user.alias.length - system.qwk_id.length - 3,
+		K_LINE|K_EDIT
+	);
+	if(console.strlen(userInput) < 1)
+		return;
+	oneliners.post(user.alias, userInput);
+}
+
+var cleanUp = function() {
+	oneliners.close();
+	console.attributes = attr;
+	console.clear();
+}
+
+try {
+	init();
+	getOneliners();
+	if(!prompt())
+		postOneliner();
+	cleanUp();
+} catch(err) {
+	log(LOG_ERR, "Oneliners error: " + err);
+}
\ No newline at end of file
diff --git a/xtrn/oneliners/readme.txt b/xtrn/oneliners/readme.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a3cc69ce0f003a6a2ebb75c7e8fde07d2510d190
--- /dev/null
+++ b/xtrn/oneliners/readme.txt
@@ -0,0 +1,100 @@
+Synchronet Oneliners
+by echicken
+
+Contents
+
+1) Installation
+1.1) Prerequisites
+1.2) Connecting to a remote wall
+1.3) Hosting your own wall
+1.4) External program configuration
+2) Rules (there are none)
+3) Support
+4) To-do list
+
+1) Installation
+
+1.1) Prerequisites
+
+I've only tested this with Synchronet installations up-to-date with the
+Synchronet CVS (http://wiki.synchro.net/dev:cvs) as of August 24th, 2014.
+You'll want to grab the latest copy of 'exec/load/frame.js', as this module
+relies on a recent update to that script.  Other updates may be required.
+
+1.2) Connecting to a remote service
+
+Connecting to a remote service means accessing a shared, inter-BBS oneliners
+wall.  Most sysops will likely want to go this route, as it will mean more
+activity and "fun" for everybody.
+
+Edit the 'settings.ini' file in your 'xtrn/oneliners/' directory to read as
+follows:
+
+server=bbs.electronicchicken.com
+port=10088
+
+Your oneliners scripts will now access the shared wall that I host.
+
+1.3) Hosting your own wall
+
+If you'd prefer to host your own wall, just leave the 'settings.ini' file in
+your 'xtrn/oneliners/' directory as-is.
+
+If you want other people to be able to connect to your wall, you'll need to
+ensure that the JSON-DB service is enabled on your BBS, and that the port that
+you choose for it is open and forwarded to your BBS.  To enable the JSON-DB
+service (if you haven't already,) add the following section to your
+'ctrl/services.ini' file:
+
+[JSON-Service]
+Port=10088
+Options=STATIC|LOOP
+Command=json-service.js
+
+You'll also need to add the following to your 'ctrl/json-service.ini' file:
+
+[oneliners]
+dir=../xtrn/oneliners/
+
+1.4) External Program Configuration
+
+In 'scfg' (that's 'BBS->Configure' from the Synchronet Control Panel in
+Windows,) go to 'External Programs->Online Programs (Doors)', select or create
+the section you want to add the oneliners program to, select 'Available Online
+Programs', hit enter on a blank line, and configure the external program as
+follows:
+
+Online Program Name: Synchronet Oneliners
+Internal Code: ONELINER
+Start-up Directory: ../xtrn/oneliners
+Command Line: ?oneliners.js
+Multiple Concurrent Users: Yes
+
+If you want this program run automatically when a user logs in, set 'Execute
+on Event' to 'Logon', and answer 'No' to 'Execute as Event Only.'
+
+There are a bunch of other options that you can leave in their default states.
+
+2) Rules (there are none)
+
+I don't care if people want to spam my shared oneliners wall with profanity
+or BBS advertisements.  I'm not interested in policing or censoring the
+content.  Everyone's free to set up their own wall and manage it as they like.
+
+3) Support
+
+If you need help, there are a few options:
+
+- Post a message to 'echicken' in the 'Synchronet Sysops' sub on DOVE-Net
+- Send an email to echicken -at- bbs.electronicchicken.com
+- Find me in #synchronet on irc.synchro.net
+
+A oneliners wall is not a great place for support discussions.  You can post
+your complaints and problems there, but if I see them I'll either ignore them
+or ask you to contact me via one of the above methods.
+
+4) To-do list
+
+- Make the colours of various elements configurable
+- Create an SSJS script to allow listing/posting oneliners via the web
+- Create a web API to enable access for non-Synchronet systems
\ No newline at end of file
diff --git a/xtrn/oneliners/service.js b/xtrn/oneliners/service.js
new file mode 100644
index 0000000000000000000000000000000000000000..65fd3590a25cfa2f6a2c75f51501971bc14cb793
--- /dev/null
+++ b/xtrn/oneliners/service.js
@@ -0,0 +1,66 @@
+var root = argv[0];
+load(root + "lib.js");
+load("json-client.js");
+
+var settings, jsonClient, usr;
+
+var processUpdate = function(update) {
+	for(var count = jsonClient.read("ONELINERS", "ONELINERS.length", 1); count >= 50; count--) {
+		jsonClient.push(
+			"ONELINERS",
+			"HISTORY",
+			jsonClient.shift("ONELINERS", "ONELINERS", 2),
+			2
+		);
+	}
+	if(typeof update != "undefined" && update.oper == "WRITE") {
+		jsonClient.write("ONELINERS", "LATEST", update.data[update.data.length - 1], 2);
+	}
+}
+
+var init = function() {
+	usr = new User(1);
+	settings = initSettings(root);
+	var dummy = [
+		{	'time' : time(),
+			'client' : system.inet_addr,
+			'alias' : "oneliners",
+			'systemName' : system.name,
+			'systemHost' : system.inet_addr,
+			'qwkid' : system.qwk_id.toLowerCase(),
+			'oneliner' : "Oneliners database initialized."
+		}
+	];
+	jsonClient = new JSONClient(settings.server, settings.port);
+	jsonClient.ident("ADMIN", usr.alias, usr.security.password);
+	if(!jsonClient.read("ONELINERS", "ONELINERS", 1)) {
+		if(file_exists(root + "oneliners.json"))
+			file_copy(root + "oneliners.json", root + "oneliners.bak");
+		jsonClient.write("ONELINERS", "ONELINERS", dummy, 2);
+		jsonClient.write("ONELINERS", "HISTORY", dummy, 2);
+		jsonClient.write("ONELINERS", "LATEST", dummy[0], 2);
+	}
+	jsonClient.subscribe("ONELINERS", "ONELINERS");
+	jsonClient.callback = processUpdate;
+	processUpdate();
+}
+
+var main = function() {
+	while(!js.terminated) {
+		mswait(5);
+		jsonClient.cycle();
+	}
+}
+
+var cleanUp = function() {
+	jsonClient.disconnect();
+}
+
+try {
+	init();
+	main();
+	cleanUp();
+	exit();
+} catch(err) {
+	log(LOG_ERR, "Oneliners service error: " + err);
+}
\ No newline at end of file
diff --git a/xtrn/oneliners/settings.ini b/xtrn/oneliners/settings.ini
new file mode 100644
index 0000000000000000000000000000000000000000..bb8b85d75ec7b822529c8e435b05d8dcd4eb4eab
--- /dev/null
+++ b/xtrn/oneliners/settings.ini
@@ -0,0 +1,2 @@
+server = bbs.electronicchicken.com
+port = 10088