changeset 624:f69b1faf812f

Irccd: split commands, closes #759 @2h
author David Demelier <markand@malikania.fr>
date Fri, 22 Dec 2017 21:31:31 +0100
parents 4515082ee83f
children 94ae26cd3cf8
files irccd/main.cpp libirccd/CMakeLists.txt libirccd/irccd/daemon/command.cpp libirccd/irccd/daemon/command.hpp libirccd/irccd/daemon/plugin_config_command.cpp libirccd/irccd/daemon/plugin_config_command.hpp libirccd/irccd/daemon/plugin_info_command.cpp libirccd/irccd/daemon/plugin_info_command.hpp libirccd/irccd/daemon/plugin_list_command.cpp libirccd/irccd/daemon/plugin_list_command.hpp libirccd/irccd/daemon/plugin_load_command.cpp libirccd/irccd/daemon/plugin_load_command.hpp libirccd/irccd/daemon/plugin_reload_command.cpp libirccd/irccd/daemon/plugin_reload_command.hpp libirccd/irccd/daemon/plugin_unload_command.cpp libirccd/irccd/daemon/plugin_unload_command.hpp libirccd/irccd/daemon/rule_add_command.cpp libirccd/irccd/daemon/rule_add_command.hpp libirccd/irccd/daemon/rule_edit_command.cpp libirccd/irccd/daemon/rule_edit_command.hpp libirccd/irccd/daemon/rule_info_command.cpp libirccd/irccd/daemon/rule_info_command.hpp libirccd/irccd/daemon/rule_list_command.cpp libirccd/irccd/daemon/rule_list_command.hpp libirccd/irccd/daemon/rule_move_command.cpp libirccd/irccd/daemon/rule_move_command.hpp libirccd/irccd/daemon/rule_remove_command.cpp libirccd/irccd/daemon/rule_remove_command.hpp libirccd/irccd/daemon/rule_service.cpp libirccd/irccd/daemon/rule_service.hpp libirccd/irccd/daemon/server_connect_command.cpp libirccd/irccd/daemon/server_connect_command.hpp libirccd/irccd/daemon/server_disconnect_command.cpp libirccd/irccd/daemon/server_disconnect_command.hpp libirccd/irccd/daemon/server_info_command.cpp libirccd/irccd/daemon/server_info_command.hpp libirccd/irccd/daemon/server_invite_command.cpp libirccd/irccd/daemon/server_invite_command.hpp libirccd/irccd/daemon/server_join_command.cpp libirccd/irccd/daemon/server_join_command.hpp libirccd/irccd/daemon/server_kick_command.cpp libirccd/irccd/daemon/server_kick_command.hpp libirccd/irccd/daemon/server_list_command.cpp libirccd/irccd/daemon/server_list_command.hpp libirccd/irccd/daemon/server_me_command.cpp libirccd/irccd/daemon/server_me_command.hpp libirccd/irccd/daemon/server_message_command.cpp libirccd/irccd/daemon/server_message_command.hpp libirccd/irccd/daemon/server_mode_command.cpp libirccd/irccd/daemon/server_mode_command.hpp libirccd/irccd/daemon/server_nick_command.cpp libirccd/irccd/daemon/server_nick_command.hpp libirccd/irccd/daemon/server_notice_command.cpp libirccd/irccd/daemon/server_notice_command.hpp libirccd/irccd/daemon/server_part_command.cpp libirccd/irccd/daemon/server_part_command.hpp libirccd/irccd/daemon/server_reconnect_command.cpp libirccd/irccd/daemon/server_reconnect_command.hpp libirccd/irccd/daemon/server_service.cpp libirccd/irccd/daemon/server_service.hpp libirccd/irccd/daemon/server_topic_command.cpp libirccd/irccd/daemon/server_topic_command.hpp tests/src/libirccd/command-plugin-config/main.cpp tests/src/libirccd/command-plugin-info/main.cpp tests/src/libirccd/command-plugin-list/main.cpp tests/src/libirccd/command-plugin-load/main.cpp tests/src/libirccd/command-plugin-reload/main.cpp tests/src/libirccd/command-plugin-unload/main.cpp tests/src/libirccd/command-rule-add/main.cpp tests/src/libirccd/command-rule-edit/main.cpp tests/src/libirccd/command-rule-info/main.cpp tests/src/libirccd/command-rule-list/main.cpp tests/src/libirccd/command-rule-move/main.cpp tests/src/libirccd/command-rule-remove/main.cpp tests/src/libirccd/command-server-connect/main.cpp tests/src/libirccd/command-server-disconnect/main.cpp tests/src/libirccd/command-server-info/main.cpp tests/src/libirccd/command-server-invite/main.cpp tests/src/libirccd/command-server-join/main.cpp tests/src/libirccd/command-server-kick/main.cpp tests/src/libirccd/command-server-list/main.cpp tests/src/libirccd/command-server-me/main.cpp tests/src/libirccd/command-server-message/main.cpp tests/src/libirccd/command-server-mode/main.cpp tests/src/libirccd/command-server-nick/main.cpp tests/src/libirccd/command-server-notice/main.cpp tests/src/libirccd/command-server-part/main.cpp tests/src/libirccd/command-server-reconnect/main.cpp tests/src/libirccd/command-server-topic/main.cpp
diffstat 89 files changed, 3045 insertions(+), 1309 deletions(-) [+]
line wrap: on
line diff
--- a/irccd/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/irccd/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -41,9 +41,37 @@
 #include <irccd/daemon/command_service.hpp>
 #include <irccd/daemon/irccd.hpp>
 #include <irccd/daemon/logger.hpp>
+#include <irccd/daemon/plugin_config_command.hpp>
+#include <irccd/daemon/plugin_info_command.hpp>
+#include <irccd/daemon/plugin_list_command.hpp>
+#include <irccd/daemon/plugin_list_command.hpp>
+#include <irccd/daemon/plugin_load_command.hpp>
+#include <irccd/daemon/plugin_reload_command.hpp>
 #include <irccd/daemon/plugin_service.hpp>
+#include <irccd/daemon/plugin_unload_command.hpp>
+#include <irccd/daemon/rule_add_command.hpp>
+#include <irccd/daemon/rule_edit_command.hpp>
+#include <irccd/daemon/rule_info_command.hpp>
+#include <irccd/daemon/rule_list_command.hpp>
+#include <irccd/daemon/rule_move_command.hpp>
+#include <irccd/daemon/rule_remove_command.hpp>
 #include <irccd/daemon/rule_service.hpp>
+#include <irccd/daemon/server_connect_command.hpp>
+#include <irccd/daemon/server_disconnect_command.hpp>
+#include <irccd/daemon/server_info_command.hpp>
+#include <irccd/daemon/server_invite_command.hpp>
+#include <irccd/daemon/server_join_command.hpp>
+#include <irccd/daemon/server_kick_command.hpp>
+#include <irccd/daemon/server_list_command.hpp>
+#include <irccd/daemon/server_me_command.hpp>
+#include <irccd/daemon/server_message_command.hpp>
+#include <irccd/daemon/server_mode_command.hpp>
+#include <irccd/daemon/server_nick_command.hpp>
+#include <irccd/daemon/server_notice_command.hpp>
+#include <irccd/daemon/server_part_command.hpp>
+#include <irccd/daemon/server_reconnect_command.hpp>
 #include <irccd/daemon/server_service.hpp>
+#include <irccd/daemon/server_topic_command.hpp>
 #include <irccd/daemon/transport_service.hpp>
 
 #if defined(HAVE_JS)
--- a/libirccd/CMakeLists.txt	Fri Dec 22 11:08:01 2017 +0100
+++ b/libirccd/CMakeLists.txt	Fri Dec 22 21:31:31 2017 +0100
@@ -32,12 +32,40 @@
     ${libirccd_SOURCE_DIR}/irccd/daemon/irc.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/local_transport_server.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/logger.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_config_command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/plugin.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_info_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_list_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_list_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_load_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_reload_command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_service.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_unload_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/rule_add_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/rule_edit_command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/rule.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/rule_info_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/rule_list_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/rule_move_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/rule_remove_command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/rule_service.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_connect_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_disconnect_command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/server.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_info_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_invite_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_join_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_kick_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_list_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_me_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_message_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_mode_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_nick_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_notice_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_part_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_reconnect_command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/server_service.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_topic_command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/transport_client.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/transport_server.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/transport_service.hpp
