Skip to content

Scripting Basics

Rune is configured in Lua. There is no separate trigger syntax or settings file. One script runs at startup, and everything is registered from it:

~/.config/rune/init.lua

Edit init.lua, type /reload, keep playing.

/reload throws away the Lua state and runs your scripts again from scratch. A script error doesn’t take the client down: it’s reported with its file:line, and the rest of your config loads normally. Fix the line and /reload again.

Two commands are useful while iterating:

  • /lua <code> runs a one-liner without touching a file: /lua rune.echo(rune.state.address)
  • /aliases, /triggers, /timers, /hooks, and /binds list what’s registered, including the file:line that registered it
-- ~/.config/rune/init.lua
-- An alias: "hp" sends the heal command
rune.alias.exact("hp", "cast 'heal' self")
-- A trigger: highlight incoming hits by rewriting the line
rune.trigger.contains("You are hit", function(matches, ctx)
return rune.style.red(ctx.line:clean())
end)
-- A timer: save every minute
rune.timer.every(60, "save", { name = "autosave" })

Save, then /reload.

When init.lua gets long, split it up with require(). Paths resolve relative to the requiring script, so files next to init.lua load without any path setup:

~/.config/rune/
├── init.lua
├── combat.lua
└── ui.lua
-- init.lua
require("combat") -- runs combat.lua
require("ui") -- runs ui.lua

A required file is plain Lua that runs top to bottom. Put registrations in it directly; no module table or return is needed:

-- combat.lua
rune.alias.exact("k", "kill target")
rune.trigger.contains("flees in panic", "kill target")

Since /reload rebuilds the whole Lua state, edits to required files are loaded on /reload, along with init.lua.

The input hooks, local echo, default keymap, and status bar are Lua scripts embedded in the binary. They use the same registration functions as your init.lua. For example, replace the default > prefix on echoed commands with an echo hook:

rune.hooks.on("echo", function(text)
return rune.style.cyan("» " .. text)
end, { priority = 50 }) -- runs before the default handler

Anything the client does through this API, your scripts can override.

Everything lives under the rune table. Find the task, follow the link:

To do this Guide Full signatures
React to server output Triggers rune.trigger
Shorten commands you type Aliases rune.alias
Run something later, or on a schedule Timers rune.timer
Intercept input, output, and client events Hooks & Events rune.hooks
Bind keys Key Bindings rune.bind
Add your own /commands Custom Commands rune.command
Handle GMCP data GMCP rune.gmcp
Keep data across reloads or restarts Storage & Worlds Storage
Toggle sets of things at once Groups rune.group
Lay out panes, bars, and pickers Layout & UI rune.ui, rune.pane
Log the session to a file Logging rune.log
Color and style text rune.style

The registration functions all behave the same way — handles, a shared options table, groups, and error quarantine. The Scripting Model covers that machinery once, and the API reference has full signatures for every rune.* namespace.

Read Triggers for pattern matching and The Scripting Model for registration options and handles. For TinTin++ and Mudlet equivalents, see Migrating from Other Clients.