Docs


Scripts Tutorial

This document describes Rete’s Tcl scripting API: how to bind to events, how return values influence client behaviour, what globals are available, and which helper commands you can call from Tcl.

Command Namespace

All Rete-specific Tcl commands are registered in the ::rete:: namespace to avoid conflicts with standard Tcl commands and libraries. Commands can be called using the full namespace prefix (e.g., ::rete::join) or by importing the namespace:

namespace import ::rete::*

Event binding

::rete::bind EVENT handlerProc
::rete::unbind EVENT handlerProc
proc handlerProc {arg1 arg2 ...} {
    # return 0 to let Rete continue normal processing
    # return 1 to stop further processing of this event
}

Return convention

Event arguments

Each event passes a fixed list of string arguments to your handler. Timestamps are passed as ISO‑8601 strings when available, otherwise "". Optional values (like account, reason) are passed as "" when absent.

RAWIN

proc my_rawin {line} {
    # line: raw line from server before any parsing
    return 0
}

RAW_OUT (outgoing raw line)

proc my_raw_out {line} {
    # line: raw line about to be sent to the server
    # return 0 to allow send, return 1 to block send
    return 0
}

REGISTERED

proc my_registered {} {
    # No arguments
    return 0
}

SILENCE

proc my_silence {action hostmask} {
    # action: "add" or "rem"
    # hostmask: the hostmask being added/removed
    return 0
}

ERROR

proc my_error {text} {
    # text: server error message
    return 0
}

RPL (numeric replies)

proc my_rpl {code text buffer serverTime} {
    # code: numeric code as string (e.g., "001", "005"), or ""
    # text: reply text
    # buffer: target buffer name (channel/DM) if applicable, or ""
    # serverTime: ISO-8601 timestamp or ""
    return 0
}

SERVERNOTICE

proc my_servernotice {from text channel serverTime} {
    # from: sender prefix or ""
    # text: notice text
    # channel: associated channel if applicable, or ""
    # serverTime: ISO-8601 timestamp or ""
    return 0
}

SASL_AUTH

proc my_sasl_handler {username password mechanism} {
    # username  - Current SASL username (may be empty, may contain spaces)
    # password  - Current SASL password (may be empty, may contain spaces)
    # mechanism - SASL mechanism name (e.g. "plain", "external")
    #
    # Return {} or "" to indicate "no override" and let Rete use the configured
    # credentials, or return a two-element list {newUsername newPassword} to
    # override what is sent to the server.

    # Example: override username and password
    set newUser "my sasl user"
    set newPass "my secret pass"
    return [list $newUser $newPass]
}

bind SASL_AUTH my_sasl_handler

JOIN

proc my_join {channel nick user host account realname serverTime} {
    # channel: channel name
    # nick: nickname of person who joined
    # user: ident/username
    # host: hostname
    # account: services account name or ""
    # realname: realname/GECOS
    # serverTime: ISO-8601 timestamp or ""
    return 0
}

PART

proc my_part {channel nick reason serverTime} {
    # channel: channel name
    # nick: nickname of person who left
    # reason: part reason or ""
    # serverTime: ISO-8601 timestamp or ""
    return 0
}

QUIT

proc my_quit {nick message serverTime} {
    # nick: nickname of person who quit
    # message: quit message or ""
    # serverTime: ISO-8601 timestamp or ""
    return 0
}

CHANMSG (channel PRIVMSG)

proc my_chanmsg {from channel text serverTime} {
    # from: sender nickname
    # channel: channel name
    # text: message text
    # serverTime: ISO-8601 timestamp or ""
    return 0
}

DIRECTMSG (direct PRIVMSG)

proc my_directmsg {from target text serverTime} {
    # from: sender nickname
    # target: recipient (your nick or theirs)
    # text: message text
    # serverTime: ISO-8601 timestamp or ""
    return 0
}

CHANNOTICE (channel NOTICE)

proc my_channotice {from channel target text serverTime} {
    # from: sender nickname or server prefix
    # channel: channel name
    # target: target nickname or ""
    # text: notice text
    # serverTime: ISO-8601 timestamp or ""
    return 0
}

DIRECTNOTICE (direct NOTICE)

proc my_directnotice {from target text serverTime} {
    # from: sender nickname or server prefix
    # target: recipient nickname
    # text: notice text
    # serverTime: ISO-8601 timestamp or ""
    return 0
}

NICK (nick change)

