RoT://Terminal
PACKAGE SDK  ·  SERVER-SIDE ONLY  ·  v1.22

Write a package.
Ship a command.

RoT Terminal packages run entirely on the server via loadstring. The client never sees your code — only the text you print through api. Build a command, register it, publish it.

Read the SDK › Open RoTerminal
RoT Terminal — session
> pkg install rotui
Installed rotui (1.22)
> pkg run myapp -rotui
Opening window "My App"…
> mytool
Hello!
> _
Concept

Everything you write is a message, not a machine

Your package never touches the client directly. It runs on the server, and the only thing that ever reaches a player's screen is text your code prints, or a UI description your code sends.

01

You write run()

A single function that receives args and an api table. This is the entire surface of your package.

02

Server executes it

Every call runs server-side via loadstring. No client ever sees or downloads your source.

03

Player sees output

Only what you print with api.println, or a window built with api.ui, reaches the terminal.

Reference

SDK

Two SDKs cover the whole package surface: the Basic SDK for commands and output, and RoT UI for building windows.

Core rule: package code runs only on the server (via loadstring). The client never receives your code — only the text you print through api.

Package format (main file / entry point)

return {
  command = "myapp",           -- the command name in the terminal
  run = function(args, api)
    api.println("Hello!", api.COLOR_OK)
  end,
}

API table (passed into run)

MemberDescription
api.print(text, level)Print without a line break.
api.println(text, level)Print a line. level: nil / "ok" / "err" / "info" / "dim"
api.playerThe Player who ran the command.
api.require(fileName)Load another file from this same package.
api.reg(cmdName, argsTable)Register a PATH alias (see below).
api.ui(description)Show a RoT UI window (see RoT UI SDK tab).

Multiple files on upload (pkg create)

Paste the first file's code (main), then end it with:

END1 Main

Paste the second file, end it with:

END2 Helper

…and so on for files in the middle of the upload.

The last file (or the only file, if there's just one) ends the entire submission. For that one, write simply:

END

or with a name:

END main.lua

Either form sends the package — no number is needed here, this is not "END3".

Inside main, pull in other files like this:

local helper = api.require("Helper")

reg — path alias

A package can register itself an additional command name, but only from inside its own run():

api.reg("mytool", {"-inject", "pkg", "run", "myapp"})

After this, a player can type mytool instead of pkg run myapp.

! Important: reg only works from inside package code. If a player types reg -path ... directly into the terminal themselves, they get "Insufficient permissions." That's not a bug — it's the protection working as intended.

Forbidden in package code (checked at pkg create, before moderation)

DataStoreService HttpService KeyLocker / secrets RemoteEvent RemoteFunction BindableEvent ReplicatedStorage ServerScriptService loadstring getfenv / setfenv

Publishing commands

CommandDescription
pkg create <name> <version> <short description>Publish a new package.
pkg create -update <name> <version> <short description>Publish a new version.
pkg create -delete <name> [version]Remove globally (author / moderator only).
pkg delete <name> [version]Remove from your own account.
pkg install <name> [version]Install permanently.
pkg run <name> [version]Execute (or just run the command directly).
pkg readyList what you have installed.
pkg listList everything in the catalog.

RoT UI is the equivalent of WinUI for Windows apps, but for terminal packages. Since your code runs on the server, you can't call Instance.new("Frame") yourself — instead you describe a window as a plain Lua table, and api.ui() sends that description to the player. The terminal client does the actual drawing, the same way the -gui moderation panel renders package cards: you don't build the Frame by hand, you say "here's what should exist," and the terminal draws it.

Requirement

A package must require rotui if it wants to build UI. Check this yourself at the start of run() — if the player doesn't have rotui installed, tell them clearly (see example below).

! If a UI package is run without rotui, the standard response is:
For run this package: "<pkg name>", run with -rotui[_version] or install rotui (1)

Basic example

return {
  command = "myapp",
  run = function(args, api)
    if args[1] ~= "-rotui" and args[1] ~= "-rotui_1.22" then
      api.println(
        'For run this package: "myapp", run with -rotui[_version] or install rotui (1)',
        api.COLOR_ERR
      )
      return
    end

    local rotui = api.require("rotui") -- if installed as a dependency

    api.ui({
      type = "window",
      title = "My App",
      children = {
        { type = "label", text = "Hello!" },
        { type = "button", text = "Click", onClick = "onPress" },
      },
    })
  end,
}

Window description schema

{
  type = "window",
  title = "Window title",
  children = { <widget>, <widget>, ... }
}

Widgets

{ type = "label",  text = "text" }

{ type = "button", text = "text", onClick = "handlerName" }

onClick is a string (a handler name), not a function! On click, the server calls your run() again with:

args = {"-rotui-event", "handlerName"}

Handle it yourself:

run = function(args, api)
  if args[1] == "-rotui-event" then
    if args[2] == "onPress" then
      api.println("Button clicked!", api.COLOR_OK)
    end
    return
  end
  -- ...normal run continues here...
end

Running a RoT UI app as a player

pkg install rotuiOnce, ahead of time.
pkg run <app> -rotui_1.22Run a specific version.
pkg run <app> -rotuiRun any available version.

In-terminal reference

pkg run rotui -sdk   -- the same cheat sheet, straight from the game
Boundaries

What you can't touch

The sandbox is checked before moderation ever looks at your package. Trip one of these and the upload never goes out: DataStoreService, HttpService, remote events of any kind, replicated/server storage, and raw loadstring. Your package speaks to players through api, and nothing else.