diff --git a/QCore.js b/QCore.js
new file mode 100644
index 0000000..5864e28
--- /dev/null
+++ b/QCore.js
@@ -0,0 +1,261 @@
+/*********************************
+ * QCore                         *
+ *********************************/
+var http = require('http');
+var fs = require('fs');
+var m_path = require('path');
+var formidable = require('formidable');
+
+var QCore = function() {
+  var acts = {};
+  this.acts = acts;
+  self = this;
+  this.mimetypes = {};
+  this.index = null;
+  this.index_parts = [];
+  this.last_part = { start: 0, end: 0 };
+
+  this.etags = {}; // etags cache, obj: uri { etag: '...', timestamp: }
+
+  this.res_cache = {}; // resource cache ('/view.png' => '03xc345');
+  this.rules = {};
+
+  this.http = http.createServer(function(req, res) {
+    var last = req.url.lastIndexOf("/");
+    var act = req.url.substr(last+1);
+    if (acts[act]) {
+      req.url = req.url.substr(0, last); // rewrite the url without the act
+    } else {
+      act = "";
+    }
+    req.fields = {};
+    req.files = {};
+    if (req.method == 'POST') {
+      var form = new formidable.IncomingForm();
+      form.multiples = true;
+      form.uploadDir = process.cwd()+'/tmp';
+      form.parse(req, function(err, fields, files) {
+        // TODO: handle err
+        req.fields = fields;
+        req.files = files;
+        for (var i = 0, keys = Object.keys(acts[act]); i < keys.length; i++) {
+          acts[act][i](req, res);
+        }
+      });
+      return;
+    } else {
+      for (var i = 0, keys = Object.keys(acts[act]); i < keys.length; i++) {
+        acts[act][i](req, res);
+      }
+    }
+  });
+
+  this.act = function(act, callback) {
+    if (typeof acts[act] === 'undefined') {
+      acts[act] = [];
+    }
+    acts[act].push(callback);
+  };
+  this.rule = function(zone, replace, callback) {
+    if (typeof this.rules[zone] === 'undefined') {
+      this.rules[zone] = {};
+    }
+    this.rules[zone][replace] = callback;
+  };
+  var fs_timeout; // timer to bypass multiple fs.watch triggering due to unstable API
+  this.loadIndex = function(event, filename) {
+    if (fs_timeout) return;
+    fs_timeout = setTimeout(function() { fs_timeout = null; }, 100);
+    var reg = /@@[a-z]+@@/ig;
+    fs.readFile('index.html', function(err, data) {
+      if (err) throw err;
+      self.index = data;
+      var search;
+      self.index_parts = [];
+      while ((search = reg.exec(data)) !== null) {
+        self.index_parts.push({ replace: search[0], start: search.index, end: reg.lastIndex });
+      }
+    });
+  };
+  // TODO: loadActs should allow for dynamic updates by using fs.watch and clearing the require cache
+  this.loadActs = function(cb) {
+    var self = this;
+    fs.readdir('acts', function(err, files) {
+      if (err) throw err;
+      for (var i = 0; i < files.length; i++) {
+        var ext = self.getExt(files[i]);
+        if (ext == 'js') {
+          console.log('Loading act: ' + files[i]);
+          require('./acts/'+files[i])(self);
+        }
+      }
+      (typeof cb !== 'undefined' ? cb() : '');
+    });
+  };
+  fs.watch('index.html', this.loadIndex);
+};
+QCore.prototype.defaults = {};
+QCore.prototype.listen = function(port, cb) {
+  this.loadActs();
+  this.loadIndex();
+  if (typeof cb !== 'undefined') cb();
+  try { 
+    this.http.listen(port);
+  } catch(err) {
+    console.log('uhoh, could not listen on port '+port);
+    console.log(err);
+  }
+};
+QCore.prototype.setDefault = function(key, value) {
+  this.defaults[key] = value;
+};
+QCore.prototype.getDefault = function(key) {
+  if (key in this.defaults) {
+    return this.defaults[key];
+  }
+  return '';
+};
+QCore.prototype.addMIMEtype = function(ext, mimetype) {
+  if (typeof this.mimetypes[ext] === 'undefined') this.mimetypes[ext] = [];
+  this.mimetypes[ext].push(mimetype);
+};
+QCore.prototype.getMIMEtype = function(ext) {
+  if (this.mimetypes[ext]) return this.mimetypes[ext][0];
+  return '';
+};
+QCore.prototype.parsePage = function(zone, fallback_zone, req, res) {
+  var qparse = new QCore.prototype.QParse();
+  qparse.indices = this.index_parts.slice(); // clone it TODO: just make an indices (number-only) array
+
+  var context = this;
+  var cb = function(qparse, callback) {
+    res.write(context.index.slice(qparse.offset, qparse.current.start));
+    if (typeof context.rules[zone] === 'undefined') { zone = fallback_zone; }
+    if (typeof context.rules[zone] === 'undefined') { zone = ''; }
+    if (typeof context.rules[zone] !== 'undefined') {
+      if (typeof context.rules[zone][qparse.current.replace] !== 'undefined') {
+        context.rules[zone][qparse.current.replace](req, res, qparse, callback);
+      } else {
+        // fallback action
+        if (typeof context.rules[fallback_zone][qparse.current.replace] !== 'undefined') {
+          context.rules[fallback_zone][qparse.current.replace](req, res, qparse, callback);
+        // default action
+        } else if (typeof context.rules[''][qparse.current.replace] !== 'undefined') {
+          context.rules[''][qparse.current.replace](req, res, qparse, callback);
+        } else {
+          callback();
+        }
+      }
+    }
+  };
+
+  var series = function(qparse) {
+    if (qparse.current) {
+      cb(qparse, function(next) {
+        if (qparse.next) {
+          res.write(context.index.slice(qparse.current.end, qparse.next.start));
+          qparse.offset = qparse.next.end;
+          qparse.current = qparse.indices.shift();
+          qparse.next = qparse.indices[0];
+          series(qparse);
+        } else {
+          res.write(context.index.slice(qparse.current.end));
+          qparse.current = qparse.indices.shift();
+          qparse.next = qparse.indices[0];
+          series(qparse);
+        }
+      });
+    } else {
+      res.end();
+    }
+  };
+  qparse.current = qparse.indices.shift();
+  qparse.next = qparse.indices[0];
+  series(qparse);
+};
+
+QCore.prototype.readFile = function(stream, path, cb) {
+  fs.exists(path, function(exists) {
+    if (exists) {
+      var rs = fs.createReadStream(path);
+      rs.on('data', function(chunk) {
+        stream.write(chunk);
+      });
+      rs.on('end', function() {
+        cb('EOF');
+      });
+      rs.on('error', function(err) {
+        cb('ERR', err);
+      });
+    } else {
+      cb('FNF'); // file not found
+    }
+  });
+};
+QCore.prototype.readFiles = function(dir, callback) {
+  fs.readdir(dir, function(err, files) {
+    files.forEach(function(file) {
+      var stat_file = dir+'/'+file
+      fs.stat(stat_file, function(err, stats) {
+        if (stats.isDirectory()) {
+          QCore.prototype.readFiles(stat_file, callback);
+          callback(err, stat_file, true);
+        } else if (stats.isFile()) {
+          callback(err, stat_file, false);
+        }
+      });
+    });
+  });
+};
+QCore.prototype.readDirectory = function(dir, callback) {
+  var all_files = [];
+  fs.readdir(dir, function(err, files) {
+    if (err) {
+      callback(err, null);
+      return;
+    }
+    var len = files.length;
+    var total = 0;
+    if (len == 0) callback(err, all_files);
+    files.forEach(function(file, index) {
+      var stat_file = dir + '/' + file;
+      fs.stat(stat_file, function(err, stats) {
+        if (stats.isDirectory()) {
+          all_files.push({'is_dir': true, 'name': file});
+        } else if (stats.isFile()) {
+          all_files.push({'is_dir': false, 'name': file});
+        }
+        if (++total == len) {
+          callback(err, all_files);
+        }
+      });
+    });
+  });
+};
+QCore.prototype.getExt = function(file) {
+  if (typeof file === 'undefined') return '';
+  var last = file.lastIndexOf(".");
+  if (last == -1) return '';
+  var ext = file.substr(last+1);
+  return ext;
+};
+QCore.prototype.r_mkdir = function(dir, mode, cb) {
+  console.log('creating '+dir);
+  fs.mkdir(dir, mode, function(err) {
+    if (err && err.code == 'ENOENT') {
+      QCore.r_mkdir(m_path.dirname(dir), mode, QCore.r_mkdir.bind(this, dir, mode, cb));
+    } else if (typeof cb !== 'undefined') {
+      cb(err);
+    }
+  });
+};
+/*********************************
+ * QParse                         *
+ *********************************/
+QCore.prototype.QParse = function() {};
+QCore.prototype.QParse.prototype.indices = {};
+QCore.prototype.QParse.prototype.offset = 0;
+QCore.prototype.QParse.prototype.current = null;
+QCore.prototype.QParse.prototype.next = null;
+
+module.exports = QCore;
diff --git a/index.html b/index.html
index 4ddabd5..bfcec48 100644
--- a/index.html
+++ b/index.html
@@ -1,7 +1,7 @@
 
 
   
