changeset 528:9daccaeedcce

Irccdctl: split cli.hpp, closes #726
author David Demelier <markand@malikania.fr>
date Thu, 16 Nov 2017 23:12:45 +0100
parents a88796ed040a
children b3a0f61a35fe
files irccdctl/CMakeLists.txt irccdctl/cli.cpp irccdctl/cli.hpp irccdctl/main.cpp irccdctl/plugin_config_cli.cpp irccdctl/plugin_config_cli.hpp irccdctl/plugin_info_cli.cpp irccdctl/plugin_info_cli.hpp irccdctl/plugin_list_cli.cpp irccdctl/plugin_list_cli.hpp irccdctl/plugin_load_cli.cpp irccdctl/plugin_load_cli.hpp irccdctl/plugin_reload_cli.cpp irccdctl/plugin_reload_cli.hpp irccdctl/plugin_unload_cli.cpp irccdctl/plugin_unload_cli.hpp irccdctl/rule_add_cli.cpp irccdctl/rule_add_cli.hpp irccdctl/rule_edit_cli.cpp irccdctl/rule_edit_cli.hpp irccdctl/rule_info_cli.cpp irccdctl/rule_info_cli.hpp irccdctl/rule_list_cli.cpp irccdctl/rule_list_cli.hpp irccdctl/rule_move_cli.cpp irccdctl/rule_move_cli.hpp irccdctl/rule_remove_cli.cpp irccdctl/rule_remove_cli.hpp irccdctl/server_channel_mode_cli.cpp irccdctl/server_channel_mode_cli.hpp irccdctl/server_channel_notice_cli.cpp irccdctl/server_channel_notice_cli.hpp irccdctl/server_connect_cli.cpp irccdctl/server_connect_cli.hpp irccdctl/server_disconnect_cli.cpp irccdctl/server_disconnect_cli.hpp irccdctl/server_info_cli.cpp irccdctl/server_info_cli.hpp irccdctl/server_invite_cli.cpp irccdctl/server_invite_cli.hpp irccdctl/server_join_cli.cpp irccdctl/server_join_cli.hpp irccdctl/server_kick_cli.cpp irccdctl/server_kick_cli.hpp irccdctl/server_list_cli.cpp irccdctl/server_list_cli.hpp irccdctl/server_me_cli.cpp irccdctl/server_me_cli.hpp irccdctl/server_message_cli.cpp irccdctl/server_message_cli.hpp irccdctl/server_mode_cli.cpp irccdctl/server_mode_cli.hpp irccdctl/server_nick_cli.cpp irccdctl/server_nick_cli.hpp irccdctl/server_notice_cli.cpp irccdctl/server_notice_cli.hpp irccdctl/server_part_cli.cpp irccdctl/server_part_cli.hpp irccdctl/server_reconnect_cli.cpp irccdctl/server_reconnect_cli.hpp irccdctl/server_topic_cli.cpp irccdctl/server_topic_cli.hpp irccdctl/watch_cli.cpp irccdctl/watch_cli.hpp
diffstat 64 files changed, 3517 insertions(+), 1607 deletions(-) [+]
line wrap: on
line diff
--- a/irccdctl/CMakeLists.txt	Thu Nov 16 22:45:12 2017 +0100
+++ b/irccdctl/CMakeLists.txt	Thu Nov 16 23:12:45 2017 +0100
@@ -26,8 +26,70 @@
     ${irccdctl_SOURCE_DIR}/alias.cpp
     ${irccdctl_SOURCE_DIR}/alias.hpp
     ${irccdctl_SOURCE_DIR}/cli.cpp
+    ${irccdctl_SOURCE_DIR}/cli.cpp
+    ${irccdctl_SOURCE_DIR}/cli.hpp
     ${irccdctl_SOURCE_DIR}/cli.hpp
     ${irccdctl_SOURCE_DIR}/main.cpp
+    ${irccdctl_SOURCE_DIR}/plugin_config_cli.cpp
+    ${irccdctl_SOURCE_DIR}/plugin_config_cli.hpp
+    ${irccdctl_SOURCE_DIR}/plugin_info_cli.cpp
+    ${irccdctl_SOURCE_DIR}/plugin_info_cli.hpp
+    ${irccdctl_SOURCE_DIR}/plugin_list_cli.cpp
+    ${irccdctl_SOURCE_DIR}/plugin_list_cli.hpp
+    ${irccdctl_SOURCE_DIR}/plugin_load_cli.cpp
+    ${irccdctl_SOURCE_DIR}/plugin_load_cli.hpp
+    ${irccdctl_SOURCE_DIR}/plugin_reload_cli.cpp
+    ${irccdctl_SOURCE_DIR}/plugin_reload_cli.hpp
+    ${irccdctl_SOURCE_DIR}/plugin_unload_cli.cpp
+    ${irccdctl_SOURCE_DIR}/plugin_unload_cli.hpp
+    ${irccdctl_SOURCE_DIR}/rule_add_cli.cpp
+    ${irccdctl_SOURCE_DIR}/rule_add_cli.hpp
+    ${irccdctl_SOURCE_DIR}/rule_edit_cli.cpp
+    ${irccdctl_SOURCE_DIR}/rule_edit_cli.hpp
+    ${irccdctl_SOURCE_DIR}/rule_info_cli.cpp
+    ${irccdctl_SOURCE_DIR}/rule_info_cli.hpp
+    ${irccdctl_SOURCE_DIR}/rule_list_cli.cpp
+    ${irccdctl_SOURCE_DIR}/rule_list_cli.hpp
+    ${irccdctl_SOURCE_DIR}/rule_move_cli.cpp
+    ${irccdctl_SOURCE_DIR}/rule_move_cli.hpp
+    ${irccdctl_SOURCE_DIR}/rule_remove_cli.cpp
+    ${irccdctl_SOURCE_DIR}/rule_remove_cli.hpp
+    ${irccdctl_SOURCE_DIR}/server_channel_mode_cli.cpp
+    ${irccdctl_SOURCE_DIR}/server_channel_mode_cli.hpp
+    ${irccdctl_SOURCE_DIR}/server_channel_notice_cli.cpp
+    ${irccdctl_SOURCE_DIR}/server_channel_notice_cli.hpp
+    ${irccdctl_SOURCE_DIR}/server_connect_cli.cpp
+    ${irccdctl_SOURCE_DIR}/server_connect_cli.hpp
+    ${irccdctl_SOURCE_DIR}/server_disconnect_cli.cpp
+    ${irccdctl_SOURCE_DIR}/server_disconnect_cli.hpp
+    ${irccdctl_SOURCE_DIR}/server_info_cli.cpp
+    ${irccdctl_SOURCE_DIR}/server_info_cli.hpp
+    ${irccdctl_SOURCE_DIR}/server_invite_cli.cpp
+    ${irccdctl_SOURCE_DIR}/server_invite_cli.hpp
+    ${irccdctl_SOURCE_DIR}/server_join_cli.cpp
+    ${irccdctl_SOURCE_DIR}/server_join_cli.hpp
+    ${irccdctl_SOURCE_DIR}/server_kick_cli.cpp
+    ${irccdctl_SOURCE_DIR}/server_kick_cli.hpp
+    ${irccdctl_SOURCE_DIR}/server_list_cli.cpp
+    ${irccdctl_SOURCE_DIR}/server_list_cli.hpp
+    ${irccdctl_SOURCE_DIR}/server_me_cli.cpp
+    ${irccdctl_SOURCE_DIR}/server_me_cli.hpp
+    ${irccdctl_SOURCE_DIR}/server_message_cli.cpp
+    ${irccdctl_SOURCE_DIR}/server_message_cli.hpp
+    ${irccdctl_SOURCE_DIR}/server_mode_cli.cpp
+    ${irccdctl_SOURCE_DIR}/server_mode_cli.hpp
+    ${irccdctl_SOURCE_DIR}/server_nick_cli.cpp
+    ${irccdctl_SOURCE_DIR}/server_nick_cli.hpp
+    ${irccdctl_SOURCE_DIR}/server_notice_cli.cpp
+    ${irccdctl_SOURCE_DIR}/server_notice_cli.hpp
+    ${irccdctl_SOURCE_DIR}/server_part_cli.cpp
+    ${irccdctl_SOURCE_DIR}/server_part_cli.hpp
+    ${irccdctl_SOURCE_DIR}/server_reconnect_cli.cpp
+    ${irccdctl_SOURCE_DIR}/server_reconnect_cli.hpp
+    ${irccdctl_SOURCE_DIR}/server_topic_cli.cpp
+    ${irccdctl_SOURCE_DIR}/server_topic_cli.hpp
+    ${irccdctl_SOURCE_DIR}/watch_cli.cpp
+    ${irccdctl_SOURCE_DIR}/watch_cli.hpp
 )
 
 irccd_define_executable(
--- a/irccdctl/cli.cpp	Thu Nov 16 22:45:12 2017 +0100
+++ b/irccdctl/cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -64,1127 +64,6 @@
     });
 }
 
