rune.hooks
Hooks attach handlers to client events: input, output, connection lifecycle, GMCP, and more. For a task-oriented introduction, see Hooks & Events.
Quick reference
Section titled “Quick reference”rune.hooks.on(event, handler, opts?) -- attach a handler to an eventrune.hooks.enable(name) -- re-enable a named handlerrune.hooks.disable(name) -- disable without unregisteringrune.hooks.remove(name) -- unregister a named handlerrune.hooks.list() -- all handlers with event, priority, sourcerune.hooks.clear(event?) -- remove handlers for one event, or allrune.hooks.has(event) -- true if the event has handlersrune.hooks.count(event?) -- handlers for one event, or totalrune.hooks.remove_group(group) -- remove all handlers in a groupon returns a handle and accepts the
common options (name, group, priority).
rune.hooks.on
Section titled “rune.hooks.on”rune.hooks.on(event, handler, opts?) -> handleevent(string): an event name from the tables below.handler(function): receives the event’s arguments; return values matter only for data-flow events.opts(table, optional): common options.prioritydefaults to 50; lower runs first.
rune.hooks.on("connected", function(addr) rune.send("look")end, {name = "auto-look"})Data-flow events
Section titled “Data-flow events”Handlers run in priority order (lower first, default 50).
For output, prompt, echo, and input, a string, including "",
replaces the text for subsequent handlers, so rewrites chain in priority
order. nil and other values pass the current text through. false stops the
chain: it gags output or a prompt, hides an echo, or cancels an input
submission.
Input handlers run before Rune echoes, stores, or processes a submission. A canceled submission stops there. Otherwise Rune uses the final text for local echo and input processing, and adds it to history unless it is empty.
| Event | Handler receives | Fired |
|---|---|---|
input |
submitted text, context | Before local echo, history, and command or verbatim processing |
output |
line object (:raw(), :clean()) |
Once for every complete server line |
prompt |
line object, confirmed boolean |
On cumulative partial-line observations and GA/EOR-confirmed prompts |
echo |
one display-safe physical line of final input | After input rewrites; skipped while the server has echo suppressed (passwords) |
Before echo handlers run, Rune makes terminal-active control characters
visible so the value is safe to display. Tabs remain tabs.
prompt drives the prompt overlay. With confirmed = false, the value is a
partial line. It may be Username: or only the start of an ordinary line, and
it may repeat as it grows. A line delimiter later sends the complete line
through output. With confirmed = true, a GA/EOR prompt boundary confirmed
the text as a prompt. If the boundary arrives in a later batch, the hook may
receive the same text first as partial and then as confirmed. An empty prompt
boundary does not fire the hook. Rune never uses a timer or prompt pattern to
confirm a partial line.
Finishing a partial line means one thing throughout Rune: the prompt overlay is committed to scrollback and open trigger spans close. Every user submission finishes the partial line before input hooks and any echo, history, or processing, even for a local slash command, a canceled submission, a disconnected submission, or one whose eventual send fails. Separately, a programmatic send from an alias, trigger, timer, or other callback finishes it only after the connection accepts the send. Failed sends and protocol traffic such as GMCP do not.
If the partial text was really the start of a fragmented ordinary line,
sending commits the visible prefix; the rest arrives through output as a new
line. This is the trade-off for immediate partial-line display without a timer
or prompt pattern.
rune.hooks.on("prompt", function(line, confirmed) if confirmed then rune.echo("Server-confirmed prompt: " .. line:clean()) endend)Handlers for partial lines should be safe to repeat. A confirmed prompt closes
open spans before prompt handlers run. Finishing a partial line on submission
or accepted send commits its latest processed overlay without calling the
prompt hook again.
Every input handler receives (text, context). The context is read-only, and
context.mode is always "command" or "verbatim". Each handler sees the
text returned by the preceding handler:
rune.hooks.on("input", function(text, context) if context.mode == "verbatim" then -- The handler sees the whole submission once, line breaks included. rune.echo("Sending verbatim block (" .. #text .. " bytes)") end return text:gsub("^l$", "look")end, { priority = 10 })Input hooks run before the current submission enters history, so a handler
that calls rune.history.get() sees only earlier accepted submissions. A
string rewrite controls the later local echo, history entry, and command
processing.
Existing one-argument handlers remain valid because Lua ignores the context
argument. Echo hooks run after non-empty final text is recorded and can
therefore observe its history entry. An accepted "" rewrite still continues
to local echo and input processing, but it is not added to history.
In command mode, the final replacement must remain valid command text. Ordinary
game commands stay on one line; local /commands may carry multiline,
tab-indented arguments. Terminal controls are rejected. To deliberately send several physical
lines from a command handler, call rune.send_raw and return false so Rune
does not also process the original command. Verbatim-mode handlers may rewrite
the complete multiline draft.
All input handlers run in priority order, with lower numbers first. If none
returns false, Rune processes the final text after the last handler finishes.
Command mode applies slash commands, command separators, #N repeats, and
aliases. Verbatim mode sends each line without any of that command processing.
Even a handler with a priority above 100 still runs before Rune processes or
sends the command.
The named core input hook history-expansion runs at priority 100. With the
default history character, ! and !! repeat the last command, while
!prefix repeats the newest command beginning with prefix. These forms also
work as complete commands in a separator-chained line. Rune ignores local
slash commands, verbatim input, and earlier commands that still contain
history-expansion syntax for the current history character. If any expansion
has no match, Rune shows a warning and cancels the whole input line. Disable
the feature with:
rune.config.set("history_character", "")Only commands typed in normal input use history expansion. rune.send does
not perform it, and verbatim input bypasses command processing entirely.
Hooks below priority 100 see the text before expansion; hooks above 100 see the
expanded command. If you are replacing the feature with your own input hook,
you can remove the built-in history-expansion handler instead of merely
disabling it.
For output, prompt, and echo, the core handlers remain at priority 100:
trigger processing on output/prompt and the > styling on echo.
Register below 100 to run before them, or above 100 to see their results
(post-trigger rewrites; gagged lines never reach you).
Notification events
Section titled “Notification events”All handlers run; return values are ignored.
| Event | Args | Fired |
|---|---|---|
ready |
none | After core and user scripts load during startup or /reload, before Rune applies their settings and UI state |
connecting |
address | Dial started |
connected |
address | Connection established |
disconnecting |
none | Disconnect requested |
disconnected |
none | Connection closed |
reloading / reloaded |
none | Around /reload (order: reloading, ready, reloaded) |
loaded |
path | After /load or rune.load loads a file (not for startup auto-load) |
error |
message | On reported errors |
input_changed |
text | Whenever the input buffer changes, including typing or paste, history or completion, rune.input.set, and the post-submit draft |
window_size_changed |
width, height | On the first reported terminal size and every resize; rune.state.width/height already hold the new values |
gmcp |
package, data, raw JSON | On every GMCP message, before package-specific rune.gmcp.on handlers |
gmcp_enabled |
none | GMCP negotiated; the core handler sends Core.Hello |
window_size_changed receives the terminal columns and rows as numbers,
so a script can adapt an identified layout region at its own breakpoint. This
example assumes the active layout has a region with id = "sidebar"; see
Regions and inactive resources.
local function fit_sidebar(width) if width < 80 then rune.ui.regions.hide("sidebar") else rune.ui.regions.show("sidebar") endend
fit_sidebar(rune.state.width)rune.hooks.on("window_size_changed", fit_sidebar)/reload does not fire the resize hook. The explicit call using
rune.state.width sets the initial region state; the hook handles later
changes.
Named core handlers
Section titled “Named core handlers”Handlers the core registers under stable names, so you can disable or
replace them: log-output, log-echo (logging policy, priority 200),
gmcp-hello (the GMCP handshake), gmcp-reset, first-run-welcome,
history-expansion (interactive history expansion, priority 100), and
_completion_cache / _completion_input (tab-completion word harvesting,
priority 200).
Managing
Section titled “Managing”Standard registry management applies:
rune.hooks.get/enable/disable/remove(name), .list(), .count(),
.clear(), .remove_group(group): see
Registries. /hooks lists everything.
Related: Hooks & Events guide · rune.trigger · rune.gmcp