@@ -46,18 +74,44 @@
 
 set(
     SOURCES
-    ${libirccd_SOURCE_DIR}/irccd/daemon/command.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/command_service.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/dynlib_plugin.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/irccd.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/irc.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/logger.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_config_command.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/plugin.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_info_command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_list_command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_load_command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_reload_command.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_service.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_unload_command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/rule_add_command.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/rule.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/rule_edit_command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/rule_info_command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/rule_list_command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/rule_move_command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/rule_remove_command.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/rule_service.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_connect_command.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/server.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_disconnect_command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_info_command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_invite_command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_join_command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_kick_command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_list_command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_me_command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_message_command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_mode_command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_nick_command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_notice_command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_part_command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_reconnect_command.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/server_service.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_topic_command.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/transport_client.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/transport_server.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/transport_service.cpp
--- a/libirccd/irccd/daemon/command.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,729 +0,0 @@
-/*
- * command.cpp -- implementation of plugin-config command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <irccd/json_util.hpp>
-#include <irccd/string_util.hpp>
-
-#include "command.hpp"
-#include "irccd.hpp"
-#include "rule_service.hpp"
-#include "plugin_service.hpp"
-#include "server_service.hpp"
-#include "transport_client.hpp"
-
-using namespace std::string_literals;
-
-namespace irccd {
-
-namespace {
-
-void exec_set(transport_client& client, plugin& plugin, const nlohmann::json& args)
-{
-    assert(args.count("value") > 0);
-
-    auto var = args.find("variable");
-    auto value = args.find("value");
-
-    if (var == args.end() || !var->is_string())
-        throw irccd_error(irccd_error::error::incomplete_message);
-    if (value == args.end() || !value->is_string())
-        throw irccd_error(irccd_error::error::incomplete_message);
-
-    auto config = plugin.config();
-
-    config[*var] = *value;
-    plugin.set_config(config);
-    client.success("plugin-config");
-}
-
-void exec_get(transport_client& client, plugin& plugin, const nlohmann::json& args)
-{
-    auto variables = nlohmann::json::object();
-    auto var = args.find("variable");
-
-    if (var != args.end() && var->is_string())
-        variables[var->get<std::string>()] = plugin.config()[*var];
-    else
-        for (const auto& pair : plugin.config())
-            variables[pair.first] = pair.second;
-
-    /*
-     * Don't put all variables into the response, put them into a sub property
-     * 'variables' instead.
-     *
-     * It's easier for the client to iterate over all.
-     */
-    client.send({
-        { "command",    "plugin-config" },
-        { "variables",  variables       }
-    });
-}
-
-nlohmann::json to_json(const rule& rule)
-{
-    auto join = [] (const auto& set) {
-        auto array = nlohmann::json::array();
-
-        for (const auto& entry : set)
-            array.push_back(entry);
-
-        return array;
-    };
-    auto str = [] (auto action) {
-        switch (action) {
-        case rule::action_type::accept:
-            return "accept";
-        default:
-            return "drop";
-        }
-    };
-
-    return {
-        { "servers",    join(rule.servers())    },
-        { "channels",   join(rule.channels())   },
-        { "plugins",    join(rule.plugins())    },
-        { "events",     join(rule.events())     },
-        { "action",     str(rule.action())      }
-    };
-}
-
-rule from_json(const nlohmann::json& json)
-{
-    auto toset = [] (auto object, auto name) {
-        rule::set result;
-
-        for (const auto& s : object[name])
-            if (s.is_string())
-                result.insert(s.template get<std::string>());
-
-        return result;
-    };
-    auto toaction = [] (auto object, auto name) {
-        auto v = object[name];
-
-        if (!v.is_string())
-            throw rule_error(rule_error::invalid_action);
-
-        auto s = v.template get<std::string>();
-        if (s == "accept")
-            return rule::action_type::accept;
-        if (s == "drop")
-            return rule::action_type::drop;
-
-        throw rule_error(rule_error::invalid_action);
-    };
-
-    return {
-        toset(json, "servers"),
-        toset(json, "channels"),
-        toset(json, "origins"),
-        toset(json, "plugins"),
-        toset(json, "events"),
-        toaction(json, "action")
-    };
-}
-
-unsigned get_rule_index(const nlohmann::json& json, const std::string& key = "index")
-{
-    auto index = json.find(key);
-
-    if (index == json.end() || !index->is_number_integer() || index->get<int>() < 0)
-        throw rule_error(rule_error::invalid_index);
-
-    return index->get<int>();
-}
-
-std::shared_ptr<server> get_server(irccd& daemon, const nlohmann::json& args)
-{
-    auto id = json_util::get_string(args, "server");
-
-    if (!string_util::is_identifier(id))
-        throw server_error(server_error::invalid_identifier, "");
-
-    auto server = daemon.servers().get(id);
-
-    if (!server)
-        throw server_error(server_error::not_found, id);
-
-    return server;
-}
-
-} // !namespace
-
-std::string plugin_config_command::get_name() const noexcept
-{
-    return "plugin-config";
-}
-
-void plugin_config_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    auto plugin = irccd.plugins().require(json_util::require_identifier(args, "plugin"));
-
-    if (args.count("value") > 0)
-        exec_set(client, *plugin, args);
-    else
-        exec_get(client, *plugin, args);
-}
-
-std::string plugin_info_command::get_name() const noexcept
-{
-    return "plugin-info";
-}
-
-void plugin_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    auto plugin = irccd.plugins().require(json_util::require_identifier(args, "plugin"));
-
-    client.send({
-        { "command",    "plugin-info"       },
-        { "author",     plugin->author()    },
-        { "license",    plugin->license()   },
-        { "summary",    plugin->summary()   },
-        { "version",    plugin->version()   }
-    });
-}
-
-std::string plugin_list_command::get_name() const noexcept
-{
-    return "plugin-list";
-}
-
-void plugin_list_command::exec(irccd& irccd, transport_client& client, const nlohmann::json&)
-{
-    auto list = nlohmann::json::array();
-
-    for (const auto& plugin : irccd.plugins().list())
-        list += plugin->name();
-
-    client.send({
-        { "command",    "plugin-list"   },
-        { "list",       list            }
-    });
-}
-
-std::string plugin_load_command::get_name() const noexcept
-{
-    return "plugin-load";
-}
-
-void plugin_load_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    irccd.plugins().load(json_util::require_identifier(args, "plugin"));
-    client.success("plugin-load");
-}
-
-std::string plugin_reload_command::get_name() const noexcept
-{
-    return "plugin-reload";
-}
-
-void plugin_reload_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    irccd.plugins().reload(json_util::require_identifier(args, "plugin"));
-    client.success("plugin-reload");
-}
-
-std::string plugin_unload_command::get_name() const noexcept
-{
-    return "plugin-unload";
-}
-
-void plugin_unload_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    irccd.plugins().unload(json_util::require_identifier(args, "plugin"));
-    client.success("plugin-unload");
-}
-
-std::string server_connect_command::get_name() const noexcept
-{
-    return "server-connect";
-}
-
-void server_connect_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    auto server = server_service::from_json(irccd.service(), args);
-
-    if (irccd.servers().has(server->name()))
-        throw server_error(server_error::error::already_exists, server->name());
-
-    irccd.servers().add(std::move(server));
-    client.success("server-connect");
-}
-
-std::string server_disconnect_command::get_name() const noexcept
-{
-    return "server-disconnect";
-}
-
-void server_disconnect_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    auto it = args.find("server");
-
-    if (it == args.end())
-        irccd.servers().clear();
-    else {
-        if (!it->is_string())
-            throw server_error(server_error::invalid_identifier, "");
-
-        auto name = it->get<std::string>();
-        auto s = irccd.servers().get(name);
-
-        if (!s)
-            throw server_error(server_error::not_found, name);
-
-        irccd.servers().remove(name);
-    }
-
-    client.success("server-disconnect");
-}
-
-std::string server_info_command::get_name() const noexcept
-{
-    return "server-invite";
-}
-
-void server_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    auto response = nlohmann::json::object();
-    auto server = get_server(irccd, args);
-
-    // General stuff.
-    response.push_back({"command", "server-info"});
-    response.push_back({"name", server->name()});
-    response.push_back({"host", server->host()});
-    response.push_back({"port", server->port()});
-    response.push_back({"nickname", server->nickname()});
-    response.push_back({"username", server->username()});
-    response.push_back({"realname", server->realname()});
-    response.push_back({"channels", server->channels()});
-
-    // Optional stuff.
-    if (server->flags() & server::ipv6)
-        response.push_back({"ipv6", true});
-    if (server->flags() & server::ssl)
-        response.push_back({"ssl", true});
-    if (server->flags() & server::ssl_verify)
-        response.push_back({"sslVerify", true});
-
-    client.send(response);
-}
-
-std::string server_invite_command::get_name() const noexcept
-{
-    return "server-invite";
-}
-
-void server_invite_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    auto server = get_server(irccd, args);
-    auto target = json_util::get_string(args, "target");
-    auto channel = json_util::get_string(args, "channel");
-
-    if (target.empty())
-        throw server_error(server_error::invalid_nickname, server->name());
-    if (channel.empty())
-        throw server_error(server_error::invalid_channel, server->name());
-
-    server->invite(target, channel);
-    client.success("server-invite");
-}
-
-std::string server_join_command::get_name() const noexcept
-{
-    return "server-join";
-}
-
-void server_join_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    auto server = get_server(irccd, args);
-    auto channel = json_util::get_string(args, "channel");
-    auto password = json_util::get_string(args, "password");
-
-    if (channel.empty())
-        throw server_error(server_error::invalid_channel, server->name());
-
-    server->join(channel, password);
-    client.success("server-join");
-}
-
-std::string server_kick_command::get_name() const noexcept
-{
-    return "server-kick";
-}
-
-void server_kick_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    auto server = get_server(irccd, args);
-    auto target = json_util::get_string(args, "target");
-    auto channel = json_util::get_string(args, "channel");
-    auto reason = json_util::get_string(args, "reason");
-
-    if (target.empty())
-        throw server_error(server_error::invalid_nickname, server->name());
-    if (channel.empty())
-        throw server_error(server_error::invalid_channel, server->name());
-
-    server->kick(target, channel, reason);
-    client.success("server-kick");
-}
-
-std::string server_list_command::get_name() const noexcept
-{
-    return "server-list";
-}
-
-void server_list_command::exec(irccd& irccd, transport_client& client, const nlohmann::json&)
-{
-    auto json = nlohmann::json::object();
-    auto list = nlohmann::json::array();
-
-    for (const auto& server : irccd.servers().servers())
-        list.push_back(server->name());
-
-    client.send({
-        { "command",    "server-list"   },
-        { "list",       std::move(list) }
-    });
-}
-
-std::string server_me_command::get_name() const noexcept
-{
-    return "server-me";
-}
-
-void server_me_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    auto server = get_server(irccd, args);
-    auto channel = json_util::get_string(args, "target");
-    auto message = json_util::get_string(args, "message");
-
-    if (channel.empty())
-        throw server_error(server_error::invalid_channel, server->name());
-
-    server->me(channel, message);
-    client.success("server-me");
-}
-
-std::string server_message_command::get_name() const noexcept
-{
-    return "server-message";
-}
-
-void server_message_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    auto server = get_server(irccd, args);
-    auto channel = json_util::get_string(args, "target");
-    auto message = json_util::get_string(args, "message");
-
-    if (channel.empty())
-        throw server_error(server_error::invalid_channel, server->name());
-
-    server->message(channel, message);
-    client.success("server-message");
-}
-
-std::string server_mode_command::get_name() const noexcept
-{
-    return "server-mode";
-}
-
-void server_mode_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    auto server = get_server(irccd, args);
-    auto channel = json_util::get_string(args, "channel");
-    auto mode = json_util::get_string(args, "mode");
-
-    if (channel.empty())
-        throw server_error(server_error::invalid_channel, server->name());
-    if (mode.empty())
-        throw server_error(server_error::invalid_mode, server->name());
-
-    auto limit = json_util::get_string(args, "limit");
-    auto user = json_util::get_string(args, "user");
-    auto mask = json_util::get_string(args, "mask");
-
-    server->mode(channel, mode, limit, user, mask);
-    client.success("server-mode");
-}
-
-std::string server_nick_command::get_name() const noexcept
-{
-    return "server-nick";
-}
-
-void server_nick_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    auto server = get_server(irccd, args);
-    auto nick = json_util::get_string(args, "nickname");
-
-    if (nick.empty())
-        throw server_error(server_error::invalid_nickname, server->name());
-
-    server->set_nickname(nick);
-    client.success("server-nick");
-}
-
-std::string server_notice_command::get_name() const noexcept
-{
-    return "server-notice";
-}
-
-void server_notice_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    auto server = get_server(irccd, args);
-    auto channel = json_util::get_string(args, "target");
-    auto message = json_util::get_string(args, "message");
-
-    if (channel.empty())
-        throw server_error(server_error::invalid_channel, server->name());
-
-    server->notice(channel, message);
-    client.success("server-notice");
-}
-
-std::string server_part_command::get_name() const noexcept
-{
-    return "server-part";
-}
-
-void server_part_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    auto server = get_server(irccd, args);
-    auto channel = json_util::get_string(args, "channel");
-    auto reason = json_util::get_string(args, "reason");
-
-    if (channel.empty())
-        throw server_error(server_error::invalid_channel, server->name());
-
-    server->part(channel, reason);
-    client.success("server-part");
-}
-
-std::string server_reconnect_command::get_name() const noexcept
-{
-    return "server-reconnect";
-}
-
-void server_reconnect_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    auto server = args.find("server");
-
-    if (server == args.end()) {
-        for (auto& server : irccd.servers().servers())
-            server->reconnect();
-    } else {
-        if (!server->is_string() || !string_util::is_identifier(server->get<std::string>()))
-            throw server_error(server_error::invalid_identifier, "");
-
-        auto name = server->get<std::string>();
-        auto s = irccd.servers().get(name);
-
-        if (!s)
-            throw server_error(server_error::not_found, name);
-
-        s->reconnect();
-    }
-
-    client.success("server-reconnect");
-}
-
-std::string server_topic_command::get_name() const noexcept
-{
-    return "server-topic";
-}
-
-void server_topic_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    auto server = get_server(irccd, args);
-    auto channel = json_util::get_string(args, "channel");
-    auto topic = json_util::get_string(args, "topic");
-
-    if (channel.empty())
-        throw server_error(server_error::invalid_channel, server->name());
-
-    server->topic(channel, topic);
-    client.success("server-topic");
-}
-
-std::string rule_edit_command::get_name() const noexcept
-{
-    return "rule-edit";
-}
-
-void rule_edit_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    static const auto updateset = [] (auto& set, auto args, const auto& key) {
-        for (const auto& v : args["remove-"s + key]) {
-            if (v.is_string())
-                set.erase(v.template get<std::string>());
-        }
-        for (const auto& v : args["add-"s + key]) {
-            if (v.is_string())
-                set.insert(v.template get<std::string>());
-        }
-    };
-
-    // Create a copy to avoid incomplete edition in case of errors.
-    auto index = get_rule_index(args);
-    auto rule = irccd.rules().require(index);
-
-    updateset(rule.channels(), args, "channels");
-    updateset(rule.events(), args, "events");
-    updateset(rule.plugins(), args, "plugins");
-    updateset(rule.servers(), args, "servers");
-
-    auto action = args.find("action");
-
-    if (action != args.end()) {
-        if (!action->is_string())
-            throw rule_error(rule_error::error::invalid_action);
-
-        if (action->get<std::string>() == "accept")
-            rule.set_action(rule::action_type::accept);
-        else if (action->get<std::string>() == "drop")
-            rule.set_action(rule::action_type::drop);
-        else
-            throw rule_error(rule_error::invalid_action);
-    }
-
-    // All done, sync the rule.
-    irccd.rules().require(index) = rule;
-    client.success("rule-edit");
-}
-
-std::string rule_list_command::get_name() const noexcept
-{
-    return "rule-list";
-}
-
-void rule_list_command::exec(irccd& irccd, transport_client& client, const nlohmann::json&)
-{
-    auto array = nlohmann::json::array();
-
-    for (const auto& rule : irccd.rules().list())
-        array.push_back(to_json(rule));
-
-    client.send({
-        { "command",    "rule-list"         },
-        { "list",       std::move(array)    }
-    });
-}
-
-std::string rule_info_command::get_name() const noexcept
-{
-    return "rule-info";
-}
-
-void rule_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    auto json = to_json(irccd.rules().require(get_rule_index(args)));
-
-    json.push_back({"command", "rule-info"});
-    client.send(std::move(json));
-}
-
-std::string rule_remove_command::get_name() const noexcept
-{
-    return "rule-remove";
-}
-
-void rule_remove_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    auto index = get_rule_index(args);
-
-    if (index >= irccd.rules().length())
-        throw rule_error(rule_error::invalid_index);
-
-    irccd.rules().remove(index);
-    client.success("rule-remove");
-}
-
-std::string rule_move_command::get_name() const noexcept
-{
-    return "rule-move";
-}
-
-void rule_move_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    auto from = get_rule_index(args, "from");
-    auto to = get_rule_index(args, "to");
-
-    /*
-     * Examples of moves
-     * --------------------------------------------------------------
-     *
-     * Before: [0] [1] [2]
-     *
-     * from = 0
-     * to   = 2
-     *
-     * After:  [1] [2] [0]
-     *
-     * --------------------------------------------------------------
-     *
-     * Before: [0] [1] [2]
-     *
-     * from = 2
-     * to   = 0
-     *
-     * After:  [2] [0] [1]
-     *
-     * --------------------------------------------------------------
-     *
-     * Before: [0] [1] [2]
-     *
-     * from = 0
-     * to   = 123
-     *
-     * After:  [1] [2] [0]
-     */
-
-    // Ignore dumb input.
-    if (from == to) {
-        client.success("rule-move");
-        return;
-    }
-
-    if (from >= irccd.rules().length())
-        throw rule_error(rule_error::error::invalid_index);
-
-    auto save = irccd.rules().list()[from];
-
-    irccd.rules().remove(from);
-    irccd.rules().insert(save, to > irccd.rules().length() ? irccd.rules().length() : to);
-    client.success("rule-move");
-}
-
-std::string rule_add_command::get_name() const noexcept
-{
-    return "rule-add";
-}
-
-void rule_add_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
-{
-    auto index = json_util::get_uint(args, "index", irccd.rules().length());
-    auto rule = from_json(args);
-
-    if (index > irccd.rules().length())
-        throw rule_error(rule_error::error::invalid_index);
-
-    irccd.rules().insert(rule, index);
-    client.success("rule-add");
-}
-
-} // !irccd
--- a/libirccd/irccd/daemon/command.hpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/libirccd/irccd/daemon/command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -26,10 +26,6 @@
 
 #include <irccd/sysconfig.hpp>
 