-/*
- * plugin_info_cli.
- * ------------------------------------------------------------------
- */
-
-void plugin_config_cli::set(ctl::controller& ctl, const std::vector<std::string>&args)
-{
-    request(ctl, {
-        { "plugin", args[0] },
-        { "variable", args[1] },
-        { "value", args[2] }
-    });
-}
-
-void plugin_config_cli::get(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    auto json = nlohmann::json::object({
-        { "plugin", args[0] },
-        { "variable", args[1] }
-    });
-
-    request(ctl, std::move(json), [args] (auto result) {
-        if (result["variables"].is_object())
-            std::cout << json_util::pretty(result["variables"][args[1]]) << std::endl;
-    });
-}
-
-void plugin_config_cli::getall(ctl::controller& ctl, const std::vector<std::string> &args)
-{
-    request(ctl, {{ "plugin", args[0] }}, [] (auto result) {
-        auto variables = result["variables"];
-
-        for (auto v = variables.begin(); v != variables.end(); ++v)
-            std::cout << std::setw(16) << std::left << v.key() << " : " << json_util::pretty(v.value()) << std::endl;
-    });
-}
-
-std::string plugin_config_cli::name() const
-{
-    return "plugin-config";
-}
-
-void plugin_config_cli::exec(ctl::controller& ctl, const std::vector<std::string> &args)
-{
-    switch (args.size()) {
-    case 3:
-        set(ctl, args);
-        break;
-    case 2:
-        get(ctl, args);
-        break;
-    case 1:
-        getall(ctl, args);
-        break;
-    default:
-        throw std::invalid_argument("plugin-config requires at least 1 argument");
-    }
-}
-
-/*
- * plugin_info_cli.
- * ------------------------------------------------------------------
- */
-
-std::string plugin_info_cli::name() const
-{
-    return "plugin-info";
-}
-
-void plugin_info_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    if (args.size() < 1)
-        throw std::invalid_argument("plugin-info requires 1 argument");
-
-    request(ctl, {{ "plugin", args[0] }}, [] (auto result) {
-        std::cout << std::boolalpha;
-        std::cout << "Author         : " << json_util::get_string(result, "author") << std::endl;
-        std::cout << "License        : " << json_util::get_string(result, "license") << std::endl;
-        std::cout << "Summary        : " << json_util::get_string(result, "summary") << std::endl;
-        std::cout << "Version        : " << json_util::get_string(result, "version") << std::endl;
-    });
-}
-
-/*
- * plugin_list_cli.
- * ------------------------------------------------------------------
- */
-
-std::string plugin_list_cli::name() const
-{
-    return "plugin-list";
-}
-
-void plugin_list_cli::exec(ctl::controller& ctl, const std::vector<std::string>&)
-{
-    request(ctl, {{ "command", "plugin-list" }}, [] (auto result) {
-        for (const auto &value : result["list"])
-            if (value.is_string())
-                std::cout << value.template get<std::string>() << std::endl;
-    });
-}
-
-/*
- * plugin_load_cli.
- * ------------------------------------------------------------------
- */
-
-std::string plugin_load_cli::name() const
-{
-    return "plugin-load";
-}
-
-void plugin_load_cli::exec(ctl::controller& ctl, const std::vector<std::string> &args)
-{
-    if (args.size() < 1)
-        throw std::invalid_argument("plugin-load requires 1 argument");
-
-    request(ctl, {{ "plugin", args[0] }});
-}
-
-/*
- * plugin_reload_cli.
- * ------------------------------------------------------------------
- */
-
-std::string plugin_reload_cli::name() const
-{
-    return "plugin-reload";
-}
-
-void plugin_reload_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    if (args.size() < 1)
-        throw std::invalid_argument("plugin-reload requires 1 argument");
-
-    request(ctl, {{ "plugin", args[0] }});
-}
-
-/*
- * plugin_unload_cli.
- * ------------------------------------------------------------------
- */
-
-std::string plugin_unload_cli::name() const
-{
-    return "plugin-unload";
-}
-
-void plugin_unload_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    if (args.size() < 1)
-        throw std::invalid_argument("plugin-unload requires 1 argument");
-
-    request(ctl, {{ "plugin", args[0] }});
-}
-
-/*
- * server_channel_cli.
- * ------------------------------------------------------------------
- */
-
-std::string server_channel_mode_cli::name() const
-{
-    return "server-cmode";
-}
-
-void server_channel_mode_cli::exec(ctl::controller& ctl, const std::vector<std::string> &args)
-{
-    if (args.size() < 3)
-        throw std::invalid_argument("server-cmode requires 3 arguments");
-
-    request(ctl, {
-        { "command",    "server-cmode"  },
-        { "server",     args[0]         },
-        { "channel",    args[1]         },
-        { "mode",       args[2]         }
-    });
-}
-
-/*
- * server_channel_notice_cli.
- * ------------------------------------------------------------------
- */
-
-std::string server_channel_notice_cli::name() const
-{
-    return "server-cnotice";
-}
-
-void server_channel_notice_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    if (args.size() < 3)
-        throw std::invalid_argument("server-cnotice requires 3 arguments");
-
-    request(ctl, {
-        { "command",    "server-cnotice"    },
-        { "server",     args[0]             },
-        { "channel",    args[1]             },
-        { "message",    args[2]             }
-    });
-}
-
-/*
- * server_connect_cli.
- * ------------------------------------------------------------------
- */
-
-namespace {
-
-option::result parse(std::vector<std::string> &args)
-{
-    option::options options{
-        { "-c",             true    },
-        { "--command",      true    },
-        { "-n",             true    },
-        { "--nickname",     true    },
-        { "-r",             true    },
-        { "--realname",     true    },
-        { "-S",             false   },
-        { "--ssl-verify",   false   },
-        { "-s",             false   },
-        { "--ssl",          false   },
-        { "-u",             true    },
-        { "--username",     true    }
-    };
-
-    return option::read(args, options);
-}
-
-} // !namespace
-
-std::string server_connect_cli::name() const
-{
-    return "server-connect";
-}
-
-void server_connect_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    std::vector<std::string> copy(args);
-
-    option::result result = parse(copy);
-    option::result::const_iterator it;
-
-    if (copy.size() < 2)
-        throw std::invalid_argument("server-connect requires at least 2 arguments");
-
-    auto object = nlohmann::json::object({
-        { "name", copy[0] },
-        { "host", copy[1] }
-    });
-
-    if (copy.size() == 3) {
-        if (!string_util::is_int(copy[2]))
-            throw std::invalid_argument("invalid port number");
-
-        object["port"] = std::stoi(copy[2]);
-    }
-
-    if (result.count("-S") > 0 || result.count("--ssl-verify") > 0)
-        object["sslVerify"] = true;
-    if (result.count("-s") > 0 || result.count("--ssl") > 0)
-        object["ssl"] = true;
-    if ((it = result.find("-n")) != result.end() || (it = result.find("--nickname")) != result.end())
-        object["nickname"] = it->second;
-    if ((it = result.find("-r")) != result.end() || (it = result.find("--realname")) != result.end())
-        object["realname"] = it->second;
-    if ((it = result.find("-u")) != result.end() || (it = result.find("--username")) != result.end())
-        object["username"] = it->second;
-
-    request(ctl, object);
-}
-
-/*
- * server_disconnect_cli.
- * ------------------------------------------------------------------
- */
-
-std::string server_disconnect_cli::name() const
-{
-    return "server-disconnect";
-}
-
-void server_disconnect_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    auto object = nlohmann::json::object({
-        { "command", "server-disconnect" }
-    });
-
-    if (args.size() > 0)
-        object["server"] = args[0];
-
-    request(ctl, object);
-}
-
-/*
- * server_info_cli.
- * ------------------------------------------------------------------
- */
-
-std::string server_info_cli::name() const
-{
-    return "server-info";
-}
-
-void server_info_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    if (args.size() < 1)
-        throw std::invalid_argument("server-info requires 1 argument");
-
-    auto json = nlohmann::json::object({
-        { "command",    "server-info"   },
-        { "server",     args[0]         }
-    });
-
-    request(ctl, std::move(json), [] (auto result) {
-        std::cout << std::boolalpha;
-        std::cout << "Name           : " << json_util::pretty(result["name"]) << std::endl;
-        std::cout << "Host           : " << json_util::pretty(result["host"]) << std::endl;
-        std::cout << "Port           : " << json_util::pretty(result["port"]) << std::endl;
-        std::cout << "Ipv6           : " << json_util::pretty(result["ipv6"]) << std::endl;
-        std::cout << "SSL            : " << json_util::pretty(result["ssl"]) << std::endl;
-        std::cout << "SSL verified   : " << json_util::pretty(result["sslVerify"]) << std::endl;
-        std::cout << "Channels       : ";
-
-        for (const auto &v : result["channels"])
-            if (v.is_string())
-                std::cout << v.template get<std::string>() << " ";
-
-        std::cout << std::endl;
-
-        std::cout << "Nickname       : " << json_util::pretty(result["nickname"]) << std::endl;
-        std::cout << "User name      : " << json_util::pretty(result["username"]) << std::endl;
-        std::cout << "Real name      : " << json_util::pretty(result["realname"]) << std::endl;
-    });
-}
-
-/*
- * server_invite_cli.
- * ------------------------------------------------------------------
- */
-
-std::string server_invite_cli::name() const
-{
-    return "server-invite";
-}
-
-void server_invite_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    if (args.size() < 3)
-        throw std::invalid_argument("server-invite requires 3 arguments");
-
-    request(ctl, {
-        { "command",    "server-invite" },
-        { "server",     args[0]         },
-        { "target",     args[1]         },
-        { "channel",    args[2]         }
-    });
-}
-
-/*
- * server_join_cli.
- * ------------------------------------------------------------------
- */
-
-std::string server_join_cli::name() const
-{
-    return "server-join";
-}
-
-void server_join_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    if (args.size() < 2)
-        throw std::invalid_argument("server-join requires at least 2 arguments");
-
-    auto object = nlohmann::json::object({
-        { "server",     args[0]         },
-        { "channel",    args[1]         }
-    });
-
-    if (args.size() == 3)
-        object["password"] = args[2];
-
-    request(ctl, object);
-}
-
-/*
- * server_kick_cli.
- * ------------------------------------------------------------------
- */
-
-std::string server_kick_cli::name() const
-{
-    return "server-kick";
-}
-
-void server_kick_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    if (args.size() < 3)
-        throw std::invalid_argument("server-kick requires at least 3 arguments ");
-
-    auto object = nlohmann::json::object({
-        { "server",     args[0] },
-        { "target",     args[1] },
-        { "channel",    args[2] }
-    });
-
-    if (args.size() == 4)
-        object["reason"] = args[3];
-
-    request(ctl, object);
-}
-
-/*
- * server_list_cli.
- * ------------------------------------------------------------------
- */
-
-std::string server_list_cli::name() const
-{
-    return "server-list";
-}
-
-void server_list_cli::exec(ctl::controller& ctl, const std::vector<std::string>&)
-{
-    request(ctl, {{ "command", "server-list" }}, [] (auto result) {
-        for (const auto &n : result["list"])
-            if (n.is_string())
-                std::cout << n.template get<std::string>() << std::endl;
-    });
-}
-
-/*
- * server_me_cli.
- * ------------------------------------------------------------------
- */
-
-std::string server_me_cli::name() const
-{
-    return "server-me";
-}
-
-void server_me_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    if (args.size() < 3)
-        throw std::runtime_error("server-me requires 3 arguments");
-
-    request(ctl, {
-        { "server",     args[0]     },
-        { "target",     args[1]     },
-        { "message",    args[2]     }
-    });
-}
-
-/*
- * server_message_cli.
- * ------------------------------------------------------------------
- */
-
-std::string server_message_cli::name() const
-{
-    return "server-message";
-}
-
-void server_message_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    if (args.size() < 3)
-        throw std::invalid_argument("server-message requires 3 arguments");
-
-    request(ctl, {
-        { "server",     args[0] },
-        { "target",     args[1] },
-        { "message",    args[2] }
-    });
-}
-
-/*
- * server_mode_cli.
- * ------------------------------------------------------------------
- */
-
-std::string server_mode_cli::name() const
-{
-    return "server-mode";
-}
-
-void server_mode_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    if (args.size() < 2)
-        throw std::invalid_argument("server-mode requires 2 arguments");
-
-    request(ctl, {
-        { "server", args[0] },
-        { "mode",   args[1] }
-    });
-}
-
-/*
- * server_nick_cli.
- * ------------------------------------------------------------------
- */
-
-std::string server_nick_cli::name() const
-{
-    return "server-nick";
-}
-
-void server_nick_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    if (args.size() < 2)
-        throw std::invalid_argument("server-nick requires 2 arguments");
-
-    request(ctl, {
-        { "server",     args[0] },
-        { "nickname",   args[1] }
-    });
-}
-
-/*
- * server_notice_cli.
- * ------------------------------------------------------------------
- */
-
-std::string server_notice_cli::name() const
-{
-    return "server-notice";
-}
-
-void server_notice_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    if (args.size() < 3)
-        throw std::invalid_argument("server-notice requires 3 arguments");
-
-    request(ctl, {
-        { "server",     args[0] },
-        { "target",     args[1] },
-        { "message",    args[2] }
-    });
-}
-
-/*
- * server_part_cli.
- * ------------------------------------------------------------------
- */
-
-std::string server_part_cli::name() const
-{
-    return "server-part";
-}
-
-void server_part_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    if (args.size() < 2)
-        throw std::invalid_argument("server-part requires at least 2 arguments");
-
-    auto object = nlohmann::json::object({
-        { "server",     args[0] },
-        { "channel",    args[1] }
-    });
-
-    if (args.size() >= 3)
-        object["reason"] = args[2];
-
-    request(ctl, object);
-}
-
-/*
- * server_reconnect_cli.
- * ------------------------------------------------------------------
- */
-
-std::string server_reconnect_cli::name() const
-{
-    return "server-reconnect";
-}
-
-void server_reconnect_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    auto object = nlohmann::json::object({
-        { "command", "server-reconnect" }
-    });
-
-    if (args.size() >= 1)
-        object["server"] = args[0];
-
-    request(ctl, object);
-}
-
-/*
- * server_topic_cli.
- * ------------------------------------------------------------------
- */
-
-std::string server_topic_cli::name() const
-{
-    return "server-topic";
-}
-
-void server_topic_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    if (args.size() < 3)
-        throw std::invalid_argument("server-topic requires 3 arguments");
-
-    request(ctl, {
-        { "server",     args[0] },
-        { "channel",    args[1] },
-        { "topic",      args[2] }
-    });
-}
-
-/*
- * rule_add_cli.
- * ------------------------------------------------------------------
- */
-
-std::string rule_add_cli::name() const
-{
-    return "rule-add";
-}
-
-void rule_add_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    static const option::options options{
-        { "-c",             true },
-        { "--add-channel",  true },
-        { "-e",             true },
-        { "--add-event",    true },
-        { "-i",             true },
-        { "--index",        true },
-        { "-p",             true },
-        { "--add-plugin",   true },
-        { "-s",             true },
-        { "--add-server",   true }
-    };
-
-    auto copy = args;
-    auto result = option::read(copy, options);
-
-    if (copy.size() < 1)
-        throw std::invalid_argument("rule-add requires at least 1 argument");
-
-    auto json = nlohmann::json::object({
-        { "command",    "rule-add"              },
-        { "channels",   nlohmann::json::array() },
-        { "events",     nlohmann::json::array() },
-        { "plugins",    nlohmann::json::array() },
-        { "servers",    nlohmann::json::array() }
-    });
-
-    // All sets.
-    for (const auto& pair : result) {
-        if (pair.first == "-c" || pair.first == "--add-channel")
-            json["channels"].push_back(pair.second);
-        if (pair.first == "-e" || pair.first == "--add-event")
-            json["events"].push_back(pair.second);
-        if (pair.first == "-p" || pair.first == "--add-plugin")
-            json["plugins"].push_back(pair.second);
-        if (pair.first == "-s" || pair.first == "--add-server")
-            json["servers"].push_back(pair.second);
-    }
-
-    // Index.
-    if (result.count("-i") > 0)
-        json["index"] = string_util::to_number<unsigned>(result.find("-i")->second);
-    if (result.count("--index") > 0)
-        json["index"] = string_util::to_number<unsigned>(result.find("--index")->second);
-
-    // And action.
-    if (copy[0] != "accept" && copy[0] != "drop")
-        throw std::runtime_error(string_util::sprintf("invalid action '%s'", copy[0]));
-
-    json["action"] = copy[0];
-
-    request(ctl, json);
-}
-
-/*
- * rule_edit_cli.
- * ------------------------------------------------------------------
- */
-
-std::string rule_edit_cli::name() const
-{
-    return "rule-edit";
-}
-
-void rule_edit_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    static const option::options options{
-        { "-a",                 true },
-        { "--action",           true },
-        { "-c",                 true },
-        { "--add-channel",      true },
-        { "-C",                 true },
-        { "--remove-channel",   true },
-        { "-e",                 true },
-        { "--add-event",        true },
-        { "-E",                 true },
-        { "--remove-event",     true },
-        { "-p",                 true },
-        { "--add-plugin",       true },
-        { "-P",                 true },
-        { "--remove-plugin",    true },
-        { "-s",                 true },
-        { "--add-server",       true },
-        { "-S",                 true },
-        { "--remove-server",    true },
-    };
-
-    auto copy = args;
-    auto result = option::read(copy, options);
-
-    if (copy.size() < 1)
-        throw std::invalid_argument("rule-edit requires at least 1 argument");
-
-    auto json = nlohmann::json::object({
-        { "command",    "rule-edit"             },
-        { "channels",   nlohmann::json::array() },
-        { "events",     nlohmann::json::array() },
-        { "plugins",    nlohmann::json::array() },
-        { "servers",    nlohmann::json::array() }
-    });
-
-    for (const auto& pair : result) {
-        // Action.
-        if (pair.first == "-a" || pair.first == "--action")
-            json["action"] = pair.second;
-
-        // Additions.
-        if (pair.first == "-c" || pair.first == "--add-channel")
-            json["add-channels"].push_back(pair.second);
-        if (pair.first == "-e" || pair.first == "--add-event")
-            json["add-events"].push_back(pair.second);
-        if (pair.first == "-p" || pair.first == "--add-plugin")
-            json["add-plugins"].push_back(pair.second);
-        if (pair.first == "-s" || pair.first == "--add-server")
-            json["add-servers"].push_back(pair.second);
-
-        // Removals.
-        if (pair.first == "-C" || pair.first == "--remove-channel")
-            json["remove-channels"].push_back(pair.second);
-        if (pair.first == "-E" || pair.first == "--remove-event")
-            json["remove-events"].push_back(pair.second);
-        if (pair.first == "-P" || pair.first == "--remove-plugin")
-            json["remove-plugins"].push_back(pair.second);
-        if (pair.first == "-S" || pair.first == "--remove-server")
-            json["remove-servers"].push_back(pair.second);
-    }
-
-    // Index.
-    json["index"] = string_util::to_number<unsigned>(copy[0]);
-
-    request(ctl, json);
-}
-
-/*
- * rule_list_cli.
- * ------------------------------------------------------------------
- */
-
-namespace {
-
-void show_rule(const nlohmann::json& json, int index)
-{
-    assert(json.is_object());
-
-    auto unjoin = [] (auto array) {
-        std::ostringstream oss;
-
-        for (auto it = array.begin(); it != array.end(); ++it) {
-            if (!it->is_string())
-                continue;
-
-            oss << it->template get<std::string>() << " ";
-        }
-
-        return oss.str();
-    };
-    auto unstr = [] (auto action) {
-        if (action.is_string() && action == "accept")
-            return "accept";
-        else
-            return "drop";
-    };
-
-    std::cout << "rule:        " << index << std::endl;
-    std::cout << "servers:     " << unjoin(json["servers"]) << std::endl;
-    std::cout << "channels:    " << unjoin(json["channels"]) << std::endl;
-    std::cout << "plugins:     " << unjoin(json["plugins"]) << std::endl;
-    std::cout << "events:      " << unjoin(json["events"]) << std::endl;
-    std::cout << "action:      " << unstr(json["action"]) << std::endl;
-    std::cout << std::endl;
-}
-
-} // !namespace
-
-std::string rule_list_cli::name() const
-{
-    return "rule-list";
-}
-
-void rule_list_cli::exec(ctl::controller& ctl, const std::vector<std::string>&)
-{
-    request(ctl, {{ "command", "rule-list" }}, [] (auto result) {
-        auto pos = 0;
-
-        for (const auto& obj : result["list"]) {
-            if (obj.is_object())
-                show_rule(obj, pos++);
-        }
-    });
-}
-
-/*
- * rule_info_cli.
- * ------------------------------------------------------------------
- */
-
-std::string rule_info_cli::name() const
-{
-    return "rule-info";
-}
-
-void rule_info_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    if (args.size() < 1)
-        throw std::invalid_argument("rule-info requires 1 argument");
-
-    int index = 0;
-
-    try {
-        index = std::stoi(args[0]);
-    } catch (...) {
-        throw std::invalid_argument("invalid number '" + args[0] + "'");
-    }
-
-    auto json = nlohmann::json::object({
-        { "command",    "rule-info" },
-        { "index",      index       }
-    });
-
-    request(ctl, std::move(json), [] (auto result) {
-        show_rule(result, 0);
-    });
-}
-
-/*
- * rule_remove_cli.
- * ------------------------------------------------------------------
- */
-
-std::string rule_remove_cli::name() const
-{
-    return "rule-remove";
-}
-
-void rule_remove_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    if (args.size() < 1)
-        throw std::invalid_argument("rule-remove requires 1 argument");
-
-    int index = 0;
-
-    try {
-        index = std::stoi(args[0]);
-    } catch (...) {
-        throw std::invalid_argument("invalid number '" + args[0] + "'");
-    }
-
-    request(ctl, {
-        { "command",    "rule-remove"   },
-        { "index",      index           }
-    });
-}
-
-/*
- * rule_move_cli.
- * ------------------------------------------------------------------
- */
-
-std::string rule_move_cli::name() const
-{
-    return "rule-move";
-}
-
-void rule_move_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    if (args.size() < 2)
-        throw std::invalid_argument("rule-move requires 2 arguments");
-
-    int from = 0;
-    int to = 0;
-
-    try {
-        from = std::stoi(args[0]);
-        to = std::stoi(args[1]);
-    } catch (...) {
-        throw std::invalid_argument("invalid number");
-    }
-
-    request(ctl, {
-        { "command",    "rule-move" },
-        { "from",       from        },
-        { "to",         to          }
-    });
-}
-
-/*
- * WatchCli.
- * ------------------------------------------------------------------
- */
-
-namespace {
-
-std::string format(std::vector<std::string> args)
-{
-    auto result = option::read(args, {
-        { "-f",         true },
-        { "--format",   true }
-    });
-
-    if (result.count("-f") > 0)
-        return result.find("-f")->second;
-    if (result.count("--format") > 0)
-        return result.find("--format")->second;
-
-    return "native";
-}
-
-void onChannelMode(const nlohmann::json &v)
-{
-    std::cout << "event:       onChannelMode\n";
-    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
-    std::cout << "mode:        " << json_util::pretty(v, "mode") << "\n";
-    std::cout << "argument:    " << json_util::pretty(v, "argument") << "\n";
-}
-
-void onChannelNotice(const nlohmann::json &v)
-{
-    std::cout << "event:       onChannelNotice\n";
-    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
-    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
-    std::cout << "message:     " << json_util::pretty(v, "message") << "\n";
-}
-
-void onConnect(const nlohmann::json &v)
-{
-    std::cout << "event:       onConnect\n";
-    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
-}
-
-void onInvite(const nlohmann::json &v)
-{
-    std::cout << "event:       onInvite\n";
-    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
-    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
-}
-
-void onJoin(const nlohmann::json &v)
-{
-    std::cout << "event:       onJoin\n";
-    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
-    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
-}
-
-void onKick(const nlohmann::json &v)
-{
-    std::cout << "event:       onKick\n";
-    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
-    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
-    std::cout << "target:      " << json_util::pretty(v, "target") << "\n";
-    std::cout << "reason:      " << json_util::pretty(v, "reason") << "\n";
-}
-
-void onMessage(const nlohmann::json &v)
-{
-    std::cout << "event:       onMessage\n";
-    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
-    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
-    std::cout << "message:     " << json_util::pretty(v, "message") << "\n";
-}
-
-void onMe(const nlohmann::json &v)
-{
-    std::cout << "event:       onMe\n";
-    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
-    std::cout << "target:      " << json_util::pretty(v, "target") << "\n";
-    std::cout << "message:     " << json_util::pretty(v, "message") << "\n";
-}
-
-void onMode(const nlohmann::json &v)
-{
-    std::cout << "event:       onMode\n";
-    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
-    std::cout << "mode:        " << json_util::pretty(v, "mode") << "\n";
-}
-
-void onNames(const nlohmann::json &v)
-{
-    std::cout << "event:       onNames\n";
-    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
-    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
-    std::cout << "names:       " << json_util::pretty(v, "names") << "\n";
-}
-
-void onNick(const nlohmann::json &v)
-{
-    std::cout << "event:       onNick\n";
-    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
-    std::cout << "nickname:    " << json_util::pretty(v, "nickname") << "\n";
-}
-
-void onNotice(const nlohmann::json &v)
-{
-    std::cout << "event:       onNotice\n";
-    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
-    std::cout << "message:     " << json_util::pretty(v, "message") << "\n";
-}
-
-void onPart(const nlohmann::json &v)
-{
-    std::cout << "event:       onPart\n";
-    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
-    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
-    std::cout << "reason:      " << json_util::pretty(v, "reason") << "\n";
-}
-
-void onQuery(const nlohmann::json &v)
-{
-    std::cout << "event:       onQuery\n";
-    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
-    std::cout << "message:     " << json_util::pretty(v, "message") << "\n";
-}
-
-void onTopic(const nlohmann::json &v)
-{
-    std::cout << "event:       onTopic\n";
-    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
-    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
-    std::cout << "topic:       " << json_util::pretty(v, "topic") << "\n";
-}
-
-void onWhois(const nlohmann::json &v)
-{
-    std::cout << "event:       onWhois\n";
-    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
-    std::cout << "nickname:    " << json_util::pretty(v, "nickname") << "\n";
-    std::cout << "username:    " << json_util::pretty(v, "username") << "\n";
-    std::cout << "host:        " << json_util::pretty(v, "host") << "\n";
-    std::cout << "realname:    " << json_util::pretty(v, "realname") << "\n";
-}
-
-const std::unordered_map<std::string, std::function<void (const nlohmann::json&)>> events{
-    { "onChannelMode",      onChannelMode   },
-    { "onChannelNotice",    onChannelNotice },
-    { "onConnect",          onConnect       },
-    { "onInvite",           onInvite        },
-    { "onJoin",             onJoin          },
-    { "onKick",             onKick          },
-    { "onMessage",          onMessage       },
-    { "onMe",               onMe            },
-    { "onMode",             onMode          },
-    { "onNames",            onNames         },
-    { "onNick",             onNick          },
-    { "onNotice",           onNotice        },
-    { "onPart",             onPart          },
-    { "onQuery",            onQuery         },
-    { "onTopic",            onTopic         },
-    { "onWhois",            onWhois         }
-};
-
-void get_event(ctl::controller& ctl, std::string fmt)
-{
-    ctl.recv([&ctl, fmt] (auto code, auto message) {
-        if (code)
-            throw boost::system::system_error(code);
-
-        auto it = events.find(json_util::to_string(message["event"]));
-
-        if (it != events.end()) {
-            if (fmt == "json")
-                std::cout << message.dump(4) << std::endl;
-            else {
-                it->second(message);
-                std::cout << std::endl;
-            }
-        }
-
-        get_event(ctl, std::move(fmt));
-    });
-}
-
-} // !namespace
-
-std::string watch_cli::name() const
-{
-    return "watch";
-}
-
-void watch_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
-{
-    auto fmt = format(args);
-
-    if (fmt != "native" && fmt != "json")
-        throw std::invalid_argument("invalid format given: " + fmt);
-
-    get_event(ctl, fmt);
-}
-
 } // !cli
 
 } // !irccd
