diff --git a/extension/reviewbotext/extension.py b/extension/reviewbotext/extension.py
index 1cb35525f3083dca81e80af395d38804f5f99be9..19d8116d5bf86d2f660fb4cea54649fcbdf38694 100644
--- a/extension/reviewbotext/extension.py
+++ b/extension/reviewbotext/extension.py
@@ -2,17 +2,24 @@ from django.conf import settings
 from django.contrib.auth import login
 from django.contrib.auth.models import User
 from django.contrib.sites.models import Site
+from django.core.exceptions import ObjectDoesNotExist
 from django.http import HttpRequest
 from django.utils.importlib import import_module
 
 from celery import Celery
 from djblets.siteconfig.models import SiteConfiguration
+from djblets.webapi.resources import register_resource_for_model, \
+                                     unregister_resource_for_model
 from reviewboard.extensions.base import Extension
+from reviewboard.extensions.hooks import DiffViewerActionHook, \
+                                         ReviewRequestActionHook, \
+                                         TemplateHook
 
 from reviewbotext.handlers import SignalHandlers
 from reviewbotext.models import ReviewBotTool
 from reviewbotext.resources import review_bot_review_resource, \
-                                   review_bot_tool_resource
+                                   review_bot_tool_resource, \
+                                   review_bot_trigger_review_resource
 
 
 class ReviewBotExtension(Extension):
@@ -30,6 +37,7 @@ class ReviewBotExtension(Extension):
     resources = [
         review_bot_review_resource,
         review_bot_tool_resource,
+        review_bot_trigger_review_resource,
     ]
 
     def __init__(self, *args, **kwargs):
@@ -37,13 +45,31 @@ class ReviewBotExtension(Extension):
         self.settings.load()
         self.celery = Celery('reviewbot.tasks')
         self.signal_handlers = SignalHandlers(self)
+        register_resource_for_model(ReviewBotTool,
+                                    review_bot_tool_resource)
+        self.add_action_hooks()
+        self.template_hook = TemplateHook(self,
+                                          'base-scripts-post',
+                                          'reviewbot_hook_action.html')
+
+    def add_action_hooks(self):
+        actions = [{
+            'id': 'reviewbot-link',
+            'label': 'Review Bot',
+            'url': '#'
+        }]
+        self.review_action_hook = ReviewRequestActionHook(self,
+                                                          actions=actions)
+        self.diff_action_hook = DiffViewerActionHook(self, actions=actions)
 
     def shutdown(self):
         self.signal_handlers.disconnect()
+        unregister_resource_for_model(ReviewBotTool)
         super(ReviewBotExtension, self).shutdown()
 
-    def notify(self, request_payload):
+    def notify(self, request_payload, selected_tools=None):
         """Add the request to the queue."""
+
         self.celery.conf.BROKER_URL = self.settings['BROKER_URL']
 
         review_settings = {
@@ -55,8 +81,21 @@ class ReviewBotExtension(Extension):
             'session': self._login_user(self.settings['user']),
             'url': self._rb_url(),
         }
-        tools = ReviewBotTool.objects.filter(enabled=True,
-                                             run_automatically=True)
+
+        if (selected_tools is not None):
+            tools = []
+            for tool in selected_tools:
+            # Double-check that the tool can be run manually in case
+            # this setting was changed between trigger time and queue time.
+                try:
+                    tools.append(
+                        ReviewBotTool.objects.get(id=tool['id'],
+                                                  allow_run_manually=True))
+                except ObjectDoesNotExist:
+                    pass
+        else:
+            tools = ReviewBotTool.objects.filter(enabled=True,
+                                                 run_automatically=True)
 
         for tool in tools:
             review_settings['ship_it'] = tool.ship_it