-#include <cassert>
-#include <map>
-#include <vector>
-
 #include "json.hpp"
 
 namespace irccd {
@@ -71,569 +67,6 @@
     virtual void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) = 0;
 };
 
-/**
- * \brief Implementation of plugin-config transport command.
- *
- * Replies:
- *
- *   - plugin_error::not_found
- */
-class plugin_config_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of plugin-info transport command.
- *
- * Replies:
- *
- *   - plugin_error::not_found
- */
-class plugin_info_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of plugin-list transport command.
- */
-class plugin_list_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of plugin-load transport command.
- *
- * Replies:
- *
- *   - plugin_error::already_exists
- *   - plugin_error::not_found
- *   - plugin_error::exec_error
- */
-class plugin_load_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of plugin-reload transport command.
- *
- * Replies:
- *
- *   - plugin_error::not_found
- *   - plugin_error::exec_error
- */
-class plugin_reload_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of plugin-unload transport command.
- *
- * Replies:
- *
- *   - plugin_error::not_found
- *   - plugin_error::exec_error
- */
-class plugin_unload_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of server-connect transport command.
- *
- * Replies:
- *
- *   - server_error::already_exists,
- *   - server_error::invalid_hostname,
- *   - server_error::invalid_identifier,
- *   - server_error::invalid_port_number,
- *   - server_error::ssl_disabled.
- */
-class server_connect_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of server-disconnect transport command.
- *
- * Replies:
- *
- *   - server_error::invalid_identifier,
- *   - server_error::not_found.
- */
-class server_disconnect_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of server-info transport command.
- *
- * Replies:
- *
- *   - server_error::invalid_identifier,
- *   - server_error::not_found.
- */
-class server_info_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of server-invite transport command.
- *
- * Replies:
- *
- *   - server_error::invalid_channel,
- *   - server_error::invalid_identifier,
- *   - server_error::invalid_nickname,
- *   - server_error::not_found.
- */
-class server_invite_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of server-join transport command.
- *
- * Replies:
- *
- *   - server_error::invalid_channel,
- *   - server_error::invalid_identifier,
- *   - server_error::not_found.
- */
-class server_join_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of server-kick transport command.
- *
- * Replies:
- *
- *   - server_error::invalid_channel,
- *   - server_error::invalid_identifier,
- *   - server_error::invalid_nickname,
- *   - server_error::not_found.
- */
-class server_kick_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of server-list transport command.
- */
-class server_list_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of server-me transport command.
- *
- * Replies:
- *
- *   - server_error::invalid_channel,
- *   - server_error::invalid_identifier,
- *   - server_error::not_found.
- */
-class server_me_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of server-message transport command.
- *
- * Replies:
- *
- *   - server_error::invalid_channel,
- *   - server_error::invalid_identifier,
- *   - server_error::not_found.
- */
-class server_message_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of server-mode transport command.
- *
- * Replies:
- *
- *   - server_error::invalid_channel,
- *   - server_error::invalid_identifier,
- *   - server_error::invalid_mode,
- *   - server_error::not_found.
- */
-class server_mode_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of server-nick transport command.
- *
- * Replies:
- *
- *   - server_error::invalid_identifier,
- *   - server_error::invalid_nickname,
- *   - server_error::not_found.
- */
-class server_nick_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of server-notice transport command.
- *
- * Replies:
- *
- *   - server_error::invalid_channel,
- *   - server_error::invalid_identifier,
- *   - server_error::not_found.
- */
-class server_notice_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of server-part transport command.
- *
- * Replies:
- *
- *   - server_error::invalid_channel,
- *   - server_error::invalid_identifier,
- *   - server_error::not_found.
- */
-class server_part_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of server-reconnect transport command.
- *
- * Replies:
- *
- *   - server_error::invalid_identifier,
- *   - server_error::not_found.
- */
-class server_reconnect_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of server-topic transport command.
- *
- * Replies:
- *
- *   - server_error::invalid_channel,
- *   - server_error::invalid_identifier,
- *   - server_error::not_found.
- */
-class server_topic_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of rule-edit transport command.
- *
- * Replies:
- *
- *   - rule_error::invalid_index
- *   - rule_error::invalid_action
- */
-class rule_edit_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of rule-list transport command.
- */
-class rule_list_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of rule-info transport command.
- *
- * Replies:
- *
- *   - rule_error::invalid_index
- */
-class rule_info_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of rule-remove transport command.
- *
- * Replies:
- *
- *   - rule_error::invalid_index
- */
-class rule_remove_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of rule-move transport command.
- *
- * Replies:
- *
- *   - rule_error::invalid_index
- */
-class rule_move_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
-/**
- * \brief Implementation of rule-add transport command.
- *
- * Replies:
- *
- *   - rule_error::invalid_action
- */
-class rule_add_command : public command {
-public:
-    /**
-     * \copydoc command::get_name
-     */
-    std::string get_name() const noexcept override;
-
-    /**
-     * \copydoc command::exec
-     */
-    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
-};
-
 } // !irccd
 
 #endif // !IRCCD_DAEMON_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/plugin_config_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,89 @@