--- a/irccdctl/cli.hpp	Thu Nov 16 22:45:12 2017 +0100
+++ b/irccdctl/cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -19,6 +19,7 @@
 #ifndef IRCCD_CLI_HPP
 #define IRCCD_CLI_HPP
 
+#include <stdexcept>
 #include <string>
 #include <vector>
 
@@ -30,16 +31,14 @@
 
 class controller;
 
-/*
- * cli.
- * ------------------------------------------------------------------
- */
-
 /**
  * \brief Abstract CLI class.
  */
 class cli {
 public:
+    /**
+     * Convenient handler for request function.
+     */
     using handler_t = std::function<void (nlohmann::json)>;
 
 private:
@@ -66,498 +65,30 @@
     void request(ctl::controller& ctl, nlohmann::json json, handler_t handler = nullptr);
 
 public:
-    cli() noexcept = default;
-
-    virtual ~cli() noexcept = default;
-
-    virtual std::string name() const = 0;
-
-    virtual void exec(ctl::controller& ctl, const std::vector<std::string>& args) = 0;
-};
-
-/**
- * \brief Implementation of irccdctl plugin-config.
- */
-class plugin_config_cli : public cli {
-private:
-    void set(ctl::controller&, const std::vector<std::string>&);
-    void get(ctl::controller&, const std::vector<std::string>&);
-    void getall(ctl::controller&, const std::vector<std::string>&);
-
-public:
     /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl plugin-info.
- */
-class plugin_info_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
+     * Default constructor.
      */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl plugin-list.
- */
-class plugin_list_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl plugin-load.
- */
-class plugin_load_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl plugin-reload.
- */
-class plugin_reload_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl plugin-unload.
- */
-class plugin_unload_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
+    cli() noexcept = default;
 
     /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl server-cmode.
- */
-class server_channel_mode_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl server-cnotice.
- */
-class server_channel_notice_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl server-connect.
- */
-class server_connect_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl server-disconnect.
- */
-class server_disconnect_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
+     * Virtual destructor defaulted.
      */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl server-info.
- */
-class server_info_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl server-invite.
- */
-class server_invite_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl server-join.
- */
-class server_join_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl server-kick.
- */
-class server_kick_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
+    virtual ~cli() noexcept = default;
 
     /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl server-list.
- */
-class server_list_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl server-me.
- */
-class server_me_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl server-message.
- */
-class server_message_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl server-mode.
- */
-class server_mode_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
+     * Return the command name.
+     *
+     * \return the name
      */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl server-nick.
- */
-class server_nick_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl server-notice.
- */
-class server_notice_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl server-part.
- */
-class server_part_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl server-reconnect.
- */
-class server_reconnect_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
+    virtual std::string name() const = 0;
 
     /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl server-topic.
- */
-class server_topic_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl rule-add.
- */
-class rule_add_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl rule-edit.
- */
-class rule_edit_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl rule-list.
- */
-class rule_list_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
+     * Execute the command.
+     *
+     * \param ctl the controller
+     * \param args the user arguments
      */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl rule-info.
- */
-class rule_info_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl rule-remove.
- */
-class rule_remove_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl rule-move.
- */
-class rule_move_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
-};
-
-/**
- * \brief Implementation of irccdctl watch.
- */
-class watch_cli : public cli {
-public:
-    /**
-     * \copydoc cli::name
-     */
-    std::string name() const override;
-
-    /**
-     * \copydoc cli::exec
-     */
-    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+    virtual void exec(ctl::controller& ctl, const std::vector<std::string>& args) = 0;
 };
 
 } // !ctl
