Adding prosodyctl shell commands

Prosody allows modules to easily expose commands to its interactive shell. These commands allow server admins to interact with your module while Prosody is running, and can be used to perform various actions or retrieve information.

Adding a new command

Using the module:add_item() API, add a shell-command item for each command you want your module to provide.

Here is an example of the announce:online() command from Prosody’s mod_announce:

module:add_item("shell-command", {
    section = "announce";
    section_desc = "Broadcast announcements to users";
    name = "online";
    desc = "Send announcement to all online users on the host";
    args = {
        { name = "host", type = "string" };
        { name = "text", type = "string" };
    };
    host_selector = "host";
    handler = function(shell, host, text) --luacheck: ignore 212/shell
        local msg = st.message({ from = host, id = id.short(), type = "headline" })
            :text_tag("body", text);
        local count = send_to_online(msg, host);
        return true, ("Announcement sent to %d users"):format(count);
    end;
});
section
The section the command should be in. Either choose an existing section, or make a new one for your module.
section_desc
Descriptive text to tell the admin what this section contains.
name
The name of your command (excluding the section).
desc
A description of what your command can be used for.
args
Arguments (parameters) accepted by your command. Structure described below.
host_selector
If your module is not global, you must set this to the name of one of your parameters, which allows Prosody to determine which instance of your module to route the command invocation to. More information below.
handler
A function which will be executed when the admin invokes the command. Parameters are explained below.

Host selection

Prosody supports multiple “virtual hosts” within a single server process. Each virtual host runs its own copy of a module. This means that when a command is invoked, Prosody needs to decide which host it should be executed on.

To do this, you can specify one of the parameters as a “host selector”. Prosody will interpret this as a JID (which may be only a domain), and use the extracted domain to determine which virtual host to use.

Global modules (i.e. those that call module:set_global()) are only loaded once per Prosody instance, and do not need a host selector parameter.

Argument specification

The args field should be an array of tables, each describing a parameter passed to your command.

args = {
    { name = "parameter_name" };
};

This is used when generating the automatic help text for your command. Prosody does not currently perform any additional validation of parameters for you.

Additionally, when executing a command via prosodyctl shell’s non-interactive shortcut mode, the user can specify command-line options which get passed to the handler. To enable this, specify a flags field which follows the util.argparse format:

flags = {
    -- The --role and --group options can be specified multiple times
    array_params = { role = true, group = true };
    -- The --expires-after option accepts a value
    value_params = { expires_after = true };
};

Handler function

The handler function is executed to actually run the command:

handler = function (shell, parameter_name, opts)
    -- Do stuff here
end;

The first parameter, shell, is always passed, and contains information about the current shell session and command execution environment.

In particular, shell.session.print() can be used to output text to the user during the command execution.

After shell, any parameters specified by the user are passed.

Finally, if the command was executed via prosodyctl shell’s non-interactive shortcut mode and there was a flags field in the command config, there will be an additional opts parameter containing any parsed flags/options, passed after the normal parameters. Handler functions should gracefully handle opts being nil (as it will typically be when invoked via an interactive shell session).

Return value

All commands should return two values: success, message

The success value should be true if the command completed successfully, and nil or false if the command failed. In either case, message value should contain text which will be shown to the user to inform them of the result (i.e. a success or error message).

If the command includes asynchronous elements, it can return a promise. When the promise is settled, status is taken to be true if the promise resolved, or false if the promise was rejected. The resolved value or the rejection reason are used for the message value.

The shell parameter

This parameter contains information relating to the current shell session. Although many of the fields are for Prosody’s own internal use, some will be of general use to module developers.

  • shell.session.print() can be used to send output text to the user while the command is in progress (if you only have one thing to say, you can return it as the status message at the end instead).
  • shell.repl is a boolean which indicates whether this session is using the interactive shell mode or whether it is executed directly via prosodyctl.
  • shell.session.width contains the width of the user’s terminal in columns, if known.
  • shell.session.request_input("password") prompts the user for a password and returns a promise that resolves to the user’s input. This is useful to avoid requiring the user to pass sensitive passwords as parameters.