Orbital Frame

Unleash the power of UNIX in your chatbot!

Orbital Frame is a framework for building chatbots which support UNIX-like concepts such as:

  • Commands
  • Jobs
  • Pipes
  • Signals
  • Variables
  • Scripting

It is designed to run basically everywhere through the use of adapters. Though it was built with chat services such as Slack and Discord in mind, it can even run on the web as in the demo below which is running entirely client side. It features a robust plugin system for further extending bot capabilities beyond what's possible via functions. For the full project description, see the README.

The demo below is Orbital Frame running on a custom web adapter with interaction provided through a React terminal emulator. The bot commands are provided by @orbital-frame/core-commands which are a set of basic commands to help you get up and running quickly. If you're looking for a prebuilt bot which is able to run on most chat services, check out @orbital-frame/jehuty which is configured to run on Hubot. A description of capabilities and examples are given below.

Orbital Frame Ifrit
ifrit>

Examples

@ifrit help
@ifrit help choose
@ifrit choose -n 2 one two three four

Commands

Commands can be called with arguments and options. The options and option types a command accepts are provided by the command's author in the command definition. A detailed explanation for command authors is provided here.

@ifrit echo hello

Options

Command options can either be valued or boolean, which is defined in the command itself.Command options can be either short form or long form:

some_command --long_option option_value
# or
some_command -s option_value

Unlike long options, short options can be chained. In this example, here, a, b, c, and d are all options. a, b, and c are boolean options while d is being passed the argument "arg".

some_command -abcd arg

Interpolations

Commands can be immediately evaluated for use as arguments, option values, etc. by surrounding the command or pipeline with $():

@ifrit echo "three plus two is " $(calc 3 + 2)

Interactions

Some commands start an interactive session where the command can receive nonblocking input throughout its lifespan. The interaction character is configurable based on your bot but defaults to >. For full details, see documentation in the @orbital-frame/core README:

@ifrit interact

Jobs

When user input is entered it is assigned to a job. A job is in one of four states:

    Pending
    A job begins its lifecycle in the pending state.
    Running
    Once a job begins execution, it is moved to the running state and remains there until it is either fulfilled or rejected.
    Fulfilled
    Upon success, a job moves to the terminal fulfilled state.
    Rejected
    Upon error, a job moves to the terminal rejected state.

Along with its current state, a job contains its ID, a user-local ID, the ID of the user who started the job, the job's context which is used for interaction with the chat service, a command object for the command that belongs to the job, the source code input by the user which spawned the job, the date the job was started, the date the job was finished (or null if the job hasn't reached a terminal state), and the job's output if it is in a finished state.

Foregrounding

Because multiple interactive commands can be run at once, you may want to change which job is foregrounded. If you're using @orbital-frame/core-commands, there is a bundled fg command to handle this for you. Otherwise, the source is terse enough to add it yourself:

export default ({ jobService, interactionService }) => ({ name: 'fg', synopsis: 'fg [JOB ID]', description: 'Foreground an interactive job', async execute ([ jobId ]) { const { userId } = await jobService.findOne({ 'command.pid': this.pid }) await interactionService.foreground(userId, jobId) } })

Pipes

Pipes are pipelines of commands (or functions) who pass their output as input into the next pipe.

@ifrit echo category | split -d '' | head -n 3 | join -g ''

Variables

Variables are key/value pairs:

@ifrit MY_VAR="a variable"; echo $MY_VAR

Signals

Commands can be written to respond to signals, such as signals to pause or exit immediately. For full documentation, see the entry in the @orbital-frame/core README.

Functions

Like Bash, the Orbital Frame command line supports two forms of functions which are equivalent in the AST so it's a matter of your personal style:

Form 1

function my_function { echo "This is form 1" }

Form 2

my_function () { echo "this is form 2" }

Like Bash, function arguments are given through positional environment variables $1 through $n, so even in the second form where parentheses are used, no parameters can be listed.

@ifrit function say_hello { echo "Hello, " $1 }

Scoping

By default, all variables are declared in the global scope. If you want to instead use lexical scoping, use the local keyword inside your function block:

MY_VAR=outer function set_var { local MY_VAR=inner echo $MY_VAR } set_var # echoes "inner" echo $MY_VAR # echoes "outer"

Control Structures

To keep the syntax simple, the Orbital Frame grammar does not include any control structures such as if statements or loops but these can be easily replicated using commands. @orbital-frame/core-commands includes the following logical commands:

  • and
  • or
  • not
  • true
  • false
  • if

Here is an additional example using commands loaded into ifrit which demonstrates how to do branching using the if and and commands rather than dedicated control structures:

@ifrit function analyze_length { local WORD=$1 local LOWER=$2 local UPPER=$3 local WORD_LENGTH=$(split -d '' $WORD | length) if $(and $(greater-than $WORD_LENGTH $LOWER) $(less-than $WORD_LENGTH $UPPER)) "String is valid" "String is invalid" }