--- a/irccdctl/main.cpp	Thu Nov 16 22:45:12 2017 +0100
+++ b/irccdctl/main.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -38,6 +38,37 @@
 #   include <irccd/ctl/local_connection.hpp>
 #endif
 
+#include "plugin_config_cli.hpp"
+#include "plugin_info_cli.hpp"
+#include "plugin_list_cli.hpp"
+#include "plugin_load_cli.hpp"
+#include "plugin_reload_cli.hpp"
+#include "plugin_unload_cli.hpp"
+#include "rule_add_cli.hpp"
+#include "rule_edit_cli.hpp"
+#include "rule_info_cli.hpp"
+#include "rule_list_cli.hpp"
+#include "rule_move_cli.hpp"
+#include "rule_remove_cli.hpp"
+#include "server_channel_mode_cli.hpp"
+#include "server_channel_notice_cli.hpp"
+#include "server_connect_cli.hpp"
+#include "server_disconnect_cli.hpp"
+#include "server_info_cli.hpp"
+#include "server_invite_cli.hpp"
+#include "server_join_cli.hpp"
+#include "server_kick_cli.hpp"
+#include "server_list_cli.hpp"
+#include "server_me_cli.hpp"
+#include "server_message_cli.hpp"
+#include "server_mode_cli.hpp"
+#include "server_nick_cli.hpp"
+#include "server_notice_cli.hpp"
+#include "server_part_cli.hpp"
+#include "server_reconnect_cli.hpp"
+#include "server_topic_cli.hpp"
+#include "watch_cli.hpp"
+
 #include "alias.hpp"
 #include "cli.hpp"
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/plugin_config_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,83 @@
+/*
+ * plugin_config_cli.cpp -- implementation of irccdctl plugin-config
+ *
+ * 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 "plugin_config_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+void plugin_config_cli::set(ctl::controller& ctl, const std::vector<std::string>&args)
+{
+    request(ctl, {
+        { "plugin", args[0] },
+        { "variable", args[1] },
+        { "value", args[2] }
+    });
+}
+
+void plugin_config_cli::get(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    auto json = nlohmann::json::object({
+        { "plugin", args[0] },
+        { "variable", args[1] }
+    });
+
+    request(ctl, std::move(json), [args] (auto result) {
+        if (result["variables"].is_object())
+            std::cout << json_util::pretty(result["variables"][args[1]]) << std::endl;
+    });
+}
+
+void plugin_config_cli::getall(ctl::controller& ctl, const std::vector<std::string> &args)
+{
+    request(ctl, {{ "plugin", args[0] }}, [] (auto result) {
+        auto variables = result["variables"];
+
+        for (auto v = variables.begin(); v != variables.end(); ++v)
+            std::cout << std::setw(16) << std::left << v.key() << " : " << json_util::pretty(v.value()) << std::endl;
+    });
+}
+
+std::string plugin_config_cli::name() const
+{
+    return "plugin-config";
+}
+
+void plugin_config_cli::exec(ctl::controller& ctl, const std::vector<std::string> &args)
+{
+    switch (args.size()) {
+    case 3:
+        set(ctl, args);
+        break;
+    case 2:
+        get(ctl, args);
+        break;
+    case 1:
+        getall(ctl, args);
+        break;
+    default:
+        throw std::invalid_argument("plugin-config requires at least 1 argument");
+    }
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/plugin_config_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,58 @@
+/*
+ * plugin_config_cli.hpp -- implementation of irccdctl plugin-config
+ *
+ * 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_PLUGIN_CONFIG_CLI_HPP
+#define IRCCD_PLUGIN_CONFIG_CLI_HPP
+
+/**
+ * \file plugin_config_cli.hpp
+ * \brief Implementation of irccdctl plugin-config.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl plugin-config.
+ */
+class plugin_config_cli : public cli {
+private:
+    void set(ctl::controller&, const std::vector<std::string>&);
+    void get(ctl::controller&, const std::vector<std::string>&);
+    void getall(ctl::controller&, const std::vector<std::string>&);
+
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_PLUGIN_CONFIG_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/plugin_info_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,48 @@
+/*
+ * plugin_info_cli.cpp -- implementation of irccdctl plugin-info
+ *
+ * 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 "plugin_info_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string plugin_info_cli::name() const
+{
+    return "plugin-info";
+}
+
+void plugin_info_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    if (args.size() < 1)
+        throw std::invalid_argument("plugin-info requires 1 argument");
+
+    request(ctl, {{ "plugin", args[0] }}, [] (auto result) {
+        std::cout << std::boolalpha;
+        std::cout << "Author         : " << json_util::get_string(result, "author") << std::endl;
+        std::cout << "License        : " << json_util::get_string(result, "license") << std::endl;
+        std::cout << "Summary        : " << json_util::get_string(result, "summary") << std::endl;
+        std::cout << "Version        : " << json_util::get_string(result, "version") << std::endl;
+    });
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/plugin_info_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * plugin_info_cli.hpp -- implementation of irccdctl plugin-info
+ *
+ * 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_PLUGIN_INFO_CLI_HPP
+#define IRCCD_PLUGIN_INFO_CLI_HPP
+
+/**
+ * \file plugin_info_cli.hpp
+ * \brief Implementation of irccdctl plugin-info.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl plugin-info.
+ */
+class plugin_info_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_PLUGIN_INFO_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/plugin_list_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,41 @@
+/*
+ * plugin_list_cli.cpp -- implementation of irccdctl plugin-list
+ *
+ * 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 "plugin_list_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string plugin_list_cli::name() const
+{
+    return "plugin-list";
+}
+
+void plugin_list_cli::exec(ctl::controller& ctl, const std::vector<std::string>&)
+{
+    request(ctl, {{ "command", "plugin-list" }}, [] (auto result) {
+        for (const auto& value : result["list"])
+            if (value.is_string())
+                std::cout << value.template get<std::string>() << std::endl;
+    });
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/plugin_list_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * plugin_list_cli.hpp -- implementation of irccdctl plugin-list
+ *
+ * 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_PLUGIN_LIST_CLI_HPP
+#define IRCCD_PLUGIN_LIST_CLI_HPP
+
+/**
+ * \file plugin_list_cli.hpp
+ * \brief Implementation of irccdctl plugin-list.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl plugin-list.
+ */
+class plugin_list_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_PLUGIN_LIST_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/plugin_load_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,40 @@
+/*
+ * plugin_load_cli.cpp -- implementation of irccdctl plugin-load
+ *
+ * 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 "plugin_load_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string plugin_load_cli::name() const
+{
+    return "plugin-load";
+}
+
+void plugin_load_cli::exec(ctl::controller& ctl, const std::vector<std::string> &args)
+{
+    if (args.size() < 1)
+        throw std::invalid_argument("plugin-load requires 1 argument");
+
+    request(ctl, {{ "plugin", args[0] }});
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/plugin_load_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * plugin_load_cli.hpp -- implementation of irccdctl plugin-load
+ *
+ * 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_PLUGIN_LOAD_CLI_HPP
+#define IRCCD_PLUGIN_LOAD_CLI_HPP
+
+/**
+ * \file plugin_load_cli.hpp
+ * \brief Implementation of irccdctl plugin-load.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl plugin-load.
+ */
+class plugin_load_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_PLUGIN_LOAD_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/plugin_reload_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,40 @@
+/*
+ * plugin_reload_cli.cpp -- implementation of irccdctl plugin-reload
+ *
+ * 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 "plugin_reload_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string plugin_reload_cli::name() const
+{
+    return "plugin-reload";
+}
+
+void plugin_reload_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    if (args.size() < 1)
+        throw std::invalid_argument("plugin-reload requires 1 argument");
+
+    request(ctl, {{ "plugin", args[0] }});
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/plugin_reload_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * plugin_reload_cli.hpp -- implementation of irccdctl plugin-reload
+ *
+ * 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_PLUGIN_RELOAD_CLI_HPP
+#define IRCCD_PLUGIN_RELOAD_CLI_HPP
+
+/**
+ * \file plugin_reload_cli.hpp
+ * \brief Implementation of irccdctl plugin-reload.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl plugin-reload.
+ */
+class plugin_reload_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_PLUGIN_RELOAD_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/plugin_unload_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,41 @@
+/*
+ * plugin_unload_cli.cpp -- implementation of irccdctl plugin-unload
+ *
+ * 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 "plugin_unload_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string plugin_unload_cli::name() const
+{
+    return "plugin-unload";
+}
+
+void plugin_unload_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    if (args.size() < 1)
+        throw std::invalid_argument("plugin-unload requires 1 argument");
+
+    request(ctl, {{ "plugin", args[0] }});
+}
+
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/plugin_unload_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * plugin_unload_cli.hpp -- implementation of irccdctl plugin-unload
+ *
+ * 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_PLUGIN_UNLOAD_CLI_HPP
+#define IRCCD_PLUGIN_UNLOAD_CLI_HPP
+
+/**
+ * \file plugin_unload_cli.hpp
+ * \brief Implementation of irccdctl plugin-unload.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl plugin-unload.
+ */
+class plugin_unload_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_PLUGIN_UNLOAD_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/rule_add_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,91 @@
+/*
+ * rule_add_cli.cpp -- implementation of irccdctl rule-add
+ *
+ * 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/options.hpp>
+#include <irccd/string_util.hpp>
+
+#include "rule_add_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string rule_add_cli::name() const
+{
+    return "rule-add";
+}
+
+void rule_add_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    static const option::options options{
+        { "-c",             true },
+        { "--add-channel",  true },
+        { "-e",             true },
+        { "--add-event",    true },
+        { "-i",             true },
+        { "--index",        true },
+        { "-p",             true },
+        { "--add-plugin",   true },
+        { "-s",             true },
+        { "--add-server",   true }
+    };
+
+    auto copy = args;
+    auto result = option::read(copy, options);
+
+    if (copy.size() < 1)
+        throw std::invalid_argument("rule-add requires at least 1 argument");
+
+    auto json = nlohmann::json::object({
+        { "command",    "rule-add"              },
+        { "channels",   nlohmann::json::array() },
+        { "events",     nlohmann::json::array() },
+        { "plugins",    nlohmann::json::array() },
+        { "servers",    nlohmann::json::array() }
+    });
+
+    // All sets.
+    for (const auto& pair : result) {
+        if (pair.first == "-c" || pair.first == "--add-channel")
+            json["channels"].push_back(pair.second);
+        if (pair.first == "-e" || pair.first == "--add-event")
+            json["events"].push_back(pair.second);
+        if (pair.first == "-p" || pair.first == "--add-plugin")
+            json["plugins"].push_back(pair.second);
+        if (pair.first == "-s" || pair.first == "--add-server")
+            json["servers"].push_back(pair.second);
+    }
+
+    // Index.
+    if (result.count("-i") > 0)
+        json["index"] = string_util::to_number<unsigned>(result.find("-i")->second);
+    if (result.count("--index") > 0)
+        json["index"] = string_util::to_number<unsigned>(result.find("--index")->second);
+
+    // And action.
+    if (copy[0] != "accept" && copy[0] != "drop")
+        throw std::runtime_error(string_util::sprintf("invalid action '%s'", copy[0]));
+
+    json["action"] = copy[0];
+
+    request(ctl, json);
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/rule_add_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * rule_add_cli.hpp -- implementation of irccdctl rule-add
+ *
+ * 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_RULE_ADD_CLI_HPP
+#define IRCCD_RULE_ADD_CLI_HPP
+
+/**
+ * \file rule_add_cli.hpp
+ * \brief Implementation of irccdctl rule-add.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl rule-add.
+ */
+class rule_add_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_RULE_ADD_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/rule_edit_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,104 @@
+/*
+ * rule_edit_cli.cpp -- implementation of irccdctl rule-edit
+ *
+ * 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/options.hpp>
+#include <irccd/string_util.hpp>
+
+#include "rule_edit_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string rule_edit_cli::name() const
+{
+    return "rule-edit";
+}
+
+void rule_edit_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    static const option::options options{
+        { "-a",                 true },
+        { "--action",           true },
+        { "-c",                 true },
+        { "--add-channel",      true },
+        { "-C",                 true },
+        { "--remove-channel",   true },
+        { "-e",                 true },
+        { "--add-event",        true },
+        { "-E",                 true },
+        { "--remove-event",     true },
+        { "-p",                 true },
+        { "--add-plugin",       true },
+        { "-P",                 true },
+        { "--remove-plugin",    true },
+        { "-s",                 true },
+        { "--add-server",       true },
+        { "-S",                 true },
+        { "--remove-server",    true },
+    };
+
+    auto copy = args;
+    auto result = option::read(copy, options);
+
+    if (copy.size() < 1)
+        throw std::invalid_argument("rule-edit requires at least 1 argument");
+
+    auto json = nlohmann::json::object({
+        { "command",    "rule-edit"             },
+        { "channels",   nlohmann::json::array() },
+        { "events",     nlohmann::json::array() },
+        { "plugins",    nlohmann::json::array() },
+        { "servers",    nlohmann::json::array() }
+    });
+
+    for (const auto& pair : result) {
+        // Action.
+        if (pair.first == "-a" || pair.first == "--action")
+            json["action"] = pair.second;
+
+        // Additions.
+        if (pair.first == "-c" || pair.first == "--add-channel")
+            json["add-channels"].push_back(pair.second);
+        if (pair.first == "-e" || pair.first == "--add-event")
+            json["add-events"].push_back(pair.second);
+        if (pair.first == "-p" || pair.first == "--add-plugin")
+            json["add-plugins"].push_back(pair.second);
+        if (pair.first == "-s" || pair.first == "--add-server")
+            json["add-servers"].push_back(pair.second);
+
+        // Removals.
+        if (pair.first == "-C" || pair.first == "--remove-channel")
+            json["remove-channels"].push_back(pair.second);
+        if (pair.first == "-E" || pair.first == "--remove-event")
+            json["remove-events"].push_back(pair.second);
+        if (pair.first == "-P" || pair.first == "--remove-plugin")
+            json["remove-plugins"].push_back(pair.second);
+        if (pair.first == "-S" || pair.first == "--remove-server")
+            json["remove-servers"].push_back(pair.second);
+    }
+
+    // Index.
+    json["index"] = string_util::to_number<unsigned>(copy[0]);
+
+    request(ctl, json);
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/rule_edit_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * rule_edit_cli.hpp -- implementation of irccdctl rule-edit
+ *
+ * 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_RULE_EDIT_CLI_HPP
+#define IRCCD_RULE_EDIT_CLI_HPP
+
+/**
+ * \file rule_edit_cli.hpp
+ * \brief Implementation of irccdctl rule-edit.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl rule-edit.
+ */
+class rule_edit_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_RULE_EDIT_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/rule_info_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,87 @@
+/*
+ * rule_info_cli.cpp -- implementation of irccdctl rule-info
+ *
+ * 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 "rule_info_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+void rule_info_cli::print(const nlohmann::json& json, int index)
+{
+    assert(json.is_object());
+
+    auto unjoin = [] (auto array) {
+        std::ostringstream oss;
+
+        for (auto it = array.begin(); it != array.end(); ++it) {
+            if (!it->is_string())
+                continue;
+
+            oss << it->template get<std::string>() << " ";
+        }
+
+        return oss.str();
+    };
+    auto unstr = [] (auto action) {
+        if (action.is_string() && action == "accept")
+            return "accept";
+        else
+            return "drop";
+    };
+
+    std::cout << "rule:        " << index << std::endl;
+    std::cout << "servers:     " << unjoin(json["servers"]) << std::endl;
+    std::cout << "channels:    " << unjoin(json["channels"]) << std::endl;
+    std::cout << "plugins:     " << unjoin(json["plugins"]) << std::endl;
+    std::cout << "events:      " << unjoin(json["events"]) << std::endl;
+    std::cout << "action:      " << unstr(json["action"]) << std::endl;
+    std::cout << std::endl;
+}
+
+std::string rule_info_cli::name() const
+{
+    return "rule-info";
+}
+
+void rule_info_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    if (args.size() < 1)
+        throw std::invalid_argument("rule-info requires 1 argument");
+
+    int index = 0;
+
+    try {
+        index = std::stoi(args[0]);
+    } catch (...) {
+        throw std::invalid_argument("invalid number '" + args[0] + "'");
+    }
+
+    auto json = nlohmann::json::object({
+        { "command",    "rule-info" },
+        { "index",      index       }
+    });
+
+    request(ctl, std::move(json), [] (auto result) {
+        print(result, 0);
+    });
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/rule_info_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,61 @@
+/*
+ * rule_info_cli.hpp -- implementation of irccdctl rule-info
+ *
+ * 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_RULE_INFO_CLI_HPP
+#define IRCCD_RULE_INFO_CLI_HPP
+
+/**
+ * \file rule_info_cli.hpp
+ * \brief Implementation of irccdctl rule-info.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl rule-info.
+ */
+class rule_info_cli : public cli {
+public:
+    /**
+     * Pretty print a rule to stdout.
+     *
+     * \param json the rule information
+     * \param index the rule index
+     */
+    static void print(const nlohmann::json& json, int index = 0);
+
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_RULE_INFO_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/rule_list_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,45 @@
+/*
+ * rule_list_cli.cpp -- implementation of irccdctl rule-list
+ *
+ * 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 "rule_info_cli.hpp"
+#include "rule_list_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string rule_list_cli::name() const
+{
+    return "rule-list";
+}
+
+void rule_list_cli::exec(ctl::controller& ctl, const std::vector<std::string>&)
+{
+    request(ctl, {{ "command", "rule-list" }}, [] (auto result) {
+        auto pos = 0;
+
+        for (const auto& obj : result["list"]) {
+            if (obj.is_object())
+                rule_info_cli::print(obj, pos++);
+        }
+    });
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/rule_list_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * rule_list_cli.hpp -- implementation of irccdctl rule-list
+ *
+ * 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_RULE_LIST_CLI_HPP
+#define IRCCD_RULE_LIST_CLI_HPP
+
+/**
+ * \file rule_list_cli.hpp
+ * \brief Implementation of irccdctl rule-list.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl rule-list.
+ */
+class rule_list_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_RULE_LIST_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/rule_move_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,54 @@
+/*
+ * rule_move_cli.cpp -- implementation of irccdctl rule-move
+ *
+ * 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 "rule_move_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string rule_move_cli::name() const
+{
+    return "rule-move";
+}
+
+void rule_move_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    if (args.size() < 2)
+        throw std::invalid_argument("rule-move requires 2 arguments");
+
+    int from = 0;
+    int to = 0;
+
+    try {
+        from = std::stoi(args[0]);
+        to = std::stoi(args[1]);
+    } catch (...) {
+        throw std::invalid_argument("invalid number");
+    }
+
+    request(ctl, {
+        { "command",    "rule-move" },
+        { "from",       from        },
+        { "to",         to          }
+    });
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/rule_move_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * rule_move_cli.hpp -- implementation of irccdctl rule-move
+ *
+ * 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_RULE_MOVE_CLI_HPP
+#define IRCCD_RULE_MOVE_CLI_HPP
+
+/**
+ * \file rule_move_cli.hpp
+ * \brief Implementation of irccdctl rule-move.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl rule-move.
+ */
+class rule_move_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_RULE_MOVE_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/rule_remove_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,51 @@
+/*
+ * rule_remove_cli.cpp -- implementation of irccdctl rule-remove
+ *
+ * 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 "rule_remove_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string rule_remove_cli::name() const
+{
+    return "rule-remove";
+}
+
+void rule_remove_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    if (args.size() < 1)
+        throw std::invalid_argument("rule-remove requires 1 argument");
+
+    int index = 0;
+
+    try {
+        index = std::stoi(args[0]);
+    } catch (...) {
+        throw std::invalid_argument("invalid number '" + args[0] + "'");
+    }
+
+    request(ctl, {
+        { "command",    "rule-remove"   },
+        { "index",      index           }
+    });
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/rule_remove_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * rule_remove_cli.hpp -- implementation of irccdctl rule-remove
+ *
+ * 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_RULE_REMOVE_CLI_HPP
+#define IRCCD_RULE_REMOVE_CLI_HPP
+
+/**
+ * \file rule_remove_cli.hpp
+ * \brief Implementation of irccdctl rule-remove.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl rule-remove.
+ */
+class rule_remove_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_RULE_REMOVE_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_channel_mode_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,45 @@
+/*
+ * server_channel_mode_cli.cpp -- implementation of irccdctl server-cmode
+ *
+ * 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 "server_channel_mode_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string server_channel_mode_cli::name() const
+{
+    return "server-cmode";
+}
+
+void server_channel_mode_cli::exec(ctl::controller& ctl, const std::vector<std::string> &args)
+{
+    if (args.size() < 3)
+        throw std::invalid_argument("server-cmode requires 3 arguments");
+
+    request(ctl, {
+        { "command",    "server-cmode"  },
+        { "server",     args[0]         },
+        { "channel",    args[1]         },
+        { "mode",       args[2]         }
+    });
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_channel_mode_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * server_channel_mode_cli.hpp -- implementation of irccdctl server-cmode
+ *
+ * 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_SERVER_CHANNEL_MODE_CLI_HPP
+#define IRCCD_SERVER_CHANNEL_MODE_CLI_HPP
+
+/**
+ * \file server_channel_mode_cli.hpp
+ * \brief Implementation of irccdctl server-cmode.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl server-cmode.
+ */
+class server_channel_mode_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_SERVER_CHANNEL_MODE_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_channel_notice_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,45 @@
+/*
+ * server_channel_notice_cli.cpp -- implementation of irccdctl server-cnotice
+ *
+ * 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 "server_channel_notice_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string server_channel_notice_cli::name() const
+{
+    return "server-cnotice";
+}
+
+void server_channel_notice_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    if (args.size() < 3)
+        throw std::invalid_argument("server-cnotice requires 3 arguments");
+
+    request(ctl, {
+        { "command",    "server-cnotice"    },
+        { "server",     args[0]             },
+        { "channel",    args[1]             },
+        { "message",    args[2]             }
+    });
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_channel_notice_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * server_channel_notice_cli.hpp -- implementation of irccdctl server-cnotice
+ *
+ * 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_SERVER_CHANNEL_NOTICE_CLI_HPP
+#define IRCCD_SERVER_CHANNEL_NOTICE_CLI_HPP
+
+/**
+ * \file server_channel_notice_cli.hpp
+ * \brief Implementation of irccdctl server-cnotice.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl server-cnotice.
+ */
+class server_channel_notice_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_SERVER_CHANNEL_NOTICE_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_connect_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,95 @@
+/*
+ * server_connect_cli.cpp -- implementation of irccdctl server-connect
+ *
+ * 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/options.hpp>
+#include <irccd/string_util.hpp>
+
+#include "server_connect_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+namespace {
+
+option::result parse(std::vector<std::string> &args)
+{
+    option::options options{
+        { "-c",             true    },
+        { "--command",      true    },
+        { "-n",             true    },
+        { "--nickname",     true    },
+        { "-r",             true    },
+        { "--realname",     true    },
+        { "-S",             false   },
+        { "--ssl-verify",   false   },
+        { "-s",             false   },
+        { "--ssl",          false   },
+        { "-u",             true    },
+        { "--username",     true    }
+    };
+
+    return option::read(args, options);
+}
+
+} // !namespace
+
+std::string server_connect_cli::name() const
+{
+    return "server-connect";
+}
+
+void server_connect_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    std::vector<std::string> copy(args);
+
+    option::result result = parse(copy);
+    option::result::const_iterator it;
+
+    if (copy.size() < 2)
+        throw std::invalid_argument("server-connect requires at least 2 arguments");
+
+    auto object = nlohmann::json::object({
+        { "name", copy[0] },
+        { "host", copy[1] }
+    });
+
+    if (copy.size() == 3) {
+        if (!string_util::is_int(copy[2]))
+            throw std::invalid_argument("invalid port number");
+
+        object["port"] = std::stoi(copy[2]);
+    }
+
+    if (result.count("-S") > 0 || result.count("--ssl-verify") > 0)
+        object["sslVerify"] = true;
+    if (result.count("-s") > 0 || result.count("--ssl") > 0)
+        object["ssl"] = true;
+    if ((it = result.find("-n")) != result.end() || (it = result.find("--nickname")) != result.end())
+        object["nickname"] = it->second;
+    if ((it = result.find("-r")) != result.end() || (it = result.find("--realname")) != result.end())
+        object["realname"] = it->second;
+    if ((it = result.find("-u")) != result.end() || (it = result.find("--username")) != result.end())
+        object["username"] = it->second;
+
+    request(ctl, object);
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_connect_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * server_connect_cli.hpp -- implementation of irccdctl server-connect
+ *
+ * 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_SERVER_CONNECT_CLI_HPP
+#define IRCCD_SERVER_CONNECT_CLI_HPP
+
+/**
+ * \file server_connect_cli.hpp
+ * \brief Implementation of irccdctl server-connect.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl server-connect.
+ */
+class server_connect_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_SERVER_CONNECT_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_disconnect_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,44 @@
+/*
+ * server_disconnect_cli.cpp -- implementation of irccdctl server-disconnect
+ *
+ * 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 "server_disconnect_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string server_disconnect_cli::name() const
+{
+    return "server-disconnect";
+}
+
+void server_disconnect_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    auto object = nlohmann::json::object({
+        { "command", "server-disconnect" }
+    });
+
+    if (args.size() > 0)
+        object["server"] = args[0];
+
+    request(ctl, object);
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_disconnect_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * server_disconnect_cli.hpp -- implementation of irccdctl server-disconnect
+ *
+ * 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_SERVER_DISCONNECT_CLI_HPP
+#define IRCCD_SERVER_DISCONNECT_CLI_HPP
+
+/**
+ * \file server_disconnect_cli.hpp
+ * \brief Implementation of irccdctl server-disconnect.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl server-disconnect.
+ */
+class server_disconnect_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_SERVER_DISCONNECT_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_info_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,66 @@
+/*
+ * server_info_cli.cpp -- implementation of irccdctl server-info
+ *
+ * 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 "server_info_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string server_info_cli::name() const
+{
+    return "server-info";
+}
+
+void server_info_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    if (args.size() < 1)
+        throw std::invalid_argument("server-info requires 1 argument");
+
+    auto json = nlohmann::json::object({
+        { "command",    "server-info"   },
+        { "server",     args[0]         }
+    });
+
+    request(ctl, std::move(json), [] (auto result) {
+        std::cout << std::boolalpha;
+        std::cout << "Name           : " << json_util::pretty(result["name"]) << std::endl;
+        std::cout << "Host           : " << json_util::pretty(result["host"]) << std::endl;
+        std::cout << "Port           : " << json_util::pretty(result["port"]) << std::endl;
+        std::cout << "Ipv6           : " << json_util::pretty(result["ipv6"]) << std::endl;
+        std::cout << "SSL            : " << json_util::pretty(result["ssl"]) << std::endl;
+        std::cout << "SSL verified   : " << json_util::pretty(result["sslVerify"]) << std::endl;
+        std::cout << "Channels       : ";
+
+        for (const auto& v : result["channels"])
+            if (v.is_string())
+                std::cout << v.template get<std::string>() << " ";
+
+        std::cout << std::endl;
+
+        std::cout << "Nickname       : " << json_util::pretty(result["nickname"]) << std::endl;
+        std::cout << "User name      : " << json_util::pretty(result["username"]) << std::endl;
+        std::cout << "Real name      : " << json_util::pretty(result["realname"]) << std::endl;
+    });
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_info_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * server_info_cli.hpp -- implementation of irccdctl server-info
+ *
+ * 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_SERVER_INFO_CLI_HPP
+#define IRCCD_SERVER_INFO_CLI_HPP
+
+/**
+ * \file server_info_cli.hpp
+ * \brief Implementation of irccdctl server-info.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl server-info.
+ */
+class server_info_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_SERVER_INFO_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_invite_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,45 @@
+/*
+ * server_invite_cli.cpp -- implementation of irccdctl server-invite
+ *
+ * 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 "server_invite_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string server_invite_cli::name() const
+{
+    return "server-invite";
+}
+
+void server_invite_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    if (args.size() < 3)
+        throw std::invalid_argument("server-invite requires 3 arguments");
+
+    request(ctl, {
+        { "command",    "server-invite" },
+        { "server",     args[0]         },
+        { "target",     args[1]         },
+        { "channel",    args[2]         }
+    });
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_invite_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * server_invite_cli.hpp -- implementation of irccdctl server-invite
+ *
+ * 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_SERVER_INVITE_CLI_HPP
+#define IRCCD_SERVER_INVITE_CLI_HPP
+
+/**
+ * \file server_invite_cli.hpp
+ * \brief Implementation of irccdctl server-invite.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl server-invite.
+ */
+class server_invite_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_SERVER_INVITE_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_join_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,48 @@
+/*
+ * server_join_cli.cpp -- implementation of irccdctl server-join
+ *
+ * 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 "server_join_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string server_join_cli::name() const
+{
+    return "server-join";
+}
+
+void server_join_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    if (args.size() < 2)
+        throw std::invalid_argument("server-join requires at least 2 arguments");
+
+    auto object = nlohmann::json::object({
+        { "server",     args[0]         },
+        { "channel",    args[1]         }
+    });
+
+    if (args.size() == 3)
+        object["password"] = args[2];
+
+    request(ctl, object);
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_join_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * server_join_cli.hpp -- implementation of irccdctl server-join
+ *
+ * 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_SERVER_JOIN_CLI_HPP
+#define IRCCD_SERVER_JOIN_CLI_HPP
+
+/**
+ * \file server_join_cli.hpp
+ * \brief Implementation of irccdctl server-join.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl server-join.
+ */
+class server_join_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_SERVER_JOIN_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_kick_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,49 @@
+/*
+ * server_kick_cli.cpp -- implementation of irccdctl server-kick
+ *
+ * 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 "server_kick_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string server_kick_cli::name() const
+{
+    return "server-kick";
+}
+
+void server_kick_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    if (args.size() < 3)
+        throw std::invalid_argument("server-kick requires at least 3 arguments ");
+
+    auto object = nlohmann::json::object({
+        { "server",     args[0] },
+        { "target",     args[1] },
+        { "channel",    args[2] }
+    });
+
+    if (args.size() == 4)
+        object["reason"] = args[3];
+
+    request(ctl, object);
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_kick_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * server_kick_cli.hpp -- implementation of irccdctl server-kick
+ *
+ * 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_SERVER_KICK_CLI_HPP
+#define IRCCD_SERVER_KICK_CLI_HPP
+
+/**
+ * \file server_kick_cli.hpp
+ * \brief Implementation of irccdctl server-kick.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl server-kick.
+ */
+class server_kick_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_SERVER_KICK_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_list_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,41 @@
+/*
+ * server_list_cli.cpp -- implementation of irccdctl server-list
+ *
+ * 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 "server_list_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string server_list_cli::name() const
+{
+    return "server-list";
+}
+
+void server_list_cli::exec(ctl::controller& ctl, const std::vector<std::string>&)
+{
+    request(ctl, {{ "command", "server-list" }}, [] (auto result) {
+        for (const auto& n : result["list"])
+            if (n.is_string())
+                std::cout << n.template get<std::string>() << std::endl;
+    });
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_list_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * server_list_cli.hpp -- implementation of irccdctl server-list
+ *
+ * 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_SERVER_LIST_CLI_HPP
+#define IRCCD_SERVER_LIST_CLI_HPP
+
+/**
+ * \file server_list_cli.hpp
+ * \brief Implementation of irccdctl server-list.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl server-list.
+ */
+class server_list_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_SERVER_LIST_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_me_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,44 @@
+/*
+ * server_me_cli.cpp -- implementation of irccdctl server-me
+ *
+ * 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 "server_me_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string server_me_cli::name() const
+{
+    return "server-me";
+}
+
+void server_me_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    if (args.size() < 3)
+        throw std::runtime_error("server-me requires 3 arguments");
+
+    request(ctl, {
+        { "server",     args[0]     },
+        { "target",     args[1]     },
+        { "message",    args[2]     }
+    });
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_me_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * server_me_cli.hpp -- implementation of irccdctl server-me
+ *
+ * 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_SERVER_ME_CLI_HPP
+#define IRCCD_SERVER_ME_CLI_HPP
+
+/**
+ * \file server_me_cli.hpp
+ * \brief Implementation of irccdctl server-me.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl server-me.
+ */
+class server_me_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_SERVER_ME_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_message_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,44 @@
+/*
+ * server_message_cli.cpp -- implementation of irccdctl server-message
+ *
+ * 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 "server_message_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string server_message_cli::name() const
+{
+    return "server-message";
+}
+
+void server_message_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    if (args.size() < 3)
+        throw std::invalid_argument("server-message requires 3 arguments");
+
+    request(ctl, {
+        { "server",     args[0] },
+        { "target",     args[1] },
+        { "message",    args[2] }
+    });
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_message_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * server_message_cli.hpp -- implementation of irccdctl server-message
+ *
+ * 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_SERVER_MESSAGE_CLI_HPP
+#define IRCCD_SERVER_MESSAGE_CLI_HPP
+
+/**
+ * \file server_message_cli.hpp
+ * \brief Implementation of irccdctl server-message.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl server-message.
+ */
+class server_message_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_SERVER_MESSAGE_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_mode_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,43 @@
+/*
+ * server_mode_cli.cpp -- implementation of irccdctl server-mode
+ *
+ * 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 "server_mode_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string server_mode_cli::name() const
+{
+    return "server-mode";
+}
+
+void server_mode_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    if (args.size() < 2)
+        throw std::invalid_argument("server-mode requires 2 arguments");
+
+    request(ctl, {
+        { "server", args[0] },
+        { "mode",   args[1] }
+    });
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_mode_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * server_mode_cli.hpp -- implementation of irccdctl server-mode
+ *
+ * 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_SERVER_MODE_CLI_HPP
+#define IRCCD_SERVER_MODE_CLI_HPP
+
+/**
+ * \file server_mode_cli.hpp
+ * \brief Implementation of irccdctl server-mode.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl server-mode.
+ */
+class server_mode_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_SERVER_MODE_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_nick_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,43 @@
+/*
+ * server_nick_cli.cpp -- implementation of irccdctl server-nick
+ *
+ * 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 "server_nick_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string server_nick_cli::name() const
+{
+    return "server-nick";
+}
+
+void server_nick_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    if (args.size() < 2)
+        throw std::invalid_argument("server-nick requires 2 arguments");
+
+    request(ctl, {
+        { "server",     args[0] },
+        { "nickname",   args[1] }
+    });
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_nick_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * server_nick_cli.hpp -- implementation of irccdctl server-nick
+ *
+ * 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_SERVER_NICK_CLI_HPP
+#define IRCCD_SERVER_NICK_CLI_HPP
+
+/**
+ * \file server_nick_cli.hpp
+ * \brief Implementation of irccdctl server-nick.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl server-nick.
+ */
+class server_nick_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_SERVER_NICK_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_notice_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,44 @@
+/*
+ * server_notice_cli.cpp -- implementation of irccdctl server-notice
+ *
+ * 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 "server_notice_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string server_notice_cli::name() const
+{
+    return "server-notice";
+}
+
+void server_notice_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    if (args.size() < 3)
+        throw std::invalid_argument("server-notice requires 3 arguments");
+
+    request(ctl, {
+        { "server",     args[0] },
+        { "target",     args[1] },
+        { "message",    args[2] }
+    });
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_notice_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * server_notice_cli.hpp -- implementation of irccdctl server-notice
+ *
+ * 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_SERVER_NOTICE_CLI_HPP
+#define IRCCD_SERVER_NOTICE_CLI_HPP
+
+/**
+ * \file server_notice_cli.hpp
+ * \brief Implementation of irccdctl server-notice.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl server-notice.
+ */
+class server_notice_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_SERVER_NOTICE_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_part_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,48 @@
+/*
+ * server_part_cli.cpp -- implementation of irccdctl server-part
+ *
+ * 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 "server_part_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string server_part_cli::name() const
+{
+    return "server-part";
+}
+
+void server_part_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    if (args.size() < 2)
+        throw std::invalid_argument("server-part requires at least 2 arguments");
+
+    auto object = nlohmann::json::object({
+        { "server",     args[0] },
+        { "channel",    args[1] }
+    });
+
+    if (args.size() >= 3)
+        object["reason"] = args[2];
+
+    request(ctl, object);
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_part_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * server_part_cli.hpp -- implementation of irccdctl server-part
+ *
+ * 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_SERVER_PART_CLI_HPP
+#define IRCCD_SERVER_PART_CLI_HPP
+
+/**
+ * \file server_part_cli.hpp
+ * \brief Implementation of irccdctl server-part.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl server-part.
+ */
+class server_part_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_SERVER_PART_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_reconnect_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,44 @@
+/*
+ * server_reconnect_cli.cpp -- implementation of irccdctl server-reconnect
+ *
+ * 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 "server_reconnect_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string server_reconnect_cli::name() const
+{
+    return "server-reconnect";
+}
+
+void server_reconnect_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    auto object = nlohmann::json::object({
+        { "command", "server-reconnect" }
+    });
+
+    if (args.size() >= 1)
+        object["server"] = args[0];
+
+    request(ctl, object);
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_reconnect_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * server_reconnect_cli.hpp -- implementation of irccdctl server-reconnect
+ *
+ * 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_SERVER_RECONNECT_CLI_HPP
+#define IRCCD_SERVER_RECONNECT_CLI_HPP
+
+/**
+ * \file server_reconnect_cli.hpp
+ * \brief Implementation of irccdctl server-reconnect.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl server-reconnect.
+ */
+class server_reconnect_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_SERVER_RECONNECT_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_topic_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,44 @@
+/*
+ * server_topic_cli.cpp -- implementation of irccdctl server-topic
+ *
+ * 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 "server_topic_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+std::string server_topic_cli::name() const
+{
+    return "server-topic";
+}
+
+void server_topic_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    if (args.size() < 3)
+        throw std::invalid_argument("server-topic requires 3 arguments");
+
+    request(ctl, {
+        { "server",     args[0] },
+        { "channel",    args[1] },
+        { "topic",      args[2] }
+    });
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/server_topic_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * server_topic_cli.hpp -- implementation of irccdctl server-topic
+ *
+ * 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_SERVER_TOPIC_CLI_HPP
+#define IRCCD_SERVER_TOPIC_CLI_HPP
+
+/**
+ * \file server_topic_cli.hpp
+ * \brief Implementation of irccdctl server-topic.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl server-topic.
+ */
+class server_topic_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_SERVER_TOPIC_CLI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/watch_cli.cpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,247 @@
+/*
+ * watch_cli.cpp -- implementation of irccdctl watch
+ *
+ * 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 <functional>
+#include <unordered_map>
+
+#include <irccd/json_util.hpp>
+#include <irccd/options.hpp>
+
+#include <irccd/ctl/controller.hpp>
+
+#include <boost/system/system_error.hpp>
+
+#include "watch_cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+namespace {
+
+std::string format(std::vector<std::string> args)
+{
+    auto result = option::read(args, {
+        { "-f",         true },
+        { "--format",   true }
+    });
+
+    if (result.count("-f") > 0)
+        return result.find("-f")->second;
+    if (result.count("--format") > 0)
+        return result.find("--format")->second;
+
+    return "native";
+}
+
+void onChannelMode(const nlohmann::json &v)
+{
+    std::cout << "event:       onChannelMode\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "mode:        " << json_util::pretty(v, "mode") << "\n";
+    std::cout << "argument:    " << json_util::pretty(v, "argument") << "\n";
+}
+
+void onChannelNotice(const nlohmann::json &v)
+{
+    std::cout << "event:       onChannelNotice\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
+    std::cout << "message:     " << json_util::pretty(v, "message") << "\n";
+}
+
+void onConnect(const nlohmann::json &v)
+{
+    std::cout << "event:       onConnect\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+}
+
+void onInvite(const nlohmann::json &v)
+{
+    std::cout << "event:       onInvite\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
+}
+
+void onJoin(const nlohmann::json &v)
+{
+    std::cout << "event:       onJoin\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
+}
+
+void onKick(const nlohmann::json &v)
+{
+    std::cout << "event:       onKick\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
+    std::cout << "target:      " << json_util::pretty(v, "target") << "\n";
+    std::cout << "reason:      " << json_util::pretty(v, "reason") << "\n";
+}
+
+void onMessage(const nlohmann::json &v)
+{
+    std::cout << "event:       onMessage\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
+    std::cout << "message:     " << json_util::pretty(v, "message") << "\n";
+}
+
+void onMe(const nlohmann::json &v)
+{
+    std::cout << "event:       onMe\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "target:      " << json_util::pretty(v, "target") << "\n";
+    std::cout << "message:     " << json_util::pretty(v, "message") << "\n";
+}
+
+void onMode(const nlohmann::json &v)
+{
+    std::cout << "event:       onMode\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "mode:        " << json_util::pretty(v, "mode") << "\n";
+}
+
+void onNames(const nlohmann::json &v)
+{
+    std::cout << "event:       onNames\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
+    std::cout << "names:       " << json_util::pretty(v, "names") << "\n";
+}
+
+void onNick(const nlohmann::json &v)
+{
+    std::cout << "event:       onNick\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "nickname:    " << json_util::pretty(v, "nickname") << "\n";
+}
+
+void onNotice(const nlohmann::json &v)
+{
+    std::cout << "event:       onNotice\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "message:     " << json_util::pretty(v, "message") << "\n";
+}
+
+void onPart(const nlohmann::json &v)
+{
+    std::cout << "event:       onPart\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
+    std::cout << "reason:      " << json_util::pretty(v, "reason") << "\n";
+}
+
+void onQuery(const nlohmann::json &v)
+{
+    std::cout << "event:       onQuery\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "message:     " << json_util::pretty(v, "message") << "\n";
+}
+
+void onTopic(const nlohmann::json &v)
+{
+    std::cout << "event:       onTopic\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
+    std::cout << "topic:       " << json_util::pretty(v, "topic") << "\n";
+}
+
+void onWhois(const nlohmann::json &v)
+{
+    std::cout << "event:       onWhois\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "nickname:    " << json_util::pretty(v, "nickname") << "\n";
+    std::cout << "username:    " << json_util::pretty(v, "username") << "\n";
+    std::cout << "host:        " << json_util::pretty(v, "host") << "\n";
+    std::cout << "realname:    " << json_util::pretty(v, "realname") << "\n";
+}
+
+const std::unordered_map<std::string, std::function<void (const nlohmann::json&)>> events{
+    { "onChannelMode",      onChannelMode   },
+    { "onChannelNotice",    onChannelNotice },
+    { "onConnect",          onConnect       },
+    { "onInvite",           onInvite        },
+    { "onJoin",             onJoin          },
+    { "onKick",             onKick          },
+    { "onMessage",          onMessage       },
+    { "onMe",               onMe            },
+    { "onMode",             onMode          },
+    { "onNames",            onNames         },
+    { "onNick",             onNick          },
+    { "onNotice",           onNotice        },
+    { "onPart",             onPart          },
+    { "onQuery",            onQuery         },
+    { "onTopic",            onTopic         },
+    { "onWhois",            onWhois         }
+};
+
+void get_event(ctl::controller& ctl, std::string fmt)
+{
+    ctl.recv([&ctl, fmt] (auto code, auto message) {
+        if (code)
+            throw boost::system::system_error(code);
+
+        auto it = events.find(json_util::to_string(message["event"]));
+
+        if (it != events.end()) {
+            if (fmt == "json")
+                std::cout << message.dump(4) << std::endl;
+            else {
+                it->second(message);
+                std::cout << std::endl;
+            }
+        }
+
+        get_event(ctl, std::move(fmt));
+    });
+}
+
+} // !namespace
+
+std::string watch_cli::name() const
+{
+    return "watch";
+}
+
+void watch_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args)
+{
+    auto fmt = format(args);
+
+    if (fmt != "native" && fmt != "json")
+        throw std::invalid_argument("invalid format given: " + fmt);
+
+    get_event(ctl, fmt);
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/watch_cli.hpp	Thu Nov 16 23:12:45 2017 +0100
@@ -0,0 +1,53 @@
+/*
+ * watch_cli.hpp -- implementation of irccdctl watch
+ *
+ * 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_WATCH_CLI_HPP
+#define IRCCD_WATCH_CLI_HPP
+
+/**
+ * \file watch_cli.hpp
+ * \brief Implementation of irccdctl watch.
+ */
+
+#include "cli.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Implementation of irccdctl watch.
+ */
+class watch_cli : public cli {
+public:
+    /**
+     * \copydoc cli::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc cli::exec
+     */
+    void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override;
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_WATCH_CLI_HPP