+/*
+ * plugin_config_command.cpp -- implementation of plugin-config transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/json_util.hpp>
+
+#include "irccd.hpp"
+#include "plugin_config_command.hpp"
+#include "plugin_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+namespace {
+
+void exec_set(transport_client& client, plugin& plugin, const nlohmann::json& args)
+{
+    assert(args.count("value") > 0);
+
+    auto var = args.find("variable");
+    auto value = args.find("value");
+
+    if (var == args.end() || !var->is_string())
+        throw irccd_error(irccd_error::error::incomplete_message);
+    if (value == args.end() || !value->is_string())
+        throw irccd_error(irccd_error::error::incomplete_message);
+
+    auto config = plugin.config();
+
+    config[*var] = *value;
+    plugin.set_config(config);
+    client.success("plugin-config");
+}
+
+void exec_get(transport_client& client, plugin& plugin, const nlohmann::json& args)
+{
+    auto variables = nlohmann::json::object();
+    auto var = args.find("variable");
+
+    if (var != args.end() && var->is_string())
+        variables[var->get<std::string>()] = plugin.config()[*var];
+    else
+        for (const auto& pair : plugin.config())
+            variables[pair.first] = pair.second;
+
+    /*
+     * Don't put all variables into the response, put them into a sub property
+     * 'variables' instead.
+     *
+     * It's easier for the client to iterate over all.
+     */
+    client.send({
+        { "command",    "plugin-config" },
+        { "variables",  variables       }
+    });
+}
+
+} // !namespace
+
+std::string plugin_config_command::get_name() const noexcept
+{
+    return "plugin-config";
+}
+
+void plugin_config_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    auto plugin = irccd.plugins().require(json_util::require_identifier(args, "plugin"));
+
+    if (args.count("value") > 0)
+        exec_set(client, *plugin, args);
+    else
+        exec_get(client, *plugin, args);
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/plugin_config_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * plugin_config_command.hpp -- implementation of plugin-config transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_PLUGIN_CONFIG_COMMAND_HPP
+#define IRCCD_DAEMON_PLUGIN_CONFIG_COMMAND_HPP
+
+/**
+ * \file plugin_config_command.hpp
+ * \brief Implementation of plugin-config transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of plugin-config transport command.
+ *
+ * Replies:
+ *
+ *   - plugin_error::not_found
+ */
+class plugin_config_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_PLUGIN_CONFIG_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/plugin_info_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,46 @@
+/*
+ * plugin_info_command.cpp -- implementation of plugin-info transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/json_util.hpp>
+
+#include "irccd.hpp"
+#include "plugin_info_command.hpp"
+#include "plugin_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string plugin_info_command::get_name() const noexcept
+{
+    return "plugin-info";
+}
+
+void plugin_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    auto plugin = irccd.plugins().require(json_util::require_identifier(args, "plugin"));
+
+    client.send({
+        { "command",    "plugin-info"       },
+        { "author",     plugin->author()    },
+        { "license",    plugin->license()   },
+        { "summary",    plugin->summary()   },
+        { "version",    plugin->version()   }
+    });
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/plugin_info_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * plugin_info_command.hpp -- implementation of plugin-info transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_PLUGIN_INFO_COMMAND_HPP
+#define IRCCD_DAEMON_PLUGIN_INFO_COMMAND_HPP
+
+/**
+ * \file plugin_info_command.hpp
+ * \brief Implementation of plugin-info transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of plugin-info transport command.
+ *
+ * Replies:
+ *
+ *   - plugin_error::not_found
+ */
+class plugin_info_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_PLUGIN_INFO_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/plugin_list_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,44 @@
+/*
+ * plugin_list_command.cpp -- implementation of plugin-list transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "irccd.hpp"
+#include "plugin_list_command.hpp"
+#include "plugin_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string plugin_list_command::get_name() const noexcept
+{
+    return "plugin-list";
+}
+
+void plugin_list_command::exec(irccd& irccd, transport_client& client, const nlohmann::json&)
+{
+    auto list = nlohmann::json::array();
+
+    for (const auto& plugin : irccd.plugins().list())
+        list += plugin->name();
+
+    client.send({
+        { "command",    "plugin-list"   },
+        { "list",       list            }
+    });
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/plugin_list_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,49 @@
+/*
+ * plugin_list_command.hpp -- implementation of plugin-list transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * \file plugin_list_command.hpp
+ * \brief Implementation of plugin-list transport command.
+ */
+
+#ifndef IRCCD_DAEMON_PLUGIN_LIST_COMMAND_HPP
+#define IRCCD_DAEMON_PLUGIN_LIST_COMMAND_HPP
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of plugin-list transport command.
+ */
+class plugin_list_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_PLUGIN_LIST_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/plugin_load_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,39 @@
+/*
+ * plugin_load_command.cpp -- implementation of plugin-load transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/json_util.hpp>
+
+#include "irccd.hpp"
+#include "plugin_load_command.hpp"
+#include "plugin_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string plugin_load_command::get_name() const noexcept
+{
+    return "plugin-load";
+}
+
+void plugin_load_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    irccd.plugins().load(json_util::require_identifier(args, "plugin"));
+    client.success("plugin-load");
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/plugin_load_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,55 @@
+/*
+ * plugin_load_command.hpp -- implementation of plugin-load transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_PLUGIN_LOAD_COMMAND_HPP
+#define IRCCD_DAEMON_PLUGIN_LOAD_COMMAND_HPP
+
+/**
+ * \file plugin_load_command.hpp
+ * \brief Implementation of plugin-load transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of plugin-load transport command.
+ *
+ * Replies:
+ *
+ *   - plugin_error::already_exists
+ *   - plugin_error::not_found
+ *   - plugin_error::exec_error
+ */
+class plugin_load_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_PLUGIN_LOAD_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/plugin_reload_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,39 @@
+/*
+ * plugin_reload_command.cpp -- implementation of plugin-reload transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/json_util.hpp>
+
+#include "irccd.hpp"
+#include "plugin_reload_command.hpp"
+#include "plugin_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string plugin_reload_command::get_name() const noexcept
+{
+    return "plugin-reload";
+}
+
+void plugin_reload_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    irccd.plugins().reload(json_util::require_identifier(args, "plugin"));
+    client.success("plugin-reload");
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/plugin_reload_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,54 @@
+/*
+ * plugin_reload_command.hpp -- implementation of plugin-reload transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_PLUGIN_RELOAD_COMMAND_HPP
+#define IRCCD_DAEMON_PLUGIN_RELOAD_COMMAND_HPP
+
+/**
+ * \file plugin_reload_command.hpp
+ * \brief Implementation of plugin-reload transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of plugin-reload transport command.
+ *
+ * Replies:
+ *
+ *   - plugin_error::not_found
+ *   - plugin_error::exec_error
+ */
+class plugin_reload_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_PLUGIN_RELOAD_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/plugin_unload_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,39 @@
+/*
+ * plugin_unload_command.cpp -- implementation of plugin-unload transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/json_util.hpp>
+
+#include "irccd.hpp"
+#include "plugin_service.hpp"
+#include "plugin_unload_command.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string plugin_unload_command::get_name() const noexcept
+{
+    return "plugin-unload";
+}
+
+void plugin_unload_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    irccd.plugins().unload(json_util::require_identifier(args, "plugin"));
+    client.success("plugin-unload");
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/plugin_unload_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,54 @@
+/*
+ * plugin_unload_command.hpp -- implementation of plugin-unload transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_PLUGIN_UNLOAD_COMMAND_HPP
+#define IRCCD_DAEMON_PLUGIN_UNLOAD_COMMAND_HPP
+
+/**
+ * \file plugin_unload_command.hpp
+ * \brief Implementation of plugin-unload transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of plugin-unload transport command.
+ *
+ * Replies:
+ *
+ *   - plugin_error::not_found
+ *   - plugin_error::exec_error
+ */
+class plugin_unload_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_PLUGIN_UNLOAD_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/rule_add_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,45 @@
+/*
+ * rule_add_command.cpp -- implementation of rule-add transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/json_util.hpp>
+
+#include "irccd.hpp"
+#include "rule_add_command.hpp"
+#include "rule_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string rule_add_command::get_name() const noexcept
+{
+    return "rule-add";
+}
+
+void rule_add_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    auto index = json_util::get_uint(args, "index", irccd.rules().length());
+    auto rule = rule_service::from_json(args);
+
+    if (index > irccd.rules().length())
+        throw rule_error(rule_error::error::invalid_index);
+
+    irccd.rules().insert(rule, index);
+    client.success("rule-add");
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/rule_add_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * rule_add_command.hpp -- implementation of rule-add transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_RULE_ADD_COMMAND_HPP
+#define IRCCD_DAEMON_RULE_ADD_COMMAND_HPP
+
+/**
+ * \file rule_add_command.hpp
+ * \brief Implementation of rule-add transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of rule-add transport command.
+ *
+ * Replies:
+ *
+ *   - rule_error::invalid_action
+ */
+class rule_add_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_RULE_ADD_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/rule_edit_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,74 @@
+/*
+ * rule_edit_command.cpp -- implementation of rule-edit transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "irccd.hpp"
+#include "rule_edit_command.hpp"
+#include "rule_service.hpp"
+#include "transport_client.hpp"
+
+using namespace std::string_literals;
+
+namespace irccd {
+
+std::string rule_edit_command::get_name() const noexcept
+{
+    return "rule-edit";
+}
+
+void rule_edit_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    static const auto updateset = [] (auto& set, auto args, const auto& key) {
+        for (const auto& v : args["remove-"s + key]) {
+            if (v.is_string())
+                set.erase(v.template get<std::string>());
+        }
+        for (const auto& v : args["add-"s + key]) {
+            if (v.is_string())
+                set.insert(v.template get<std::string>());
+        }
+    };
+
+    // Create a copy to avoid incomplete edition in case of errors.
+    auto index = irccd.rules().get_index(args);
+    auto rule = irccd.rules().require(index);
+
+    updateset(rule.channels(), args, "channels");
+    updateset(rule.events(), args, "events");
+    updateset(rule.plugins(), args, "plugins");
+    updateset(rule.servers(), args, "servers");
+
+    auto action = args.find("action");
+
+    if (action != args.end()) {
+        if (!action->is_string())
+            throw rule_error(rule_error::error::invalid_action);
+
+        if (action->get<std::string>() == "accept")
+            rule.set_action(rule::action_type::accept);
+        else if (action->get<std::string>() == "drop")
+            rule.set_action(rule::action_type::drop);
+        else
+            throw rule_error(rule_error::invalid_action);
+    }
+
+    // All done, sync the rule.
+    irccd.rules().require(index) = rule;
+    client.success("rule-edit");
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/rule_edit_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,54 @@
+/*
+ * rule_edit_command.hpp -- implementation of rule-edit transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_RULE_EDIT_COMMAND_HPP
+#define IRCCD_DAEMON_RULE_EDIT_COMMAND_HPP
+
+/**
+ * \file rule_edit_command.hpp
+ * \brief Implementation of rule-edit transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of rule-edit transport command.
+ *
+ * Replies:
+ *
+ *   - rule_error::invalid_index
+ *   - rule_error::invalid_action
+ */
+class rule_edit_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_RULE_EDIT_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/rule_info_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,39 @@
+/*
+ * rule_info_command.cpp -- implementation of rule-info transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "irccd.hpp"
+#include "rule_info_command.hpp"
+#include "rule_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string rule_info_command::get_name() const noexcept
+{
+    return "rule-info";
+}
+
+void rule_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    auto json = rule_service::to_json(irccd.rules().require(irccd.rules().get_index(args)));
+
+    json.push_back({"command", "rule-info"});
+    client.send(std::move(json));
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/rule_info_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * rule_info_command.hpp -- implementation of rule-info transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_RULE_INFO_COMMAND_HPP
+#define IRCCD_DAEMON_RULE_INFO_COMMAND_HPP
+
+/**
+ * \file rule_info_command.hpp
+ * \brief Implementation of rule-info transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of rule-info transport command.
+ *
+ * Replies:
+ *
+ *   - rule_error::invalid_index
+ */
+class rule_info_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_RULE_INFO_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/rule_list_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,44 @@
+/*
+ * rule_list_command.cpp -- implementation of rule-list transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "irccd.hpp"
+#include "rule_list_command.hpp"
+#include "rule_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string rule_list_command::get_name() const noexcept
+{
+    return "rule-list";
+}
+
+void rule_list_command::exec(irccd& irccd, transport_client& client, const nlohmann::json&)
+{
+    auto array = nlohmann::json::array();
+
+    for (const auto& rule : irccd.rules().list())
+        array.push_back(rule_service::to_json(rule));
+
+    client.send({
+        { "command",    "rule-list"         },
+        { "list",       std::move(array)    }
+    });
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/rule_list_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,49 @@
+/*
+ * rule_list_command.hpp -- implementation of rule-list transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_RULE_LIST_COMMAND_HPP
+#define IRCCD_DAEMON_RULE_LIST_COMMAND_HPP
+
+/**
+ * \file rule_list_command.hpp
+ * \brief Implementation of rule-list transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of rule-list transport command.
+ */
+class rule_list_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_RULE_ADD_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/rule_move_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,82 @@
+/*
+ * rule_move_command.cpp -- implementation of rule-move transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "irccd.hpp"
+#include "rule_move_command.hpp"
+#include "rule_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string rule_move_command::get_name() const noexcept
+{
+    return "rule-move";
+}
+
+void rule_move_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    auto from = rule_service::get_index(args, "from");
+    auto to = rule_service::get_index(args, "to");
+
+    /*
+     * Examples of moves
+     * --------------------------------------------------------------
+     *
+     * Before: [0] [1] [2]
+     *
+     * from = 0
+     * to   = 2
+     *
+     * After:  [1] [2] [0]
+     *
+     * --------------------------------------------------------------
+     *
+     * Before: [0] [1] [2]
+     *
+     * from = 2
+     * to   = 0
+     *
+     * After:  [2] [0] [1]
+     *
+     * --------------------------------------------------------------
+     *
+     * Before: [0] [1] [2]
+     *
+     * from = 0
+     * to   = 123
+     *
+     * After:  [1] [2] [0]
+     */
+
+    // Ignore dumb input.
+    if (from == to) {
+        client.success("rule-move");
+        return;
+    }
+
+    if (from >= irccd.rules().length())
+        throw rule_error(rule_error::error::invalid_index);
+
+    auto save = irccd.rules().list()[from];
+
+    irccd.rules().remove(from);
+    irccd.rules().insert(save, to > irccd.rules().length() ? irccd.rules().length() : to);
+    client.success("rule-move");
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/rule_move_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * rule_move_command.hpp -- implementation of rule-move transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_RULE_MOVE_COMMAND_HPP
+#define IRCCD_DAEMON_RULE_MOVE_COMMAND_HPP
+
+/**
+ * \file rule_move_command.hpp
+ * \brief Implementation of rule-move transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of rule-move transport command.
+ *
+ * Replies:
+ *
+ *   - rule_error::invalid_index
+ */
+class rule_move_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_RULE_MOVE_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/rule_remove_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,42 @@
+/*
+ * rule_remove_command.cpp -- implementation of rule-remove transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "irccd.hpp"
+#include "rule_remove_command.hpp"
+#include "rule_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string rule_remove_command::get_name() const noexcept
+{
+    return "rule-remove";
+}
+
+void rule_remove_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    auto index = rule_service::get_index(args);
+
+    if (index >= irccd.rules().length())
+        throw rule_error(rule_error::invalid_index);
+
+    irccd.rules().remove(index);
+    client.success("rule-remove");
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/rule_remove_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * rule_remove_command.hpp -- implementation of rule-remove transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_RULE_REMOVE_COMMAND_HPP
+#define IRCCD_DAEMON_RULE_REMOVE_COMMAND_HPP
+
+/**
+ * \file rule_remove_command.hpp
+ * \brief Implementation of rule-remove transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of rule-remove transport command.
+ *
+ * Replies:
+ *
+ *   - rule_error::invalid_index
+ */
+class rule_remove_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_RULE_REMOVE_COMMAND_HPP
--- a/libirccd/irccd/daemon/rule_service.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/libirccd/irccd/daemon/rule_service.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -77,6 +77,80 @@
 
 } // !namespace
 
