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.luaThe edit/reload loop
Section titled “The edit/reload loop”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/bindslist what’s registered, including thefile:linethat registered it
A first script
Section titled “A first script”-- ~/.config/rune/init.lua
-- An alias: "hp" sends the heal commandrune.alias.exact("hp", "cast 'heal' self")
-- A trigger: highlight incoming hits by rewriting the linerune.trigger.contains("You are hit", function(matches, ctx) return rune.style.red(ctx.line:clean())end)
-- A timer: save every minuterune.timer.every(60, "save", { name = "autosave" })Save, then /reload.
Growing past one file
Section titled “Growing past one file”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.luarequire("combat") -- runs combat.luarequire("ui") -- runs ui.luaA required file is plain Lua that runs top to bottom. Put registrations in
it directly; no module table or return is needed:
-- combat.luarune.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
picked up on the next /reload just like edits to init.lua.
A map of the API
Section titled “A map of the API”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 | Triggers | 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.
The client itself is Lua
Section titled “The client itself is Lua”The API above isn’t a plugin surface bolted onto the side; the client’s
own behavior is built with it. The input pipeline, local echo, the default
keymap, and the status bar are Lua scripts embedded in the binary,
registered through the same hooks your init.lua uses. For example, the
> prefix on echoed commands is a hook handler, so you can replace it:
rune.hooks.on("echo", function(text) return rune.style.cyan("» " .. text)end, { priority = 50 }) -- runs before the default handlerAnything the client does through this API, your scripts can override.
The Scripting Model explains the machinery every registration shares. Coming from TinTin++ or Mudlet? Start with Migrating from Other Clients.