diff --git a/include/cli/modules/serve.php b/include/cli/modules/serve.php
new file mode 100644
index 0000000000000000000000000000000000000000..26075c6bd14e9b76b210665a105bd6c198a5998f
--- /dev/null
+++ b/include/cli/modules/serve.php
@@ -0,0 +1,95 @@
+<?php
+
+class CliServerModule extends Module {
+    var $prologue = "Run a CLI server for osTicket";
+
+    var $options = array(
+        'port' => array('-p','--port',
+            'default'=>'8000',
+            'help'=>'Specify the listening port number. Default is 8000',
+        ),
+        'host' => array('-h','--host',
+            'default'=>'localhost',
+            'help'=>'Specify the bind address. Default is "localhost"',
+        ),
+    );
+
+    function make_router() {
+        $temp = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
+        $router_path = $temp
+            . substr(md5('osticket-router'.getcwd()), -12)
+            . '.php';
+
+        // Ensure that the router file is cleaned up on exit
+        $cleanup = function() use ($router_path) {
+            @unlink($router_path);
+        };
+        if (function_exists('pcntl_signal'))
+            pcntl_signal(SIGINT, $cleanup);
+
+        // This will very likely not fire
+        register_shutdown_function($cleanup);
+
+        $fp = fopen($router_path, 'wt');
+        fwrite($fp, <<<EOF
+<?php
+\$full_path = \$_SERVER["DOCUMENT_ROOT"] . \$_SERVER["REQUEST_URI"];
+# Ensure trailing slash on folders
+if (is_dir(\$full_path)
+    && rtrim(\$full_path, '/') == \$full_path
+) {
+    header("Location: " . \$_SERVER["REQUEST_URI"] . '/');
+}
+elseif (file_exists(\$_SERVER['SCRIPT_FILENAME'])) {
+    return false;
+}
+// Support various dispatchers
+elseif (\$offs = stripos(\$_SERVER["REQUEST_URI"], 'scp/apps/')) {
+    \$_SERVER["PATH_INFO"] = substr(\$_SERVER["REQUEST_URI"], \$offs + 8);
+    chdir('scp/');
+    require "apps/dispatcher.php";
+}
+elseif (\$offs = stripos(\$_SERVER["REQUEST_URI"], 'pages/')) {
+    \$_SERVER["PATH_INFO"] = substr(\$_SERVER["REQUEST_URI"], \$offs + 5);
+    require "pages/index.php";
+}
+elseif (\$offs = stripos(\$_SERVER["REQUEST_URI"], 'api/')) {
+    \$_SERVER["PATH_INFO"] = substr(\$_SERVER["REQUEST_URI"], \$offs + 3);
+    require "api/http.php";
+}
+EOF
+        );
+        fclose($fp);
+
+        return $router_path;
+    }
+
+    function run($args, $options) {
+        $router = $this->make_router();
+        $pipes = array();
+        $php = proc_open(
+            sprintf("php -S %s:%s -t %s %s", $options['host'], $options['port'],
+                ROOT_DIR, $router),
+            array(
+                1 => array('pipe', 'w'),
+                2 => array('pipe', 'w'),
+            ), $pipes);
+
+        stream_set_blocking($pipes[1], 0);
+        stream_set_blocking($pipes[2], 0);
+
+        while (true) {
+            if (feof($pipes[1]) || feof($pipes[2])) {
+                fclose($pipes[1]);
+                fclose($pipes[2]);
+                break;
+            }
+            if ($block = fgets($pipes[1], 1024))
+                fwrite(STDOUT, $block);
+            if ($block = fgets($pipes[2], 1024))
+                fwrite(STDERR, $block);
+            usleep(100);
+        }
+    }
+}
+Module::register('serve', 'CliServerModule');
diff --git a/include/staff/footer.inc.php b/include/staff/footer.inc.php
index a646f5eeae2ef7350ff15e7e7445a01c1dcba433..5d0cc65aef0a245665d6921d8f7a2bb14b4be8be 100644
--- a/include/staff/footer.inc.php
+++ b/include/staff/footer.inc.php
@@ -8,7 +8,7 @@
 if(is_object($thisstaff) && $thisstaff->isStaff()) { ?>
     <div>
         <!-- Do not remove <img src="autocron.php" alt="" width="1" height="1" border="0" /> or your auto cron will cease to function -->
-        <img src="autocron.php" alt="" width="1" height="1" border="0" />
+        <img src="<?php echo ROOT_PATH; ?>scp/autocron.php" alt="" width="1" height="1" border="0" />
         <!-- Do not remove <img src="autocron.php" alt="" width="1" height="1" border="0" /> or your auto cron will cease to function -->
     </div>
 <?php
@@ -41,20 +41,20 @@ if(is_object($thisstaff) && $thisstaff->isStaff()) { ?>
 </div>
 
 <script type="text/javascript" src="<?php echo ROOT_PATH; ?>js/jquery.pjax.js"></script>
-<script type="text/javascript" src="./js/bootstrap-typeahead.js"></script>
-<script type="text/javascript" src="./js/scp.js"></script>
+<script type="text/javascript" src="<?php echo ROOT_PATH; ?>scp/js/bootstrap-typeahead.js"></script>
+<script type="text/javascript" src="<?php echo ROOT_PATH; ?>scp/js/scp.js"></script>
 <script type="text/javascript" src="<?php echo ROOT_PATH; ?>js/jquery-ui-1.10.3.custom.min.js"></script>
 <script type="text/javascript" src="<?php echo ROOT_PATH; ?>js/filedrop.field.js"></script>
 <script type="text/javascript" src="<?php echo ROOT_PATH; ?>js/select2.min.js"></script>
-<script type="text/javascript" src="./js/tips.js"></script>
+<script type="text/javascript" src="<?php echo ROOT_PATH; ?>scp/js/tips.js"></script>
 <script type="text/javascript" src="<?php echo ROOT_PATH; ?>js/redactor.min.js"></script>
 <script type="text/javascript" src="<?php echo ROOT_PATH; ?>js/redactor-osticket.js"></script>
 <script type="text/javascript" src="<?php echo ROOT_PATH; ?>js/redactor-plugins.js"></script>
-<script type="text/javascript" src="./js/jquery.translatable.js"></script>
-<script type="text/javascript" src="./js/jquery.dropdown.js"></script>
-<script type="text/javascript" src="./js/bootstrap-tooltip.js"></script>
+<script type="text/javascript" src="<?php echo ROOT_PATH; ?>scp/js/jquery.translatable.js"></script>
+<script type="text/javascript" src="<?php echo ROOT_PATH; ?>scp/js/jquery.dropdown.js"></script>
+<script type="text/javascript" src="<?php echo ROOT_PATH; ?>scp/js/bootstrap-tooltip.js"></script>
 <script type="text/javascript" src="<?php echo ROOT_PATH; ?>js/fabric.min.js"></script>
-<link type="text/css" rel="stylesheet" href="./css/tooltip.css">
+<link type="text/css" rel="stylesheet" href="<?php echo ROOT_PATH; ?>scp/css/tooltip.css">
 <script type="text/javascript">
     getConfig().resolve(<?php
         include INCLUDE_DIR . 'ajax.config.php';
diff --git a/include/staff/header.inc.php b/include/staff/header.inc.php
index 0bb3b3b779e74b7c56d92e2152a38296f42417fc..13a122c54df8a1fb40a03435e431b068d9fd05bd 100644
--- a/include/staff/header.inc.php
+++ b/include/staff/header.inc.php
@@ -25,21 +25,21 @@ if ($lang) {
     <![endif]-->
     <script type="text/javascript" src="<?php echo ROOT_PATH; ?>js/jquery-1.11.2.min.js"></script>
     <link rel="stylesheet" href="<?php echo ROOT_PATH ?>css/thread.css" media="all">
-    <link rel="stylesheet" href="./css/scp.css" media="all">
+    <link rel="stylesheet" href="<?php echo ROOT_PATH ?>scp/css/scp.css" media="all">
     <link rel="stylesheet" href="<?php echo ROOT_PATH; ?>css/redactor.css" media="screen">
-    <link rel="stylesheet" href="./css/typeahead.css" media="screen">
+    <link rel="stylesheet" href="<?php echo ROOT_PATH ?>scp/css/typeahead.css" media="screen">
     <link type="text/css" href="<?php echo ROOT_PATH; ?>css/ui-lightness/jquery-ui-1.10.3.custom.min.css"
          rel="stylesheet" media="screen" />
      <link type="text/css" rel="stylesheet" href="<?php echo ROOT_PATH; ?>css/font-awesome.min.css">
     <!--[if IE 7]>
     <link rel="stylesheet" href="<?php echo ROOT_PATH; ?>css/font-awesome-ie7.min.css">
     <![endif]-->
-    <link type="text/css" rel="stylesheet" href="./css/dropdown.css">
+    <link type="text/css" rel="stylesheet" href="<?php echo ROOT_PATH ?>scp/css/dropdown.css">
     <link type="text/css" rel="stylesheet" href="<?php echo ROOT_PATH; ?>css/loadingbar.css"/>
     <link type="text/css" rel="stylesheet" href="<?php echo ROOT_PATH; ?>css/flags.css">
     <link type="text/css" rel="stylesheet" href="<?php echo ROOT_PATH; ?>css/select2.min.css">
     <link type="text/css" rel="stylesheet" href="<?php echo ROOT_PATH; ?>css/rtl.css"/>
-    <link type="text/css" rel="stylesheet" href="./css/translatable.css"/>
+    <link type="text/css" rel="stylesheet" href="<?php echo ROOT_PATH ?>scp/css/translatable.css"/>
 
     <?php
     if($ost && ($headers=$ost->getExtraHeaders())) {
@@ -61,16 +61,16 @@ if ($lang) {
         <p id="info" class="pull-right no-pjax"><?php echo sprintf(__('Welcome, %s.'), '<strong>'.$thisstaff->getFirstName().'</strong>'); ?>
            <?php
             if($thisstaff->isAdmin() && !defined('ADMINPAGE')) { ?>
-            | <a href="admin.php" class="no-pjax"><?php echo __('Admin Panel'); ?></a>
+            | <a href="<?php echo ROOT_PATH ?>scp/admin.php" class="no-pjax"><?php echo __('Admin Panel'); ?></a>
             <?php }else{ ?>
-            | <a href="index.php" class="no-pjax"><?php echo __('Agent Panel'); ?></a>
+            | <a href="<?php echo ROOT_PATH ?>scp/index.php" class="no-pjax"><?php echo __('Agent Panel'); ?></a>
             <?php } ?>
-            | <a href="profile.php"><?php echo __('Profile'); ?></a>
-            | <a href="logout.php?auth=<?php echo $ost->getLinkToken(); ?>" class="no-pjax"><?php echo __('Log Out'); ?></a>
+            | <a href="<?php echo ROOT_PATH ?>scp/profile.php"><?php echo __('Profile'); ?></a>
+            | <a href="<?php echo ROOT_PATH ?>scp/logout.php?auth=<?php echo $ost->getLinkToken(); ?>" class="no-pjax"><?php echo __('Log Out'); ?></a>
         </p>
-        <a href="index.php" class="no-pjax" id="logo">
+        <a href="<?php echo ROOT_PATH ?>scp/index.php" class="no-pjax" id="logo">
             <span class="valign-helper"></span>
-            <img src="logo.php?<?php echo strtotime($cfg->lastModified('staff_logo_id')); ?>" alt="osTicket &mdash; <?php echo __('Customer Support System'); ?>"/>
+            <img src="<?php echo ROOT_PATH ?>scp/logo.php?<?php echo strtotime($cfg->lastModified('staff_logo_id')); ?>" alt="osTicket &mdash; <?php echo __('Customer Support System'); ?>"/>
         </a>
     </div>
     <div id="pjax-container" class="<?php if ($_POST) echo 'no-pjax'; ?>">
diff --git a/include/staff/templates/navigation.tmpl.php b/include/staff/templates/navigation.tmpl.php
index 8f0444999c7d0c169acbc599dfbfa69285a2462d..5c86f47153b767c1c9bfd3d47c13b5f4e501236e 100644
--- a/include/staff/templates/navigation.tmpl.php
+++ b/include/staff/templates/navigation.tmpl.php
@@ -1,6 +1,8 @@
 <?php
 if(($tabs=$nav->getTabs()) && is_array($tabs)){
     foreach($tabs as $name =>$tab) {
+        if ($tab['href'][0] != '/')
+            $tab['href'] = ROOT_PATH . 'scp/' . $tab['href'];
         echo sprintf('<li class="%s %s"><a href="%s">%s</a>',
             $tab['active'] ? 'active':'inactive',
             @$tab['class'] ?: '',
@@ -10,6 +12,8 @@ if(($tabs=$nav->getTabs()) && is_array($tabs)){
             foreach($subnav as $k => $item) {
                 if (!($id=$item['id']))
                     $id="nav$k";
+                if ($item['href'][0] != '/')
+                    $item['href'] = ROOT_PATH . 'scp/' . $item['href'];
 
                 echo sprintf(
                     '<li><a class="%s" href="%s" title="%s" id="%s">%s</a></li>',
diff --git a/scp/admin.inc.php b/scp/admin.inc.php
index 434589acaa0584d7164fa32afc48c6848adea88c..9f701eb0bbeaa7d9e53bb227ba09c1061077cd4d 100644
--- a/scp/admin.inc.php
+++ b/scp/admin.inc.php
@@ -13,7 +13,7 @@
 
     vim: expandtab sw=4 ts=4 sts=4:
 **********************************************************************/
-require('staff.inc.php');
+require_once 'staff.inc.php';
 //Make sure config is loaded and the staff is set and of admin type
 if(!$ost or !$thisstaff or !$thisstaff->isAdmin()){
     header('Location: index.php');
diff --git a/scp/apps/dispatcher.php b/scp/apps/dispatcher.php
index ac5ac585040ffdbc0bcbc8d9056c2cd651628e31..63255923a7bc73d2c38989d1f658fb295d5e511e 100644
--- a/scp/apps/dispatcher.php
+++ b/scp/apps/dispatcher.php
@@ -14,19 +14,14 @@
 
     vim: expandtab sw=4 ts=4 sts=4:
 **********************************************************************/
-# Override staffLoginPage() defined in staff.inc.php to return an
-# HTTP/Forbidden status rather than the actual login page.
-# XXX: This should be moved to the AjaxController class
-function staffLoginPage($msg='Unauthorized') {
-    Http::response(403,'Must login: '.Format::htmlchars($msg));
-    exit;
-}
+if (basename($_SERVER['SCRIPT_NAME'])==basename(__FILE__))
+    die('Access denied'); //Say hi to our friend..
 
 require('staff.inc.php');
 
 //Clean house...don't let the world see your crap.
-ini_set('display_errors','0'); //Disable error display
-ini_set('display_startup_errors','0');
+#ini_set('display_errors','0'); //Disable error display
+#ini_set('display_startup_errors','0');
 
 //TODO: disable direct access via the browser? i,e All request must have REFER?
 if(!defined('INCLUDE_DIR'))	Http::response(500, 'Server configuration error');
@@ -34,7 +29,17 @@ if(!defined('INCLUDE_DIR'))	Http::response(500, 'Server configuration error');
 require_once INCLUDE_DIR.'/class.dispatcher.php';
 $dispatcher = new Dispatcher();
 
-Signal::send('apps.scp', $dispatcher);
+$PI = $ost->get_path_info();
+if (strpos(strtolower($PI), '/admin/') === 0) {
+    require('admin.inc.php');
+    $PI = substr($PI, 6);
+    Signal::send('apps.admin', $dispatcher);
+}
+else {
+    Signal::send('apps.scp', $dispatcher);
+}
+
+$nav->setActiveTab('apps');
 
 # Call the respective function
-print $dispatcher->resolve($ost->get_path_info());
+print $dispatcher->resolve($PI);
diff --git a/scp/staff.inc.php b/scp/staff.inc.php
index bb06f3eb8e92dcf8094b1f1500137c282eddceec..757befc8d5a449b78326531b2500387e904df846 100644
--- a/scp/staff.inc.php
+++ b/scp/staff.inc.php
@@ -49,7 +49,9 @@ if(!function_exists('staffLoginPage')) { //Ajax interface can pre-declare the fu
         $_SESSION['_staff']['auth']['dest'] =
             '/' . ltrim($_SERVER['REQUEST_URI'], '/');
         $_SESSION['_staff']['auth']['msg']=$msg;
-        require(SCP_DIR.'login.php');
+
+        // Redirect here with full path for application-type plugins
+        Http::redirect(ROOT_PATH.'scp/login.php');
         exit;
     }
 }