+rule rule_service::from_json(const nlohmann::json& json)
+{
+    auto toset = [] (auto object, auto name) {
+        rule::set result;
+
+        for (const auto& s : object[name])
+            if (s.is_string())
+                result.insert(s.template get<std::string>());
+
+        return result;
+    };
+    auto toaction = [] (auto object, auto name) {
+        auto v = object[name];
+
+        if (!v.is_string())
+            throw rule_error(rule_error::invalid_action);
+
+        auto s = v.template get<std::string>();
+        if (s == "accept")
+            return rule::action_type::accept;
+        if (s == "drop")
+            return rule::action_type::drop;
+
+        throw rule_error(rule_error::invalid_action);
+    };
+
+    return {
+        toset(json, "servers"),
+        toset(json, "channels"),
+        toset(json, "origins"),
+        toset(json, "plugins"),
+        toset(json, "events"),
+        toaction(json, "action")
+    };
+}
+
+unsigned rule_service::get_index(const nlohmann::json& json, const std::string& key)
+{
+    auto index = json.find(key);
+
+    if (index == json.end() || !index->is_number_integer() || index->get<int>() < 0)
+        throw rule_error(rule_error::invalid_index);
+
+    return index->get<int>();
+}
+
+nlohmann::json rule_service::to_json(const rule& rule)
+{
+    auto join = [] (const auto& set) {
+        auto array = nlohmann::json::array();
+
+        for (const auto& entry : set)
+            array.push_back(entry);
+
+        return array;
+    };
+    auto str = [] (auto action) {
+        switch (action) {
+        case rule::action_type::accept:
+            return "accept";
+        default:
+            return "drop";
+        }
+    };
+
+    return {
+        { "servers",    join(rule.servers())    },
+        { "channels",   join(rule.channels())   },
+        { "plugins",    join(rule.plugins())    },
+        { "events",     join(rule.events())     },
+        { "action",     str(rule.action())      }
+    };
+}
+
 rule_service::rule_service(irccd &irccd)
     : irccd_(irccd)
 {
--- a/libirccd/irccd/daemon/rule_service.hpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/libirccd/irccd/daemon/rule_service.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -26,6 +26,8 @@
 
 #include <vector>
 
+#include <json.hpp>
+
 #include "rule.hpp"
 
 namespace irccd {
@@ -44,6 +46,37 @@
 
 public:
     /**
+     * Load a rule from a JSON object.
+     *
+     * For possible use in transport commands or Javascript API.
+     *
+     * \pre json.is_object()
+     * \param json the JSON object
+     * \return the new rule
+     * \throw rule_error on errors
+     */
+    static rule from_json(const nlohmann::json& json);
+
+    /**
+     * Helper to get rule index in a JSON object.
+     *
+     * \pre json.is_object()
+     * \param json the JSON object
+     * \param key the index property
+     * \return the index
+     * \throw rule_error on errors
+     */
+    static unsigned get_index(const nlohmann::json& json, const std::string& key = "index");
+
+    /**
+     * Convert a rule into a JSON object.
+     *
+     * \param rule the rule
+     * \throw the JSON representation
+     */
+    static nlohmann::json to_json(const rule& rule);
+
+    /**
      * Create the rule service.
      */
     rule_service(irccd& instance);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_connect_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,42 @@
+/*
+ * server_connect_command.cpp -- implementation of server-connect transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "irccd.hpp"
+#include "server_connect_command.hpp"
+#include "server_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string server_connect_command::get_name() const noexcept
+{
+    return "server-connect";
+}
+
+void server_connect_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    auto server = server_service::from_json(irccd.service(), args);
+
+    if (irccd.servers().has(server->name()))
+        throw server_error(server_error::error::already_exists, server->name());
+
+    irccd.servers().add(std::move(server));
+    client.success("server-connect");
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_connect_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,57 @@
+/*
+ * server_connect_command.hpp -- implementation of server-connect transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_SERVER_CONNECT_COMMAND_HPP
+#define IRCCD_DAEMON_SERVER_CONNECT_COMMAND_HPP
+
+/**
+ * \file server_connect_command.hpp
+ * \brief Implementation of server-connect transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of server-connect transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::already_exists,
+ *   - server_error::invalid_hostname,
+ *   - server_error::invalid_identifier,
+ *   - server_error::invalid_port_number,
+ *   - server_error::ssl_disabled.
+ */
+class server_connect_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_SERVER_CONNECT_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_disconnect_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * server_disconnect_command.cpp -- implementation of server-disconnect transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "irccd.hpp"
+#include "server_disconnect_command.hpp"
+#include "server_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string server_disconnect_command::get_name() const noexcept
+{
+    return "server-disconnect";
+}
+
+void server_disconnect_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    auto it = args.find("server");
+
+    if (it == args.end())
+        irccd.servers().clear();
+    else {
+        if (!it->is_string())
+            throw server_error(server_error::invalid_identifier, "");
+
+        auto name = it->get<std::string>();
+        auto s = irccd.servers().get(name);
+
+        if (!s)
+            throw server_error(server_error::not_found, name);
+
+        irccd.servers().remove(name);
+    }
+
+    client.success("server-disconnect");
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_disconnect_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,54 @@
+/*
+ * server_disconnect_command.hpp -- implementation of server-disconnect transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_SERVER_DISCONNECT_COMMAND_HPP
+#define IRCCD_DAEMON_SERVER_DISCONNECT_COMMAND_HPP
+
+/**
+ * \file server_disconnect_command.hpp
+ * \brief Implementation of server-disconnect transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of server-disconnect transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_identifier,
+ *   - server_error::not_found.
+ */
+class server_disconnect_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_SERVER_DISCONNECT_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_info_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,57 @@
+/*
+ * server_info_command.cpp -- implementation of server-info transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "irccd.hpp"
+#include "server_info_command.hpp"
+#include "server_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string server_info_command::get_name() const noexcept
+{
+    return "server-info";
+}
+
+void server_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    auto response = nlohmann::json::object();
+    auto server = irccd.servers().require(args);
+
+    // General stuff.
+    response.push_back({"command", "server-info"});
+    response.push_back({"name", server->name()});
+    response.push_back({"host", server->host()});
+    response.push_back({"port", server->port()});
+    response.push_back({"nickname", server->nickname()});
+    response.push_back({"username", server->username()});
+    response.push_back({"realname", server->realname()});
+    response.push_back({"channels", server->channels()});
+
+    // Optional stuff.
+    if (server->flags() & server::ipv6)
+        response.push_back({"ipv6", true});
+    if (server->flags() & server::ssl)
+        response.push_back({"ssl", true});
+    if (server->flags() & server::ssl_verify)
+        response.push_back({"sslVerify", true});
+
+    client.send(response);
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_info_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,54 @@
+/*
+ * server_info_command.hpp -- implementation of server-info transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_SERVER_INFO_COMMAND_HPP
+#define IRCCD_DAEMON_SERVER_INFO_COMMAND_HPP
+
+/**
+ * \file server_info_command.hpp
+ * \brief Implementation of server-info transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of server-info transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_identifier,
+ *   - server_error::not_found.
+ */
+class server_info_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_SERVER_INFO_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_invite_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,48 @@
+/*
+ * server_invite_command.cpp -- implementation of server-invite transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/json_util.hpp>
+
+#include "irccd.hpp"
+#include "server_invite_command.hpp"
+#include "server_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string server_invite_command::get_name() const noexcept
+{
+    return "server-invite";
+}
+
+void server_invite_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    auto server = irccd.servers().require(args);
+    auto target = json_util::get_string(args, "target");
+    auto channel = json_util::get_string(args, "channel");
+
+    if (target.empty())
+        throw server_error(server_error::invalid_nickname, server->name());
+    if (channel.empty())
+        throw server_error(server_error::invalid_channel, server->name());
+
+    server->invite(target, channel);
+    client.success("server-invite");
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_invite_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,56 @@
+/*
+ * server_invite_command.hpp -- implementation of server-invite transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_SERVER_INVITE_COMMAND_HPP
+#define IRCCD_DAEMON_SERVER_INVITE_COMMAND_HPP
+
+/**
+ * \file server_invite_command.hpp
+ * \brief Implementation of server-invite transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of server-invite transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_channel,
+ *   - server_error::invalid_identifier,
+ *   - server_error::invalid_nickname,
+ *   - server_error::not_found.
+ */
+class server_invite_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_SERVER_INVITE_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_join_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,46 @@
+/*
+ * server_join_command.cpp -- implementation of server-join transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/json_util.hpp>
+
+#include "irccd.hpp"
+#include "server_join_command.hpp"
+#include "server_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string server_join_command::get_name() const noexcept
+{
+    return "server-join";
+}
+
+void server_join_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    auto server = irccd.servers().require(args);
+    auto channel = json_util::get_string(args, "channel");
+    auto password = json_util::get_string(args, "password");
+
+    if (channel.empty())
+        throw server_error(server_error::invalid_channel, server->name());
+
+    server->join(channel, password);
+    client.success("server-join");
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_join_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,55 @@
+/*
+ * server_join_command.hpp -- implementation of server-join transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_SERVER_JOIN_COMMAND_HPP
+#define IRCCD_DAEMON_SERVER_JOIN_COMMAND_HPP
+
+/**
+ * \file server_join_command.hpp
+ * \brief Implementation of server-join transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of server-join transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_channel,
+ *   - server_error::invalid_identifier,
+ *   - server_error::not_found.
+ */
+class server_join_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_SERVER_JOIN_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_kick_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,49 @@
+/*
+ * server_kick_command.cpp -- implementation of server-kick transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/json_util.hpp>
+
+#include "irccd.hpp"
+#include "server_kick_command.hpp"
+#include "server_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string server_kick_command::get_name() const noexcept
+{
+    return "server-kick";
+}
+
+void server_kick_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    auto server = irccd.servers().require(args);
+    auto target = json_util::get_string(args, "target");
+    auto channel = json_util::get_string(args, "channel");
+    auto reason = json_util::get_string(args, "reason");
+
+    if (target.empty())
+        throw server_error(server_error::invalid_nickname, server->name());
+    if (channel.empty())
+        throw server_error(server_error::invalid_channel, server->name());
+
+    server->kick(target, channel, reason);
+    client.success("server-kick");
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_kick_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,56 @@
+/*
+ * server_kick_command.hpp -- implementation of server-kick transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_SERVER_KICK_COMMAND_HPP
+#define IRCCD_DAEMON_SERVER_KICK_COMMAND_HPP
+
+/**
+ * \file server_kick_command.hpp
+ * \brief Implementation of server-kick transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of server-kick transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_channel,
+ *   - server_error::invalid_identifier,
+ *   - server_error::invalid_nickname,
+ *   - server_error::not_found.
+ */
+class server_kick_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_SERVER_KICK_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_list_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,45 @@
+/*
+ * server_list_command.cpp -- implementation of server-list transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "irccd.hpp"
+#include "server_list_command.hpp"
+#include "server_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string server_list_command::get_name() const noexcept
+{
+    return "server-list";
+}
+
+void server_list_command::exec(irccd& irccd, transport_client& client, const nlohmann::json&)
+{
+    auto json = nlohmann::json::object();
+    auto list = nlohmann::json::array();
+
+    for (const auto& server : irccd.servers().servers())
+        list.push_back(server->name());
+
+    client.send({
+        { "command",    "server-list"   },
+        { "list",       std::move(list) }
+    });
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_list_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,49 @@
+/*
+ * server_list_command.hpp -- implementation of server-list transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_SERVER_LIST_COMMAND_HPP
+#define IRCCD_DAEMON_SERVER_LIST_COMMAND_HPP
+
+/**
+ * \file server_list_command.hpp
+ * \brief Implementation of server-list transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of server-list transport command.
+ */
+class server_list_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_SERVER_LIST_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_me_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,46 @@
+/*
+ * server_me_command.cpp -- implementation of server-me transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/json_util.hpp>
+
+#include "irccd.hpp"
+#include "server_me_command.hpp"
+#include "server_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string server_me_command::get_name() const noexcept
+{
+    return "server-me";
+}
+
+void server_me_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    auto server = irccd.servers().require(args);
+    auto channel = json_util::get_string(args, "target");
+    auto message = json_util::get_string(args, "message");
+
+    if (channel.empty())
+        throw server_error(server_error::invalid_channel, server->name());
+
+    server->me(channel, message);
+    client.success("server-me");
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_me_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,55 @@
+/*
+ * server_me_command.hpp -- implementation of server-me transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_SERVER_ME_COMMAND_HPP
+#define IRCCD_DAEMON_SERVER_ME_COMMAND_HPP
+
+/**
+ * \file server_me_command.hpp
+ * \brief Implementation of server-me transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of server-me transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_channel,
+ *   - server_error::invalid_identifier,
+ *   - server_error::not_found.
+ */
+class server_me_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_SERVER_ME_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_message_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,46 @@
+/*
+ * server_message_command.cpp -- implementation of server-message transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/json_util.hpp>
+
+#include "irccd.hpp"
+#include "server_message_command.hpp"
+#include "server_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string server_message_command::get_name() const noexcept
+{
+    return "server-message";
+}
+
+void server_message_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    auto server = irccd.servers().require(args);
+    auto channel = json_util::get_string(args, "target");
+    auto message = json_util::get_string(args, "message");
+
+    if (channel.empty())
+        throw server_error(server_error::invalid_channel, server->name());
+
+    server->message(channel, message);
+    client.success("server-message");
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_message_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,55 @@
+/*
+ * server_message_command.hpp -- implementation of server-message transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_SERVER_MESSAGE_COMMAND_HPP
+#define IRCCD_DAEMON_SERVER_MESSAGE_COMMAND_HPP
+
+/**
+ * \file server_message_command.hpp
+ * \brief Implementation of server-message transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of server-message transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_channel,
+ *   - server_error::invalid_identifier,
+ *   - server_error::not_found.
+ */
+class server_message_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_SERVER_MESSAGE_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_mode_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,52 @@
+/*
+ * server_mode_command.cpp -- implementation of server-mode transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/json_util.hpp>
+
+#include "irccd.hpp"
+#include "server_mode_command.hpp"
+#include "server_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string server_mode_command::get_name() const noexcept
+{
+    return "server-mode";
+}
+
+void server_mode_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    auto server = irccd.servers().require(args);
+    auto channel = json_util::get_string(args, "channel");
+    auto mode = json_util::get_string(args, "mode");
+
+    if (channel.empty())
+        throw server_error(server_error::invalid_channel, server->name());
+    if (mode.empty())
+        throw server_error(server_error::invalid_mode, server->name());
+
+    auto limit = json_util::get_string(args, "limit");
+    auto user = json_util::get_string(args, "user");
+    auto mask = json_util::get_string(args, "mask");
+
+    server->mode(channel, mode, limit, user, mask);
+    client.success("server-mode");
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_mode_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,56 @@
+/*
+ * server_mode_command.hpp -- implementation of server-mode transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_SERVER_MODE_COMMAND_HPP
+#define IRCCD_DAEMON_SERVER_MODE_COMMAND_HPP
+
+/**
+ * \file server_mode_command.hpp
+ * \brief Implementation of server-mode transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of server-mode transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_channel,
+ *   - server_error::invalid_identifier,
+ *   - server_error::invalid_mode,
+ *   - server_error::not_found.
+ */
+class server_mode_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_SERVER_MODE_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_nick_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,45 @@
+/*
+ * server_nick_command.cpp -- implementation of server-nick transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/json_util.hpp>
+
+#include "irccd.hpp"
+#include "server_nick_command.hpp"
+#include "server_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string server_nick_command::get_name() const noexcept
+{
+    return "server-nick";
+}
+
+void server_nick_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    auto server = irccd.servers().require(args);
+    auto nick = json_util::get_string(args, "nickname");
+
+    if (nick.empty())
+        throw server_error(server_error::invalid_nickname, server->name());
+
+    server->set_nickname(nick);
+    client.success("server-nick");
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_nick_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,55 @@
+/*
+ * server_nick_command.hpp -- implementation of server-nick transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_SERVER_NICK_COMMAND_HPP
+#define IRCCD_DAEMON_SERVER_NICK_COMMAND_HPP
+
+/**
+ * \file server_nick_command.hpp
+ * \brief Implementation of server-nick transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of server-nick transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_identifier,
+ *   - server_error::invalid_nickname,
+ *   - server_error::not_found.
+ */
+class server_nick_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_SERVER_NICK_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_notice_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,46 @@
+/*
+ * server_notice_command.cpp -- implementation of server-notice transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/json_util.hpp>
+
+#include "irccd.hpp"
+#include "server_notice_command.hpp"
+#include "server_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string server_notice_command::get_name() const noexcept
+{
+    return "server-notice";
+}
+
+void server_notice_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    auto server = irccd.servers().require(args);
+    auto channel = json_util::get_string(args, "target");
+    auto message = json_util::get_string(args, "message");
+
+    if (channel.empty())
+        throw server_error(server_error::invalid_channel, server->name());
+
+    server->notice(channel, message);
+    client.success("server-notice");
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_notice_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,55 @@
+/*
+ * server_notice_command.hpp -- implementation of server-notice transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_SERVER_NOTICE_COMMAND_HPP
+#define IRCCD_DAEMON_SERVER_NOTICE_COMMAND_HPP
+
+/**
+ * \file server_notice_command.hpp
+ * \brief Implementation of server-notice transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of server-notice transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_channel,
+ *   - server_error::invalid_identifier,
+ *   - server_error::not_found.
+ */
+class server_notice_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_SERVER_NOTICE_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_part_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,46 @@
+/*
+ * server_part_command.cpp -- implementation of server-part transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/json_util.hpp>
+
+#include "irccd.hpp"
+#include "server_part_command.hpp"
+#include "server_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string server_part_command::get_name() const noexcept
+{
+    return "server-part";
+}
+
+void server_part_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    auto server = irccd.servers().require(args);
+    auto channel = json_util::get_string(args, "channel");
+    auto reason = json_util::get_string(args, "reason");
+
+    if (channel.empty())
+        throw server_error(server_error::invalid_channel, server->name());
+
+    server->part(channel, reason);
+    client.success("server-part");
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_part_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,55 @@
+/*
+ * server_part_command.hpp -- implementation of server-part transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_SERVER_PART_COMMAND_HPP
+#define IRCCD_DAEMON_SERVER_PART_COMMAND_HPP
+
+/**
+ * \file server_part_command.hpp
+ * \brief Implementation of server-part transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of server-part transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_channel,
+ *   - server_error::invalid_identifier,
+ *   - server_error::not_found.
+ */
+class server_part_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_SERVER_PART_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_reconnect_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,56 @@
+/*
+ * server_reconnect_command.cpp -- implementation of server-reconnect transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/string_util.hpp>
+
+#include "irccd.hpp"
+#include "server_reconnect_command.hpp"
+#include "server_service.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string server_reconnect_command::get_name() const noexcept
+{
+    return "server-reconnect";
+}
+
+void server_reconnect_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    auto server = args.find("server");
+
+    if (server == args.end()) {
+        for (auto& server : irccd.servers().servers())
+            server->reconnect();
+    } else {
+        if (!server->is_string() || !string_util::is_identifier(server->get<std::string>()))
+            throw server_error(server_error::invalid_identifier, "");
+
+        auto name = server->get<std::string>();
+        auto s = irccd.servers().get(name);
+
+        if (!s)
+            throw server_error(server_error::not_found, name);
+
+        s->reconnect();
+    }
+
+    client.success("server-reconnect");
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_reconnect_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,54 @@
+/*
+ * server_reconnect_command.hpp -- implementation of server-reconnect transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_SERVER_RECONNECT_COMMAND_HPP
+#define IRCCD_DAEMON_SERVER_RECONNECT_COMMAND_HPP
+
+/**
+ * \file server_reconnect_command.hpp
+ * \brief Implementation of server-reconnect transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of server-reconnect transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_identifier,
+ *   - server_error::not_found.
+ */
+class server_reconnect_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_SERVER_RECONNECT_COMMAND_HPP
--- a/libirccd/irccd/daemon/server_service.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/libirccd/irccd/daemon/server_service.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -684,6 +684,21 @@
     return server;
 }
 
+std::shared_ptr<server> server_service::require(const nlohmann::json& args, const std::string& key)
+{
+    auto id = json_util::get_string(args, key);
+
+    if (!string_util::is_identifier(id))
+        throw server_error(server_error::invalid_identifier, "");
+
+    auto server = get(id);
+
+    if (!server)
+        throw server_error(server_error::not_found, id);
+
+    return server;
+}
+
 void server_service::remove(const std::string& name)
 {
     auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) {
--- a/libirccd/irccd/daemon/server_service.hpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/libirccd/irccd/daemon/server_service.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -120,6 +120,16 @@
     std::shared_ptr<server> require(const std::string& name) const;
 
     /**
+     * Find a server from a JSON object.
+     *
+     * \pre json.is_object()
+     * \param json the JSON object
+     * \param key the server identifier property
+     * \throw server_error on errors
+     */
+    std::shared_ptr<server> require(const nlohmann::json& json, const std::string& key = "server");
+
+    /**
      * Remove a server from the irccd instance.
      *
      * The server if any, will be disconnected.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_topic_command.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,46 @@
+/*
+ * server_topic_command.cpp -- implementation of server-topic transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/json_util.hpp>
+
+#include "irccd.hpp"
+#include "server_service.hpp"
+#include "server_topic_command.hpp"
+#include "transport_client.hpp"
+
+namespace irccd {
+
+std::string server_topic_command::get_name() const noexcept
+{
+    return "server-topic";
+}
+
+void server_topic_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
+{
+    auto server = irccd.servers().require(args);
+    auto channel = json_util::get_string(args, "channel");
+    auto topic = json_util::get_string(args, "topic");
+
+    if (channel.empty())
+        throw server_error(server_error::invalid_channel, server->name());
+
+    server->topic(channel, topic);
+    client.success("server-topic");
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_topic_command.hpp	Fri Dec 22 21:31:31 2017 +0100
@@ -0,0 +1,55 @@
+/*
+ * server_topic_command.hpp -- implementation of server-topic transport command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DAEMON_SERVER_TOPIC_COMMAND_HPP
+#define IRCCD_DAEMON_SERVER_TOPIC_COMMAND_HPP
+
+/**
+ * \file server_topic_command.hpp
+ * \brief Implementation of server-topic transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Implementation of server-topic transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_channel,
+ *   - server_error::invalid_identifier,
+ *   - server_error::not_found.
+ */
+class server_topic_command : public command {
+public:
+    /**
+     * \copydoc command::get_name
+     */
+    std::string get_name() const noexcept override;
+
+    /**
+     * \copydoc command::exec
+     */
+    void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_SERVER_TOPIC_COMMAND_HPP
--- a/tests/src/libirccd/command-plugin-config/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-plugin-config/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "plugin-config"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/plugin_config_command.hpp>
 #include <irccd/daemon/plugin_service.hpp>
 
 #include <irccd/test/command_test.hpp>
--- a/tests/src/libirccd/command-plugin-info/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-plugin-info/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "plugin-info"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/plugin_info_command.hpp>
 #include <irccd/daemon/plugin_service.hpp>
 
 #include <irccd/test/command_test.hpp>
--- a/tests/src/libirccd/command-plugin-list/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-plugin-list/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "plugin-list"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/plugin_list_command.hpp>
 #include <irccd/daemon/plugin_service.hpp>
 
 #include <irccd/test/command_test.hpp>
--- a/tests/src/libirccd/command-plugin-load/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-plugin-load/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "plugin-load"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/plugin_load_command.hpp>
 #include <irccd/daemon/plugin_service.hpp>
 
 #include <irccd/test/command_test.hpp>
--- a/tests/src/libirccd/command-plugin-reload/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-plugin-reload/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "plugin-reload"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/plugin_reload_command.hpp>
 #include <irccd/daemon/plugin_service.hpp>
 
 #include <irccd/test/command_test.hpp>
--- a/tests/src/libirccd/command-plugin-unload/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-plugin-unload/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -19,8 +19,8 @@
 #define BOOST_TEST_MODULE "plugin-unload"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/command.hpp>
 #include <irccd/daemon/plugin_service.hpp>
+#include <irccd/daemon/plugin_unload_command.hpp>
 
 #include <irccd/test/command_test.hpp>
 
--- a/tests/src/libirccd/command-rule-add/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-rule-add/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -21,7 +21,8 @@
 
 #include <irccd/json_util.hpp>
 
-#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/rule_add_command.hpp>
+#include <irccd/daemon/rule_list_command.hpp>
 #include <irccd/daemon/rule_service.hpp>
 
 #include <irccd/test/command_test.hpp>
--- a/tests/src/libirccd/command-rule-edit/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-rule-edit/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -21,7 +21,8 @@
 
 #include <irccd/json_util.hpp>
 
-#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/rule_edit_command.hpp>
+#include <irccd/daemon/rule_info_command.hpp>
 #include <irccd/daemon/rule_service.hpp>
 
 #include <irccd/test/command_test.hpp>
--- a/tests/src/libirccd/command-rule-info/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-rule-info/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -21,7 +21,7 @@
 
 #include <irccd/json_util.hpp>
 
-#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/rule_info_command.hpp>
 #include <irccd/daemon/rule_service.hpp>
 
 #include <irccd/test/command_test.hpp>
--- a/tests/src/libirccd/command-rule-list/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-rule-list/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -21,7 +21,7 @@
 
 #include <irccd/json_util.hpp>
 
-#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/rule_list_command.hpp>
 #include <irccd/daemon/rule_service.hpp>
 
 #include <irccd/test/command_test.hpp>
--- a/tests/src/libirccd/command-rule-move/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-rule-move/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -21,7 +21,8 @@
 
 #include <irccd/json_util.hpp>
 
-#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/rule_list_command.hpp>
+#include <irccd/daemon/rule_move_command.hpp>
 #include <irccd/daemon/rule_service.hpp>
 
 #include <irccd/test/command_test.hpp>
--- a/tests/src/libirccd/command-rule-remove/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-rule-remove/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -21,7 +21,8 @@
 
 #include <irccd/json_util.hpp>
 
-#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/rule_remove_command.hpp>
+#include <irccd/daemon/rule_list_command.hpp>
 #include <irccd/daemon/rule_service.hpp>
 
 #include <irccd/test/command_test.hpp>
--- a/tests/src/libirccd/command-server-connect/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-server-connect/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -20,6 +20,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/server_service.hpp>
+#include <irccd/daemon/server_connect_command.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-disconnect/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-server-disconnect/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -19,6 +19,7 @@
 #define BOOST_TEST_MODULE "server-disconnect"
 #include <boost/test/unit_test.hpp>
 
+#include <irccd/daemon/server_disconnect_command.hpp>
 #include <irccd/daemon/server_service.hpp>
 
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-info/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-server-info/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -19,6 +19,7 @@
 #define BOOST_TEST_MODULE "server-info"
 #include <boost/test/unit_test.hpp>
 
+#include <irccd/daemon/server_info_command.hpp>
 #include <irccd/daemon/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
--- a/tests/src/libirccd/command-server-invite/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-server-invite/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -19,6 +19,7 @@
 #define BOOST_TEST_MODULE "server-invite"
 #include <boost/test/unit_test.hpp>
 
+#include <irccd/daemon/server_invite_command.hpp>
 #include <irccd/daemon/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
--- a/tests/src/libirccd/command-server-join/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-server-join/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -19,6 +19,7 @@
 #define BOOST_TEST_MODULE "server-join"
 #include <boost/test/unit_test.hpp>
 
+#include <irccd/daemon/server_join_command.hpp>
 #include <irccd/daemon/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
--- a/tests/src/libirccd/command-server-kick/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-server-kick/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -20,6 +20,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/server_service.hpp>
+#include <irccd/daemon/server_kick_command.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-list/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-server-list/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -20,6 +20,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/server_service.hpp>
+#include <irccd/daemon/server_list_command.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-me/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-server-me/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -20,6 +20,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/server_service.hpp>
+#include <irccd/daemon/server_me_command.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-message/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-server-message/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -20,6 +20,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/server_service.hpp>
+#include <irccd/daemon/server_message_command.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-mode/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-server-mode/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -20,6 +20,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/server_service.hpp>
+#include <irccd/daemon/server_mode_command.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-nick/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-server-nick/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -20,6 +20,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/server_service.hpp>
+#include <irccd/daemon/server_nick_command.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-notice/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-server-notice/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -20,6 +20,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/server_service.hpp>
+#include <irccd/daemon/server_notice_command.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-part/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-server-part/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -20,6 +20,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/server_service.hpp>
+#include <irccd/daemon/server_part_command.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-reconnect/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-server-reconnect/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -20,6 +20,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/server_service.hpp>
+#include <irccd/daemon/server_reconnect_command.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-topic/main.cpp	Fri Dec 22 11:08:01 2017 +0100
+++ b/tests/src/libirccd/command-server-topic/main.cpp	Fri Dec 22 21:31:31 2017 +0100
@@ -20,6 +20,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/server_service.hpp>
+#include <irccd/daemon/server_topic_command.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>