diff --git a/extension/reviewbotext/htdocs/css/reviewbot.css b/extension/reviewbotext/htdocs/css/reviewbot.css
new file mode 100644
index 0000000000000000000000000000000000000000..78a3fc79e61c7bd5d117347703c829555cdb6bc7
--- /dev/null
+++ b/extension/reviewbotext/htdocs/css/reviewbot.css
@@ -0,0 +1,11 @@
+#reviewbot-tool-description {
+  font-weight: bold;
+}
+
+.reviewbot-tool-dialog-subtitle {
+  font-weight: bold;
+}
+
+#reviewbot-tool-list {
+  list-style: none;
+}
\ No newline at end of file
diff --git a/extension/reviewbotext/htdocs/js/reviewbot.js b/extension/reviewbotext/htdocs/js/reviewbot.js
new file mode 100644
index 0000000000000000000000000000000000000000..241afce31e6affca9a047b317dada73c4170f3f5
--- /dev/null
+++ b/extension/reviewbotext/htdocs/js/reviewbot.js
@@ -0,0 +1,153 @@
+// Trigger the ReviewBot tools lightbox when clicking on the ReviewBot link
+//  in the nav bar.
+
+var dlg, dlgContentTable, dlgContentTBody, modal;
+
+$("#reviewbot-link").click(function() {
+    $.fetchReviewBotTools();
+});
+
+$.fetchReviewBotTools = function() {
+    RB.apiCall({
+        type: "GET",
+        dataType: "json",
+        data: {},
+        url: "/api/extensions/reviewbotext.extension.ReviewBotExtension/review-bot-tools/",
+        success: function(response) {
+            if (!dlg) {
+                $.createToolLightBox();
+            }
+            $.showToolLightBox(response);
+        }
+    });
+}
+
+$.createToolLightBox = function() {
+    modal = {
+        title: "Review Bot",
+    };
+
+    dlg = $("<div/>")
+        .attr("id", "reviewbot-tool-dialog")
+        .attr("class", "modalbox-contents")
+        .appendTo("body")
+
+    dlg.append(
+        $("<p/>")
+            .attr("id", "reviewbot-tool-description")
+            .html("Select the static analysis tools you would like to run."));
+
+    dlgContentTable = $("<table/>")
+        .appendTo(dlg)
+        .append($("<colgroup/>")
+            .append('<col/>'));
+
+    dlgContentTBody = $("<tbody/>")
+        .appendTo(dlgContentTable);
+
+    /*
+     * Add all new content to the dialog here.
+     */
+
+    // Content of Installed Tools generated dynamically later.
+    $.addSection("reviewbot-installed-tools", "Installed Tools");
+}
+
+/*
+ * Add a section to the Review Bot tools dialog.
+ *
+ * Content to the section can be added elsewhere using the content_id.
+ */
+$.addSection = function(content_id, subtitle) {
+    dlgContentTBody
+        .append($("<tr/>")
+            .append(
+                $("<td/>")
+                    .html(
+                        $("<span class='reviewbot-tool-dialog-subtitle'/>")
+                            .html(subtitle))))
+        .append($("<tr/>")
+            .append(
+                $("<td/>")
+                    .html($("<div/>")
+                        .attr("id", content_id))));
+}
+
+$.showToolLightBox = function(response) {
+    // Display list of installed tools.
+    // Later on more data can be handled and displayed here.
+    var tools = response["review_bot_tools"];
+    var toolList = $("<ul/>")
+        .attr("id", "reviewbot-tool-list");
+
+    $.each(tools, function(index, tool){
+        if (tool["enabled"] && tool["allow_run_manually"]) {
+            toolList.append(
+                $("<li/>")
+                    .append($('<input type="checkbox"/>')
+                        .attr("id", "reviewbot-tool-checkbox_" + index)
+                        .attr("class", "toolCheckbox")
+                        .attr("checked", "checked")
+                        .prop("tool_id", tool["id"])
+                        .change(function() {
+                            var allChecked =
+                                ($(".toolCheckbox:checked").length > 0);
+                            $("#button_run").attr("disabled", !allChecked);
+                        }))
+                    .append($("<label/>")
+                        .attr("for", "reviewbot-tool-checkbox_" + index)
+                        .html(tool["name"] + " " + tool["version"]))
+            );
+        }
+    });
+
+    // Re-append dlg to body since closing the modalbox removes it.
+    dlg.appendTo("body");
+
+    if (toolList.children().length > 0) {
+        $("#reviewbot-installed-tools").html(toolList);
+
+        modal.buttons = [
+            $('<input id="button_cancel" type="button" value="Cancel"/>'),
+            $('<input id="button_run" type="button"/>')
+                .val("Run Tools")
+                .click(function(e){
+                    $.runSelectedTools($(".toolCheckbox:checked"));
+                }),
+        ];
+    } else {
+        // If no tools were loaded, display message.
+        $("#reviewbot-installed-tools")
+            .html("No tools available to run manually.");
+
+        modal.buttons = [
+            $('<input id="button_ok" type="button" value="OK"/>'),
+        ];
+    }
+    dlg.modalBox(modal);
+}
+
+$.runSelectedTools = function(selectedTools){
+    var tools = [];
+
+    $.each(selectedTools, function(index, selectedTool){
+        tools.push(
+            { id : $(selectedTool).prop("tool_id") }
+        );
+    });
+
+    request_payload = {
+        review_request_id: gReviewRequestId,
+        tools : JSON.stringify(tools),
+    }
+
+    RB.apiCall({
+        type: "POST",
+        dataType: "json",
+        data: request_payload,
+        url: "/api/extensions/reviewbotext.extension.ReviewBotExtension/review-bot-trigger-reviews/",
+        success: function(response) {
+            alert(response);
+        }
+    });
+}
\ No newline at end of file
diff --git a/extension/reviewbotext/resources.py b/extension/reviewbotext/resources.py
index b95273831d0ea42d4f226298d8c9eb5ded109eec..ef1e0978b03f9f4c50c9c38b1e78005118847d63 100644
--- a/extension/reviewbotext/resources.py
+++ b/extension/reviewbotext/resources.py
@@ -9,7 +9,8 @@ from djblets.webapi.errors import DOES_NOT_EXIST, INVALID_FORM_DATA, \
                                   NOT_LOGGED_IN, PERMISSION_DENIED
 
 from reviewboard.diffviewer.models import FileDiff