proc my_nick {oldNick newNick serverTime} {
    # oldNick: previous nickname
    # newNick: new nickname
    # serverTime: ISO-8601 timestamp or ""
    return 0
}

TOPIC (topic info/update)

proc my_topic {channel topic who serverTime} {
    # channel: channel name
    # topic: topic text or ""
    # who: nickname who set topic, or "" for topic info (RPL 332/333)
    # serverTime: ISO-8601 timestamp or ""
    return 0
}

MODE (channel mode change)

proc my_mode {channel setter modeString params serverTime} {
    # channel: channel name
    # setter: nickname who set the mode
    # modeString: mode string (e.g., "+o", "+nt-lk"), or ""
    # params: space-separated mode parameters, or ""
    # serverTime: ISO-8601 timestamp or ""
    return 0
}

AWAY (away status change)

proc my_away {nick isAway message serverTime} {
    # nick: nickname
    # isAway: "1" if now away, "0" if back
    # message: away message or ""
    # serverTime: ISO-8601 timestamp or ""
    return 0
}

ACCOUNT (account change)

proc my_account {nick account serverTime} {
    # nick: nickname
    # account: services account name, or "" if logged out
    # serverTime: ISO-8601 timestamp or ""
    return 0
}

CHGHOST (user/host change)

proc my_chghost {nick user host serverTime} {
    # nick: nickname
    # user: new ident/username
    # host: new hostname
    # serverTime: ISO-8601 timestamp or ""
    return 0
}

OPER (oper status change)

proc my_oper {nick isOper} {
    # nick: nickname
    # isOper: "1" if now oper, "0" if no longer oper
    # NOTE: No serverTime for OPER events
    return 0
}

INVITE

proc my_invite {inviter target channel serverTime} {
    # inviter: nickname who sent invite
    # target: nickname who was invited (may be you)
    # channel: channel name
    # serverTime: ISO-8601 timestamp or ""
    return 0
}

KICK

proc my_kick {channel kicker victim reason serverTime} {
    # channel: channel name
    # kicker: nickname who kicked, or ""
    # victim: nickname who was kicked
    # reason: kick reason or ""
    # serverTime: ISO-8601 timestamp or ""
    return 0
}

WALLOPS

proc my_wallops {from text serverTime} {
    # from: sender nickname or server prefix
    # text: wallops message text
    # serverTime: ISO-8601 timestamp or ""
    return 0
}

ACTION (ACTION, /me)

proc my_action {from target text serverTime} {
    # from: sender nickname
    # target: channel name or nickname (where action was sent)
    # text: action text (without the `/me` prefix)
    # serverTime: ISO-8601 timestamp or ""
    return 0
}

CTCPREQ (CTCP request)

proc my_ctcpreq {from target command params serverTime} {
    # from: sender nickname
    # target: recipient (channel or nickname)
    # command: CTCP command name (uppercased, e.g., "VERSION", "PING")
    # params: command parameters or ""
    # serverTime: ISO-8601 timestamp or ""
    return 0
}

CTCPRPL (CTCP reply)

proc my_ctcprpl {from target command params serverTime} {
    # from: sender nickname
    # target: recipient (channel or nickname)
    # command: CTCP command name (uppercased, e.g., "VERSION", "PING")
    # params: reply parameters or ""
    # serverTime: ISO-8601 timestamp or ""
    return 0
}

Globals

Identity variables:

Server variables:

Version variables:

Access them from Tcl as normal variables. Inside procedures, you must either use global statements or the :: namespace prefix:

# At the top level of a script, use directly:
::rete::debug "I am $mynick!$myuser@$myhost (account=$myaccount)"

# Inside a procedure, use global:
proc my_handler {args} {
    global mynick myuser myhost myaccount server serveraddress
    ::rete::debug "I am $mynick!$myuser@$myhost"
}

# Or use the :: prefix to explicitly reference the global namespace:
proc my_handler {args} {
    ::rete::debug "I am $::mynick!$::myuser@$::myhost"
    ::rete::debug "Connected to $::server at $::serveraddress"
}

Helper commands (getters)

::rete::topic #channel

Returns the current topic for #channel as a string, or "" if none is known.

::rete::getchanmode #channel

Returns a human‑readable mode string for #channel if available, or "" if no cached information is available.

::rete::isupport_get KEY     ;# returns string or ""
::rete::isupport_isset KEY   ;# returns 1 if the key is present, 0 otherwise