-    
+    
     qwiki @@PAGE@@
     
     
diff --git a/index.js b/index.js
index 1808b72..e277020 100644
--- a/index.js
+++ b/index.js
@@ -1,133 +1,47 @@
 #!/usr/bin/env node
-var http = require('http');
 var fs = require('fs');
 var m_path = require('path');
 var util = require('util');
-var formidable = require('formidable');
+var QCore = require('./QCore.js');
 /* qwiki
 Node.js based Implementation of the proposed qwiki document
 */
-/*********************************
- * QCore                         *
+/********************************* 
+ * QWiki                         *
  *********************************/
-var QCore = function() {
-  var acts = {};
-  this.acts = acts;
-  self = this;
-  this.mimetypes = {};
-  this.index = null;
-  this.index_parts = [];
-  this.last_part = { start: 0, end: 0 };
-
-  this.etags = {}; // etags cache, obj: uri { etag: '...', timestamp: }
-
-  this.res_cache = {}; // resource cache ('/view.png' => '03xc345');
-  this.rules = {};
-
-  this.http = http.createServer(function(req, res) {
-    var last = req.url.lastIndexOf("/");
-    var act = req.url.substr(last+1);
-    if (acts[act]) {
-      req.url = req.url.substr(0, last); // rewrite the url without the act
+var QWiki = function() {
+  QCore.call(this);
+  // make cache directory
+  try {
+    var stats = fs.statSync('cache');
+    if (!stats.isDirectory()) {
+      console.log('error, "cache" is not a directory');
+    }
+  } catch (err) {
+    if (err.code == 'ENOENT') {
+      fs.mkdirSync('cache');
+      console.log('Created "cache/" -- this is where processed wiki pages are stored as HTML');
     } else {
-      act = "";
+      console.log('error while loading cache');
     }
-    req.fields = {};
-    req.files = {};
-    if (req.method == 'POST') {
-      var form = new formidable.IncomingForm();
-      form.multiples = true;
-      form.uploadDir = process.cwd()+'/tmp';
-      form.parse(req, function(err, fields, files) {
-        // TODO: handle err
-        req.fields = fields;
-        req.files = files;
-        for (var i = 0, keys = Object.keys(acts[act]); i < keys.length; i++) {
-          acts[act][i](req, res);
-        }
-      });
-      return;
+  }
+  // make tmp directory
+  try {
+    var stats = fs.statSync('tmp');
+    if (!stats.isDirectory()) {
+      console.log('error, "tmp" is not a directory');
+    }
+  } catch (err) {
+    if (err.code == 'ENOENT') {
+      fs.mkdirSync('tmp');
+      console.log('Created "tmp/" -- this is where uploaded files are temporarily stored');
     } else {
-      for (var i = 0, keys = Object.keys(acts[act]); i < keys.length; i++) {
-        acts[act][i](req, res);
-      }
+      console.log('error while loading tmp directory');
     }
-  });
+  }
 
-  this.act = function(act, callback) {
-    if (typeof acts[act] === 'undefined') {
-      acts[act] = [];
-    }
-    acts[act].push(callback);
-  };
-  this.rule = function(zone, replace, callback) {
-    if (typeof this.rules[zone] === 'undefined') {
-      this.rules[zone] = {};
-    }
-    this.rules[zone][replace] = callback;
-  };
-  var fs_timeout; // timer to bypass multiple fs.watch triggering due to unstable API
-  this.loadIndex = function(event, filename) {
-    if (fs_timeout) return;
-    fs_timeout = setTimeout(function() { fs_timeout = null; }, 100);
-    var reg = /@@[a-z]+@@/ig;
-    fs.readFile('index.html', function(err, data) {
-      if (err) throw err;
-      self.index = data;
-      var search;
-      self.index_parts = [];
-      while ((search = reg.exec(data)) !== null) {
-        self.index_parts.push({ replace: search[0], start: search.index, end: reg.lastIndex });
-      }
-    });
-  };
-  // TODO: loadActs should allow for dynamic updates by using fs.watch and clearing the require cache
-  this.loadActs = function(cb) {
-    var self = this;
-    fs.readdir('acts', function(err, files) {
-      if (err) throw err;
-      for (var i = 0; i < files.length; i++) {
-        var ext = self.getExt(files[i]);
-        if (ext == 'js') {
-          console.log('Loading act: ' + files[i]);
-          require('./acts/'+files[i])(self);
-        }
-      }
-      (typeof cb !== 'undefined' ? cb() : '');
-    });
-  };
-  fs.watch('index.html', this.loadIndex);
-};
-QCore.prototype.defaults = {};
-QCore.prototype.listen = function(port) {
-  this.loadActs();
-  this.loadIndex();
-  this.loadWikiIndex();
-  try { 
-    this.http.listen(port);
-  } catch(err) {
-    console.log('uhoh, could not listen on port '+port);
-    console.log(err);
-  }
-};
-QCore.prototype.setDefault = function(key, value) {
-  this.defaults[key] = value;
-};
-QCore.prototype.getDefault = function(key) {
-  if (key in this.defaults) {
-    return this.defaults[key];
-  }
-  return '';
-};
-QCore.prototype.addMIMEtype = function(ext, mimetype) {
-  if (typeof this.mimetypes[ext] === 'undefined') this.mimetypes[ext] = [];
-  this.mimetypes[ext].push(mimetype);
-};
-QCore.prototype.getMIMEtype = function(ext) {
-  if (this.mimetypes[ext]) return this.mimetypes[ext][0];
-  return '';
-};
-QCore.prototype.loadWikiIndex = function() {
+}; util.inherits(QWiki, QCore);
+QWiki.prototype.loadWikiIndex = function() {
   var self = this;
   try {
     this.wiki_index = JSON.parse(fs.readFileSync('index.json'));
@@ -137,7 +51,7 @@ QCore.prototype.loadWikiIndex = function() {
   }
   var wiki_index = this.wiki_index;
   console.log('loaded indices for ' + Object.keys(this.wiki_index.pages).length + ' pages');
-  readFiles('wiki', function(err, file, is_dir) {
+  QCore.prototype.readFiles('wiki', function(err, file, is_dir) {
     var wiki_file = file.replace('wiki/', '');
     if (is_dir) {
       if (wiki_file in wiki_index.pages) {
@@ -146,7 +60,7 @@ QCore.prototype.loadWikiIndex = function() {
         wiki_index.pages[wiki_file] = { dir: true };
       }
     } else {
-      if (self.getExt(file) == 'qwk') {
+      if (QCore.prototype.getExt(file) == 'qwk') {
         if (wiki_file in wiki_index.pages) {
         } else {
           var name = wiki_file.slice(0, -4);
@@ -161,83 +75,6 @@ QCore.prototype.loadWikiIndex = function() {
     }
   });
 }
-QCore.prototype.parsePage = function(zone, fallback_zone, req, res) {
-  var qparse = new QParse();
-  qparse.indices = this.index_parts.slice(); // clone it TODO: just make an indices (number-only) array
-
-  var context = this;
-  var cb = function(qparse, callback) {
-    res.write(context.index.slice(qparse.offset, qparse.current.start));
-    if (typeof context.rules[zone] === 'undefined') { zone = fallback_zone; }
-    if (typeof context.rules[zone] === 'undefined') { zone = ''; }
-    if (typeof context.rules[zone] !== 'undefined') {
-      if (typeof context.rules[zone][qparse.current.replace] !== 'undefined') {
-        context.rules[zone][qparse.current.replace](req, res, qparse, callback);
-      } else {
-        // fallback action
-        if (typeof context.rules[fallback_zone][qparse.current.replace] !== 'undefined') {
-          context.rules[fallback_zone][qparse.current.replace](req, res, qparse, callback);
-        // default action
-        } else if (typeof context.rules[''][qparse.current.replace] !== 'undefined') {
-          context.rules[''][qparse.current.replace](req, res, qparse, callback);
-        } else {
-          callback();
-        }
-      }
-    }
-  };
-
-  var series = function(qparse) {
-    if (qparse.current) {
-      cb(qparse, function(next) {
-        if (qparse.next) {
-          res.write(context.index.slice(qparse.current.end, qparse.next.start));
-          qparse.offset = qparse.next.end;
-          qparse.current = qparse.indices.shift();
-          qparse.next = qparse.indices[0];
-          series(qparse);
-        } else {
-          res.write(context.index.slice(qparse.current.end));
-          qparse.current = qparse.indices.shift();
-          qparse.next = qparse.indices[0];
-          series(qparse);
-        }
-      });
-    } else {
-      res.end();
-    }
-  };
-  qparse.current = qparse.indices.shift();
-  qparse.next = qparse.indices[0];
-  series(qparse);
-};
-/*********************************
- * QParse                         *
- *********************************/
-var QParse = function() {};
-QParse.prototype.indices = {};
-QParse.prototype.offset = 0;
-QParse.prototype.current = null;
-QParse.prototype.next = null;
-/********************************* 
- * QWiki                         *
- *********************************/
-var QWiki = function() {
-  QCore.call(this);
-  try {
-    var stats = fs.statSync('cache');
-    if (!stats.isDirectory()) {
-      console.log('error, "cache" is not a directory');
-    }
-  } catch (err) {
-    if (err.code == 'ENOENT') {
-      fs.mkdirSync('cache');
-      console.log('Created "cache/" -- this is where processed wiki pages are stored as HTML');
-    } else {
-      console.log('error while loading cache');
-    }
-  }
-}; util.inherits(QWiki, QCore);
 QWiki.prototype.formats = {
   raw: {
     name: 'raw',
@@ -270,8 +107,8 @@ QWiki.prototype.convertAndSave = function(path, name, source, cb) {
   }
   console.log('converting to ' + name + ' with path ' + path);
   var converted = this.formats[name].convert(source);
-  r_mkdir(m_path.dirname('cache'+path+'.html'), 0777, function() {
-    fs.writeFile('cache'+path+'.html', converted, function(err) {
+  QCore.prototype.r_mkdir(m_path.dirname('cache/'+path+'.html'), 0777, function() {
+    fs.writeFile('cache/'+path+'.html', converted, function(err) {
       if (err) {
         console.log('error writing "cache'+ path + '.html"');
       } else {
@@ -299,7 +136,7 @@ QWiki.prototype.createCache = function(wiki_page, source, cb) {
 
   var path = 'cache/' + wiki_page + '.html';
 
-  r_mkdir(m_path.dirname(path), 0777, function() {
+  QCore.prototype.r_mkdir(m_path.dirname(path), 0777, function() {
     fs.writeFile(path, converted, function(err) {
       if (err) {
         console.log('createCache: error writing "' + path + '"');
@@ -315,7 +152,7 @@ QWiki.prototype.deletePage = function(wiki_page, cb) {
 };
 QWiki.prototype.savePage = function(wiki_page, content, cb) {
   var path = 'wiki/' + wiki_page + '.qwk';
-  r_mkdir(m_path.dirname(path), 0777, function() {
+  QCore.prototype.r_mkdir(m_path.dirname(path), 0777, function() {
     console.log('savePage: writing ' + path);
     qwiki.deleteCache(wiki_page, function() {
       fs.writeFile(path, content, function(err) {
@@ -328,7 +165,6 @@ QWiki.prototype.savePage = function(wiki_page, content, cb) {
 QWiki.prototype.deleteFile = function(file, cb) {
   fs.unlink('wiki/'+file, cb);
 };
-
 /********************************* 
  * qwiki instance                *
  *********************************/
@@ -354,88 +190,6 @@ qwiki.addFormat('md', 'Markdown', function(source) {
 
 qwiki.setDefault('format', 'md');
 
-var readFile = function(stream, path, cb) {
-  fs.exists(path, function(exists) {
-    if (exists) {
-      var rs = fs.createReadStream(path);
-      rs.on('data', function(chunk) {
-        stream.write(chunk);
-      });
-      rs.on('end', function() {
-        cb('EOF');
-      });
-      rs.on('error', function(err) {
-        cb('ERR', err);
-      });
-    } else {
-      cb('FNF'); // file not found
-    }
-  });
-};
-QCore.prototype.readFile = readFile;
-
-var readFiles = function(dir, callback) {
-  fs.readdir(dir, function(err, files) {
-    files.forEach(function(file) {
-      var stat_file = dir+'/'+file
-      fs.stat(stat_file, function(err, stats) {
-        if (stats.isDirectory()) {
-          readFiles(stat_file, callback);
-          callback(err, stat_file, true);
-        } else if (stats.isFile()) {
-          callback(err, stat_file, false);
-        }
-      });
-    });
-  });
-};
-
-QCore.prototype.readDirectory = function(dir, callback) {
-  var all_files = [];
-  fs.readdir(dir, function(err, files) {
-    if (err) {
-      callback(err, null);
-      return;
-    }
-    var len = files.length;
-    var total = 0;
-    if (len == 0) callback(err, all_files);
-    files.forEach(function(file, index) {
-      var stat_file = dir + '/' + file;
-      fs.stat(stat_file, function(err, stats) {
-        if (stats.isDirectory()) {
-          all_files.push({'is_dir': true, 'name': file});
-        } else if (stats.isFile()) {
-          all_files.push({'is_dir': false, 'name': file});
-        }
-        if (++total == len) {
-          callback(err, all_files);
-        }
-      });
-    });
-  });
-};
-
-QCore.prototype.getExt = function(file) {
-  if (typeof file === 'undefined') return '';
-  var last = file.lastIndexOf(".");
-  if (last == -1) return '';
-  var ext = file.substr(last+1);
-  return ext;
-};
-
-var r_mkdir = function(dir, mode, cb) {
-  console.log('creating '+dir);
-  fs.mkdir(dir, mode, function(err) {
-    if (err && err.code == 'ENOENT') {
-      r_mkdir(m_path.dirname(dir), mode, r_mkdir.bind(this, dir, mode, cb));
-    } else if (typeof cb !== 'undefined') {
-      cb(err);
-    }
-  });
-};
-QCore.prototype.r_mkdir = r_mkdir;
-
 var port = 8080;
 process.argv.forEach(function (val, index, array) {
   var parts = val.split('=');
@@ -454,4 +208,4 @@ process.argv.forEach(function (val, index, array) {
   }
 });
 
-qwiki.listen(port);
+qwiki.listen(port, function() { qwiki.loadWikiIndex(); });