-from reviewboard.reviews.models import BaseComment, Review
+from reviewboard.extensions.base import get_extension_manager
+from reviewboard.reviews.models import BaseComment, Review, ReviewRequest
 from reviewboard.webapi.decorators import webapi_check_local_site
 from reviewboard.webapi.resources import WebAPIResource, \
                                          review_request_resource
@@ -132,9 +133,47 @@ review_bot_review_resource = ReviewBotReviewResource()
 
 
 class ReviewBotToolResource(WebAPIResource):
-    """Resource for workers to update the installed tools list."""
+    """Resource for workers to update the installed tools list.
+
+    Also provides information on the installed Review Bot tools. This can be
+    used to fetch the list of installed tools to be displayed in the Review
+    Board UI.
+
+    """
     name = 'review_bot_tool'
     allowed_methods = ('GET', 'POST',)
+    model = ReviewBotTool
+    model_object_key = 'id'
+    uri_object_key = 'reviewbot_tool_id'
+    fields = {
+        'id': {
+            'type': int,
+            'description': 'The id of the tool.',
+        },
+        'name': {
+            'type': str,
+            'description': 'The name of the tool.',
+        },
+        'version': {
+            'type': str,
+            'description': 'The tool version number.',
+        },
+        'enabled': {
+            'type': bool,
+            'description': 'Whether the user has enabled this '
+                           'tool in the Review Bot extension.',
+        },
+        'run_automatically': {
+            'type': bool,
+            'description': 'Whether the user has enabled this tool to '
+                           'run automatically.',
+        },
+        'allow_run_manually': {
+            'type': bool,
+            'description': 'Whether the user has enabled this tool to '
+                           'be triggered manually.',
+        },
+    }
 
     @webapi_check_local_site
     @webapi_login_required
@@ -210,3 +249,81 @@ class ReviewBotToolResource(WebAPIResource):
         return 201, {}
 
 review_bot_tool_resource = ReviewBotToolResource()
+
+
+class ReviewBotTriggerReviewResource(WebAPIResource):
+    """Resource that triggers a set of ReviewBot reviews given a list of tools
+    to run on a review request.
+
+    Can be called from within the ReviewBoard UI for the purpose of manually
+    triggering ReviewBot reviews.
+
+    """
+    name = 'review_bot_trigger_review'
+    allowed_methods = ('POST',)
+
+    @webapi_check_local_site
+    @webapi_login_required
+    @webapi_response_errors(DOES_NOT_EXIST, INVALID_FORM_DATA,
+                            NOT_LOGGED_IN, PERMISSION_DENIED)
+    @webapi_request_fields(
+        required={
+            'review_request_id': {
+                'type': int,
+                'description': 'ID of the review request to be reviewed.',
+            },
+            'tools': {
+                'type': str,
+                'description': 'A JSON payload containing tool information.',
+            },
+        },
+    )
+    def create(self, request, review_request_id, tools, *args, **kwargs):
+        """Trigger ReviewBot review(s) based on the set of given tools.
+
+        """
+        try:
+            tools = json.loads(tools)
+
+        except:
+            return INVALID_FORM_DATA, {
+                'fields': {
+                    'dtools': 'Malformed JSON.',
+                },
+            }
+
+        # Get the ReviewBotExtension instance. We can assume it exists because
+        # this code is executed after the extension has been registered with
+        # the manager.
+        from reviewbotext.extension import ReviewBotExtension
+        extension_manager = get_extension_manager()
+        extension = \
+            extension_manager.get_enabled_extension(ReviewBotExtension.id)
+        review_request = ReviewRequest.objects.get(id=review_request_id)
+        is_new = True
+        fields_changed = {}
+
+        # Check for a diff:
+        diffsets = review_request.diffset_history.diffsets.get_query_set()
+        if len(diffsets) > 0:
+            has_diff = True
+            diff_revision = diffsets[len(diffsets)-1].revision
+        else:
+            has_diff = False
+
+        request_payload = {
+            'review_request_id': review_request_id,
+            'new': is_new,
+            'fields_changed': fields_changed,
+            'has_diff': has_diff,
+        }
+
+        if has_diff:
+            request_payload['diff_revision'] = diff_revision
+
+        extension.notify(request_payload, tools)
+
+        # TODO: Fix the result key here.
+        return 201, {}
+
+review_bot_trigger_review_resource = ReviewBotTriggerReviewResource()
diff --git a/extension/reviewbotext/templates/reviewbot_hook_action.html b/extension/reviewbotext/templates/reviewbot_hook_action.html
new file mode 100644
index 0000000000000000000000000000000000000000..2464009b172cf6968f065690164a0b84ade8e3c3
--- /dev/null
+++ b/extension/reviewbotext/templates/reviewbot_hook_action.html
@@ -0,0 +1,3 @@
+{% load static %}
+<link rel="stylesheet" type="text/css" href="{% get_media_prefix %}/ext/Review-Bot-Extension/css/reviewbot.css"/>
+<script type="text/javascript" src="{% get_media_prefix %}/ext/Review-Bot-Extension/js/reviewbot.js"></script>
\ No newline at end of file