These map directly onto the server’s ISUPPORT tokens as seen in RPL 005.

::rete::rfcequal string1 string2

Checks if two strings are equal using IRC case-insensitive comparison (RFC 1459 matching). This uses the same comparison logic as Rete’s internal IRCCompare function, which respects the server’s CASEMAPPING ISUPPORT token.

Returns 1 if the strings are equal (case-insensitive, with RFC 1459 character equivalences: {}|~ == []\^), 0 otherwise.

Example:

# These all return 1 (equal)
::rete::rfcequal "Nick" "nick"
::rete::rfcequal "Test" "test"
::rete::rfcequal "User[Name" "user{name"  ;# [ == { under RFC 1459
::rete::rfcequal "Chan^el" "chan~el"       ;# ^ == ~ under RFC 1459
::rete::theme_get <option>

Returns the value of a theme setting from the current theme. This allows scripts to access theme configuration values such as colors, badge icons, and theme information.

Options:

Returns a string value, or an empty string if the option is not found. Colors are returned in hex format (e.g., "#FF0000"). The command automatically uses the appropriate light/dark palette based on the current appearance mode.

Example:

# Get current theme name
set themeName [::rete::theme_get theme]
::rete::debug "Current theme: $themeName"

# Get accent color
set accentColor [::rete::theme_get accent]
::rete::debug "Accent color: $accentColor"

# Get bot badge icon
set botBadge [::rete::theme_get botBadge]
::rete::debug "Bot badge: $botBadge"

# Respond with theme information
proc on_chanmsg_info {from channel text serverTime} {
    if {$text eq "!theme"} {
        set themeName [::rete::theme_get theme]
        set highlightTheme [::rete::theme_get scriptEditorHighlightrTheme]
        ::rete::msg $channel "Theme: $themeName, Highlight theme: $highlightTheme"
    }
    return 0
}
::rete::bind CHANMSG on_chanmsg_info

For complete documentation, see the “Accessing Theme Settings from TCL Scripts” section in Themes.md.

::rete::isop nick #channel
::rete::ishalfop nick #channel
::rete::isvoice nick #channel

Each returns 1 if the nick has the corresponding privilege on the channel, 0 otherwise. nick may be "" to mean “me”.

::rete::isaway nick
::rete::isbot nick
::rete::isoper nick
::rete::issecure nick
::rete::getaccount nick

These query Rete’s cached Person entry for nick and return:

issecure checks if the user has a secure (TLS/SSL) connection to the server.

::rete::ischanban ban #channel
::rete::ischanexempt exempt #channel
::rete::ischaninvite invite #channel

These commands check if a specific mask is present on the channel’s ban list, exception list, or invite-exempt list. Each returns 1 if found, 0 otherwise.

::rete::chanbans #channel
::rete::chanexempts #channel
::rete::chaninvites #channel

These commands return Tcl lists of entries for the respective channel lists. Each entry is a sublist of the form {mask bywho age}:

Example:

# Check if a ban exists
if {[::rete::ischanban *!*@*.example.com #channel]} {
    ::rete::debug "User is banned"
}

# List all bans
set bans [::rete::chanbans #channel]
foreach entry $bans {
    set mask [lindex $entry 0]
    set bywho [lindex $entry 1]
    set age [lindex $entry 2]
    ::rete::debug "Ban: $mask (set by $bywho, age: $age seconds)"
}

Helper commands (setters)

Scripts can also update certain fields on Person entries (Rete’s cached view of users on the current connection).

::rete::setcolor nick #RRGGBB

Sets color for nick to the given hex color (normalized by Rete). This affects how that nick is rendered in the UI (subject to theme rules).

::rete::setbot nick 0|1

Sets Person.isBot for nick to 1 (true) or 0 (false). This can be used by themes or scripts to treat known bots differently.

Helper commands (buffer manipulation)

::rete::appendmsg <buffer> <sender> <message> <category> ?tags...?

Appends a message to the specified buffer. This allows scripts to add messages to buffers programmatically, which can be useful for custom logging, notifications, or bot responses.

Valid categories:

Valid tags:

Returns nothing on success. If the buffer is not found, an error message is written to the Script Debug buffer.

::rete::createbuffer name type ?inputCallback? ?hidden?

Creates a new buffer with the specified name and type.

Buffer Types:

Script Buffers:

Script buffers are special buffers that can have an optional input callback function. When a callback function is provided and the user sends input in the buffer:

  1. The callback function is called with two arguments:
    • bufferName: The name of the buffer
    • inputText: The text that was entered
  2. If no callback is provided (or an empty string is passed), input is disabled for the buffer.

Notes:

Example (create/append):

# Append a normal message to a channel
::rete::appendmsg "#channel" "BotName" "Hello from script!" "normal" "channel"

# Append a server notice-style message (no sender)
::rete::appendmsg "#channel" "" "This is a system message" "serverNotice" "server"

# Append an error message
::rete::appendmsg "#channel" "" "Something went wrong!" "error" "error"

# Append a join event
::rete::appendmsg "#channel" "SomeUser" "has joined" "join" "channel"

# Append to a DM buffer
::rete::appendmsg "nickname" "BotName" "Private message" "normal" "dm"
# Create a script buffer with input callback
proc my_buffer_input {bufferName text} {
    ::rete::msg #channel "Received: $text"
}
::rete::createbuffer "MyBuffer" script "my_buffer_input"

# Create a channel buffer
::rete::createbuffer "#test" channel

# Create a hidden script buffer without input
::rete::createbuffer "LogBuffer" script "" 1
::rete::getbuffer <name> <type>

Resolves a buffer by name and type and returns its UUID string. This is useful to avoid name collisions (e.g. a DM and a script buffer both called “nick”) when using commands like appendmsg, hidebuffer, or setbuffericon.

Returns the buffer UUID as a string on success, or an empty string if no matching buffer is found.

Example:

# Get the UUID for a script buffer called "test"
set bufId [::rete::getbuffer "test" script]

if {$bufId ne ""} {
    # Append a message using the UUID
    ::rete::appendmsg $bufId "" "Hello from script buffer" "normal" "script"

    # Hide the buffer using the UUID
    ::rete::hidebuffer $bufId 1

    # Set a custom icon using the UUID
    ::rete::setbuffericon $bufId "star.fill"
}
::rete::getserverbuffer

Returns the server buffer UUID for the active session. This is a convenience function that avoids needing to know the server buffer’s name (“Server”) and type (“system”).

Returns the buffer UUID as a string on success, or an empty string if there is no active session.

Example:

# Get the server buffer and append a message
set serverBufId [::rete::getserverbuffer]
if {$serverBufId ne ""} {
    ::rete::appendmsg $serverBufId "" "System message" "info" "server"
}
::rete::setbuffericon <buffer> <iconName>

Sets a custom SF Symbol icon for a buffer. If the icon name is invalid (SF Symbol doesn’t exist) or empty, resets to the default icon based on buffer kind.

If the specified SF Symbol doesn’t exist, the command will log a warning and reset to the default icon for that buffer type.

Example:

# Set a custom icon for a script buffer
::rete::setbuffericon "MyBuffer" "star.fill"

# Set a different icon
::rete::setbuffericon "MyBuffer" "heart.circle"

# Reset to default icon
::rete::setbuffericon "MyBuffer" ""
::rete::hidebuffer <buffer> <hidden>

Hide or show a buffer in the sidebar. This works for any buffer type.

Example:

# Hide a buffer
::rete::hidebuffer "MyBuffer" 1

# Show a buffer
::rete::hidebuffer "MyBuffer" 0

# Hide a channel
::rete::hidebuffer "#test" true

# Show a channel
::rete::hidebuffer "#test" false
::rete::delbuffer <buffer>

Delete (close) a buffer. This will remove the buffer from the sidebar and discard its messages from the current session.

Notes:

Persistent storage

Scripts can store persistent data that survives across sessions using the store command. Data is organized by script namespace, ensuring that each script can only access its own data.

Important: Scripts must have a Namespace: metadata field defined to use storage commands. If a script attempts to use storage without a namespace, an error will be returned.

Storage types:

String operations:

::rete::store setstring <key> <value>
::rete::store getstring <key>

Note: Since Tcl is string-based, numeric values should be stored as strings. Tcl will automatically convert strings to numbers when needed for arithmetic operations using expr or incr.

List operations:

::rete::store setlist <key> <value1> <value2> ...
::rete::store getlist <key>
::rete::store lappend <key> <value> ...
::rete::store lremove <key> <value>

Dict operations:

::rete::store setdict <key> <dictKey1> <dictValue1> <dictKey2> <dictValue2> ...
::rete::store getdict <key>
::rete::store dictset <key> <dictKey> <value>
::rete::store dictget <key> <dictKey>
::rete::store dictunset <key> <dictKey>

Notes:

Examples:

# Store a string
::rete::store setstring username "Alice"
set user [::rete::store getstring username]

# Store a numeric value as a string (Tcl will convert when needed)
::rete::store setstring message_count "42"
set count [::rete::store getstring message_count]
# Use in arithmetic: expr {$count + 1} or incr count

# Store a list (replace entire list)
::rete::store setlist favorites "item1" "item2" "item3"

# Get the list back
set favs [::rete::store getlist favorites]
# favs is now: {item1 item2 item3}

# Append to the list (like Tcl's lappend)
::rete::store lappend favorites "item4" "item5"
# favorites is now: {item1 item2 item3 item4 item5}

# Remove a value from the list
::rete::store lremove favorites "item2"
# favorites is now: {item1 item3 item4 item5}

# Store a string with spaces (not a list)
::rete::store setstring my_message "This is a string with spaces"
set msg [::rete::store getstring my_message]
# msg is: "This is a string with spaces"

# Store a dict (key-value pairs)
::rete::store setdict config "host" "irc.example.com" "port" "6667" "ssl" "1"

# Get the dict back
set cfg [::rete::store getdict config]
# cfg is now a proper Tcl dict: {host {irc.example.com} port 6667 ssl 1}

# Set a value in the dict
::rete::store dictset config "nick" "MyBot"

# Get a value from the dict
set host [::rete::store dictget config "host"]
# host is: "irc.example.com"

# Remove a key from the dict
::rete::store dictunset config "port"

Complete example:

# Title: Example Script
# Namespace: example

# Track message count per channel
proc on_chanmsg {from channel text serverTime} {
    set count [::rete::store getstring "count_$channel"]
    if {$count eq ""} {
        set count 0
    } else {
        set count [expr {int($count)}]
    }
    incr count
    ::rete::store setstring "count_$channel" $count
    
    # Store recent messages
    ::rete::store lappend "recent_$channel" "$from: $text"
    
    # Keep only last 10 messages
    set recent [::rete::store getlist "recent_$channel"]
    if {[llength $recent] > 10} {
        set recent [lrange $recent end-9 end]
        ::rete::store setlist "recent_$channel" {*}$recent
    }
    
    return 0
}

::rete::bind CHANMSG on_chanmsg
::rete::setweburl <buffer> <url>

Sets the URL for a web buffer.

If the buffer is not found or is not a web buffer, an error is logged to the Script Debug buffer and the command is otherwise ignored.

Example:

proc open_rete_website {} {
    # Create or find a web buffer
    ::rete::createbuffer "Rete Website" web

    # Point it to the desired URL
    ::rete::setweburl "Rete Website" "https://rete.chat/"
}
::rete::addnickmenu <label> <callback>

Adds a custom menu item to the nicklist context menu (right-click on nickname).

The callback function will be called with three arguments:

proc my_nick_action {bufferId bufferName nick} {
    # bufferId   - UUID of the buffer where the nick was right-clicked
    # bufferName - Name of that buffer (e.g. #channel)
    # nick       - The nickname that was right-clicked
}

Notes:

Example:

# Define a callback function
proc my_nick_action {bufferId bufferName nick} {
    ::rete::msg $bufferName "Hello $nick from script! (buffer $bufferName / $bufferId)"
}

# Add the menu item
::rete::addnickmenu "Say Hello" "my_nick_action"

Dynamic nicklist menu providers

For fully dynamic, per-nick context menus that are computed only when a nick is right-clicked, you can register a nickmenu provider:

::rete::setnickmenuprovider <callback>

The provider function will be called with three arguments:

proc my_nickmenu_provider {bufferId bufferName nick} {
    # bufferId   - UUID of the buffer where the nick was right-clicked
    # bufferName - Name of that buffer (e.g. #channel)
    # nick       - The nickname that was right-clicked

    # Return a Tcl list of label/callback pairs:
    #   {label1 callback1 label2 callback2 ...}
    set items {}
    lappend items "Whois $nick" my_whois_proc
    lappend items "Greet $nick" my_greet_proc

    return $items
}

proc my_whois_proc {bufferId bufferName nick} {
    ::rete::putserv "WHOIS $nick"
}

proc my_greet_proc {bufferId bufferName nick} {
    ::rete::msg $bufferName "Hello $nick from dynamic menu (buffer $bufferName / $bufferId)"
}

# Register the provider
::rete::setnickmenuprovider my_nickmenu_provider

Notes:

Store header, channel buffer, and nick buffer menus

In addition to nicklist entries, scripts can attach menu items to:

You can use simple static buttons (registered once at load time) or dynamic providers that are evaluated each time the menu is opened.

::rete::addstoreheaderbutton <label> <callback>

Adds a custom menu item to the store header context menu.

The callback function will be called with two arguments:

proc my_storeheader_action {storeId serverName} {
    # storeId    - UUID of the IRC store/connection
    # serverName - Human-readable server name/address
}

Example:

proc my_storeheader_action {storeId serverName} {
    ::rete::echo "Clicked store header for $serverName ($storeId)"
}

::rete::addstoreheaderbutton "My Store Action" my_storeheader_action
::rete::addchannelbuffermenu <label> <callback>

Adds a custom menu item to channel buffer context menus (right-click on a channel buffer in the sidebar).

The callback will be invoked with two arguments:

proc my_channelbuffer_action {bufferId bufferName} {
    # bufferId   - UUID of the channel buffer
    # bufferName - Channel name (e.g. #channel)
}

Example:

proc my_channelbuffer_action {bufferId bufferName} {
    ::rete::msg $bufferName "Channel buffer menu clicked for $bufferName ($bufferId)"
}

::rete::addchannelbuffermenu "Channel Script Action" my_channelbuffer_action
::rete::addnickbuffermenu <label> <callback>

Adds a custom menu item to nick/DM buffer context menus (right-click on a direct message buffer in the sidebar).

The callback will be invoked with three arguments:

proc my_nickbuffer_action {bufferId bufferName nick} {
    # bufferId   - UUID of the DM buffer
    # bufferName - Name of the buffer (usually the nick)
    # nick       - Nickname for this DM
}

Example:

proc my_nickbuffer_action {bufferId bufferName nick} {
    ::rete::msg $bufferName "DM menu clicked for $nick in $bufferName ($bufferId)"
}

::rete::addnickbuffermenu "DM Script Action" my_nickbuffer_action

Dynamic store header / buffer menu providers

For fully dynamic menus that are computed only when the context menu is opened, you can register providers similar to nicklist and hover providers:

::rete::setstoreheadermenuprovider <callback>

The provider is called with:

proc my_storeheader_menu_provider {storeId serverName} {
    # Return {label1 callback1 label2 callback2 ...}
    set items {}
    lappend items "Reconnect $serverName" my_reconnect_proc
    return $items
}

Top‑level store menu buttons

For the macOS menu bar, Rete exposes a top‑level store menu for each connection (the menu whose title is the current store/connection name). Scripts can add custom items to this menu:

::rete::addmenubutton <label> <callback>

Adds a custom menu item to the top‑level store menu for the current connection in the macOS menu bar.

The callback function will be called with two arguments, identical to addstoreheaderbutton:

proc my_storemenu_action {storeId serverName} {
    # storeId    - UUID of the IRC store/connection
    # serverName - Human-readable server name/address
}

Example:

proc my_storemenu_action {storeId serverName} {
    ::rete::echo "Clicked top-level store menu for $serverName ($storeId)"
}

::rete::addmenubutton "My Store Menu Action" my_storemenu_action
::rete::setchannelbuffermenuprovider <callback>

The provider is called with:

proc my_channelbuffer_menu_provider {bufferId bufferName} {
    set items {}
    lappend items "Say hi in $bufferName" my_channel_hi_proc
    return $items
}
::rete::setnickbuffermenuprovider <callback>

The provider is called with:

proc my_nickbuffer_menu_provider {bufferId bufferName nick} {
    set items {}
    lappend items "Note about $nick" my_dm_note_proc
    return $items
}

Notes for these providers:

Dynamic hover-card (user info tooltip) buttons

You can also add buttons to the hover card that appears when hovering over nicknames (user info tooltip). These are computed dynamically when the tooltip is shown, similar to dynamic nicklist menu providers:

::rete::setnickhoverprovider <callback>

The provider function will be called with three arguments:

proc my_nickhover_provider {bufferId bufferName nick} {
    # bufferId   - UUID of the buffer context (channel or DM), or "" if unknown
    # bufferName - Name of that buffer (e.g. #channel or nick), or "" if unknown
    # nick       - The nickname being hovered

    # Return a Tcl list of label/callback pairs:
    #   {label1 callback1 label2 callback2 ...}
    set items {}
    lappend items "Whois" my_whois_proc
    lappend items "Greet" my_greet_proc

    return $items
}

proc my_whois_proc {bufferId bufferName nick} {
    ::rete::putserv "WHOIS $nick"
}

proc my_greet_proc {bufferId bufferName nick} {
    # Send greeting to the current buffer if known, or fall back to using the nick
    if {$bufferName ne ""} {
        ::rete::msg $bufferName "Hello $nick from hover card (buffer $bufferName / $bufferId)"
    } else {
        ::rete::msg $nick "Hello from hover card"
    }
}

# Register the provider
::rete::setnickhoverprovider my_nickhover_provider

Notes:

Helper commands (sending commands)

In addition to getters, Tcl scripts can send commands to the IRC server via the following helpers. All of them operate on the current connection (IRCStore.transport).

::rete::putserv RAWLINE...

Example:

::rete::putserv PRIVMSG #channel :Hello from Tcl
::rete::join #channel ?key?
::rete::part #channel ?reason...?
::rete::msg target text...
::rete::notice target text...
::rete::ctcp target command ?params...?
::rete::action target text...
::rete::topic_set #channel ?newTopic...?
::rete::nick newNick
::rete::quit ?message...?
::rete::mode target modes ?params...?
::rete::kick #channel nick ?reason...?
::rete::silence ?input...?

These are thin wrappers around Rete’s internal command/transport layer and behave like the corresponding /join, /part, /msg, /notice, /ctcp, /me, /topic, /nick, /quit, /mode, /kick and /silence commands typed in the client, with putserv exposing raw access to the underlying connection.

Custom commands

Scripts can register custom commands that will be called when the user types /command in the input field. Custom commands can override native commands.

::rete::addcommand command callback

The callback function will be called with three arguments:

proc my_custom_command {params bufferId bufferName} {
    # params     - Everything after the command name (the remaining text)
    # bufferId   - UUID of the buffer where the command was executed
    # bufferName - Name of that buffer (e.g. #channel, nickname, or system buffer name)
}

Notes:

Example:

# Define a callback function
proc my_greet {params bufferId bufferName} {
    if {$params eq ""} {
        ::rete::msg $bufferName "Hello! Usage: /greet <name>"
    } else {
        ::rete::msg $bufferName "Hello, $params!"
    }
}

# Register the command
::rete::addcommand greet my_greet

# Now /greet Alice will call my_greet with params="Alice"
# Override the native /join command
proc my_custom_join {params bufferId bufferName} {
    # Parse the channel name from params
    set channel [lindex [split $params] 0]
    if {$channel eq ""} {
        ::rete::debug "Usage: /join #channel"
        return
    }
    
    # Add a custom message before joining
    ::rete::debug "Joining $channel with custom handler"
    
    # Call the native join command
    ::rete::join $channel
}

# Register to override native /join
::rete::addcommand join my_custom_join
::rete::cap ls
::rete::cap values
::rete::cap values <capability>
::rete::cap enabled
::rete::cap req "cap1 cap2 ..."
::rete::cap raw "CAP SUBCOMMAND ..."

Manages IRCv3 capabilities (CAP):

Examples:

# List server capabilities
set caps [::rete::cap ls]
::rete::debug "Server supports: $caps"

# Check if SASL is enabled
set enabled [::rete::cap enabled]
if {"sasl" in $enabled} {
    ::rete::debug "SASL is enabled"
}

# Get SASL mechanisms
set mechs [::rete::cap values sasl]
::rete::debug "SASL mechanisms: $mechs"

# Request a capability
::rete::cap req "cap-notify"

# Send raw CAP command
::rete::cap raw "LS 302"

Timer commands

Rete provides timer functionality similar to Eggdrop’s timer and utimer commands, allowing scripts to schedule Tcl commands to execute at regular intervals.

::rete::timer minutes commandName ?count? ?timerName?

Creates a timer that fires every minutes minutes, aligned to the top of the minute. The first fire occurs at the next minute boundary plus minutes.

Returns the timer name (useful when auto-generated).

Example:

# Fire every 5 minutes, 10 times
::rete::timer 5 my_periodic_task 10 mytask

# Fire every 30 seconds, infinite
::rete::timer 0.5 check_status 0
::rete::utimer seconds commandName ?count? ?timerName?

Creates a timer that fires every seconds seconds, starting immediately (now + seconds).

Returns the timer name.

Example:

# Fire every 10 seconds, 5 times
::rete::utimer 10 check_connection 5 conncheck

# Fire once after 30 seconds
::rete::utimer 30 delayed_action 1
::rete::timers
::rete::utimers

Returns a space-separated list of active timers. Each entry contains: timerName intervalSeconds count nextFireAt command

::rete::killtimer timerName
::rete::killutimer timerName

Cancels and removes the specified timer. Returns nothing on success, or an error if the timer doesn’t exist.

Example:

set t [::rete::timer 5 my_task 0]
# ... later ...
::rete::killtimer $t

Notes:

The debug command

Scripts can write to the per‑store Script Debug buffer using:

::rete::debug ?level? text...

Rete maps debug verbosity levels with script as the theme category and error, warning, debug and infoas theme tags.

Script UI API (ui:: namespace)

Scripts can build script-driven windows and forms using the ::ui:: namespace.

View:

::ui::view load <jsonPath> ?-callback <proc>?
::ui::view destroy <viewHandle>
::ui::view set <viewHandle> -title <text>
::ui::view elem <viewHandle> <localId>

Returns the element handle (e.g. for localId lblToken in the JSON).

View JSON layout — how to build the JSON

The view file is a single JSON object with two top-level keys:

Widget object

Each widget is an object with:

Widget types and props

Minimal example

{
  "title": "My Window",
  "widgets": [
    { "type": "label", "id": "lblTitle", "props": { "text": "Hello" } },
    { "type": "button", "id": "btnGo", "props": { "title": "Go" } }
  ]
}

Example with containers and hbox

{
  "title": "Form",
  "widgets": [
    {
      "type": "container",
      "props": { "divider": "true" },
      "children": [
        { "type": "label", "id": "lblTarget", "props": { "text": "Target:" } },
        { "type": "input", "id": "inputTarget", "props": { "text": "" } },
        {
          "type": "hbox",
          "children": [
            { "type": "button", "id": "btnSave", "props": { "title": "Save" } },
            { "type": "button", "id": "btnClose", "props": { "title": "Close" } }
          ]
        }
      ]
    }
  ]
}

Use pretty-printed JSON (newlines and indentation) so the file is easy to edit. The app accepts any valid JSON for the structure above.

Widgets:

::ui::label set <elemHandle> -text <text>
::ui::text append <elemHandle> <text>
::ui::text clear <elemHandle>
::ui::input set <elemHandle> -text <value>
::ui::input get <elemHandle>
::ui::table setcolumns <elemHandle> <column1> <column2> ...
::ui::table add <elemHandle> -id <rowId> -data <dict>
::ui::table update <elemHandle> -id <rowId> -data <dict>
::ui::table remove <elemHandle> <rowId>
::ui::table clear <elemHandle>

Binding:

::ui::bind <elemHandle> <eventName> <scriptBody>
::ui::unbind <bindingToken>
::ui::alert -message <text> ?-title <text>? ?-style error|warning?

Shows a modal dialog with a badge on the left and the message text on the right. -style sets the badge: error (red, default) or warning (orange). Default -title is Error; use -title to override (e.g. -title "Validation").

Events: buttononClick; view window → onClose; tableonSelect, onDoubleClick; inputonSubmit (Enter key; ::ui(event) data value is the field text).

When an event fires, the script runs on the serial queue with ::ui(event) set to a dict: type, target, view, data.

Namespacing: Element handles are generated as <connectionId>::<scriptNamespace>::<viewInstance>::<localElementId>. Scripts use local ids from the JSON (e.g. tblScores, btnSave); the app applies the prefix.

Example:

set viewHandle ""   ;# set by callback
proc on_view_loaded {vh} {
    global viewHandle
    set viewHandle $vh
    set lbl [::ui::view elem $vh lblTitle]
    set btn [::ui::view elem $vh btnGo]
    ::ui::label set $lbl -text "Ready"
    set tok [::ui::bind $btn onClick {
        set n [::rete::get_current_nick]
        ::rete::debug "Clicked by $n"
    }]
}
::ui::view load "my_view.json" -callback on_view_loaded

Use a view JSON path that your script can resolve (e.g. a file next to the script via [file join [file dirname [info script]] my_view.json], or a path under the app’s script directory).

Behaviour: UI events run your Tcl on the connection serial queue; UI updates are applied on the main thread. The app does not block the serial queue waiting for the main thread, so your handlers can safely call UI commands.