rvIRC

March 19, 2026 · View on GitHub

⚠️ Active development — This project is under active development. APIs, behavior, and features may change. Use at your own risk.

Rust + VIM + IRC — A terminal IRC client with vim-style modes and commands, built with ratatui and the irc crate.

rvIRC

Requirements

Build & run

cargo build --release
./target/release/rvirc

Features

FeatureDescription
Vim-style modesNORMAL, INSERT, COMMAND with distinct status bar colors (blue, green, orange)
Multi-serverConnect to multiple IRC servers simultaneously; channels pane groups them by server
Config~/.config/rvIRC/config.toml — multiple servers, nickname, optional NickServ identify and auto-join. Per-server auto-connect
Auto-reconnectAfter unexpected disconnect, retries up to 3 times (5s, 15s, 30s). Manual :connect or :quit cancels
Encrypted DMsEnd-to-end encrypted DM via :secure. X25519 + ChaCha20-Poly1305, directional keys, TOFU, SAS verification. Lock icon and checkmark in channel list
File transferSend files via :sendfile using magic-wormhole. Accept/reject popup; in-chat progress
Inline imagesInline display of image URLs in chat via ratatui-image (Sixel, Kitty, iTerm2). Animated GIFs supported
NotificationsDesktop notifications and optional sound for unviewed buffers. :notifications on|off, :mute / :unmute
Friends listTrack nicks with :add-friend / :remove-friend. Green = online, yellow = away, red = offline. Uses MONITOR + away-notify
Superlist:superlist fetches channel list from all connected servers in one combined, filterable window
IRC formattingBold, italic, strikethrough, colors. *italic*, **bold**, ~~strikethrough~~, ||spoiler||, :colorname: — rendered from IRC format codes
rvIRC effects@@text@@ = animated rainbow; $$text$$ = scared flicker — sent as literal text, only rvIRC renders them
Search:search opens a filterable popup over the current buffer; jump to any result
Highlight words:highlight — manage a list of words that trigger mention-style notifications and highlighting

IRCv3 Capabilities

rvIRC requests and implements the full deployed IRCv3 capability surface. Capabilities are negotiated per-server; :caps shows what each server acked.

Stable Capabilities

CapabilityWhat rvIRC does
saslSASL PLAIN and EXTERNAL authentication (set sasl_mechanism in server config)
multi-prefixUser list shows all mode prefixes per nick (e.g. @+nick)
away-notifyFriends pane updates yellow/green in real time as nicks go away and come back
account-notifyTracks nick→account mapping changes (login/logout)
account-tagShows account name alongside messages where available
extended-joinAccount name and real name received on JOIN; used for account display
invite-notifyNotifies you in the status bar when someone else is invited to a channel you're in
chghostUpdates the ident@host cache when a nick changes host (no QUIT+JOIN needed)
cap-notifyHandles CAP NEW/DEL: dynamically updates the displayed cap list
userhost-in-namesUser list displays nick!user@host; userhosts stored for whois and display
server-timeAll messages display the timestamp provided by the server (time tag)
message-tagsFull message-tag support: typing indicators, reactions, replies, bot flags, and more
batchBatches chathistory delivery; netsplit/netjoin batches collapsed into one summary line
echo-messageServer echoes sent messages (ensures correct msgids for threading and editing)
labeled-responseTags requests to correlate server responses
standard-repliesFAIL/WARN/NOTE structured replies shown with color-coded [FAIL]/[WARN]/[NOTE] labels
setname:setname <realname> changes your real name; others' setname events displayed in chat

Working Group / Draft Capabilities

CapabilityWhat rvIRC does
extended-monitorExtended MONITOR for friend presence tracking; :add-friend / :remove-friend manage the list
draft/chathistoryFetches last N messages on channel join; :more / :history fetches older messages (CHATHISTORY BEFORE)
draft/read-markerSends MARKREAD when switching buffers; incoming MARKREAD from other clients syncs read state
draft/multilinePasting multi-line text sends it as a draft/multiline BATCH instead of separate messages
draft/pre-awaySends AWAY before CAP END on reconnect so you're immediately away if you were before disconnect
draft/no-implicit-namesSuppresses the server's automatic NAMES reply on JOIN (rvIRC requests it explicitly)
draft/channel-renameRENAME command moves the channel in all panes, preserves history under the new name
draft/message-redaction:redact — sends REDACT to the server; incoming REDACT replaces message text with [Message redacted]
draft/message-edit:edit — sends EDIT with draft/target-msgid and draft/edit-text tags; incoming edits update text in-place with (edited) marker
draft/message-delete:delete — sends DELETE with draft/target-msgid tag; incoming deletes replace text with [Message deleted]
draft/account-registration:register <email> <password> sends REGISTER; result shown via standard-replies (FAIL/NOTE)
draft/extended-isupportRequests early ISUPPORT delivery; parses NETWORK= and NETWORKICON= tokens
draft/metadata:metadata GET/SET/CLEAR/LIST for key-value user metadata; display-name, pronouns, and avatar fetched automatically on :whois
account-extban:bans / :banlist shows scrollable ban-list popup; $a:account extbans rendered as [account: name]

Message Interaction (Client Tags — no cap required)

Tag / SpecWhat rvIRC does
+reply:replyr in NORMAL mode shows numbers 1–9, 0 on the last 10 messages; press a number to thread a reply
+draft/react:react <emoji> — sends a TAGMSG reaction; incoming reactions displayed inline below the target message
+draft/channel-contextDMs from bots that include a channel context appear in the channel buffer, not a DM window
+typingTyping indicator: rvIRC sends active/done events as you type; displays other users' typing status below the input bar

Server Behavior (ISUPPORT tokens)

TokenWhat rvIRC does
UTF8ONLYShows a notice in the server pane when the server enforces UTF-8
NETWORK=Network name shown in the server pane on connect
NETWORKICON=Network icon URL shown in the server pane on connect
WHOXExtended WHO on join fetches each user's account name for account-extban display
STSPersists STS upgrade policies; forces TLS on future connections to that host

Commands

Type : to enter COMMAND mode, or / in the input bar (e.g. /join #rust). Commands are case-insensitive. Tab-completion works on command names.

Connection

CommandDescription
connect <name> / server <name>Connect to a server by config name
serversShow server list from config; pick one to connect
reconnectReconnect to the current server
disconnect [server]Disconnect from current server, or the named server
quit / exit / q [message]Disconnect all and quit; optional quit message shown to other users

Channels & Messaging

CommandDescription
join #channel [key]Join a channel (# prepended if omitted); optional key for keyed channels
part / leave [#channel]Part current channel; or close current DM window
list [server]Fetch and show channel list from current server or named server. Type to filter, Enter to join
superlistFetch channel list from all connected servers; combined filterable window
msg <nick> <text> / query / messageSend a private message and open a DM window
me <action>Send a CTCP ACTION (/me) to the current channel or DM
topicShow current channel topic; topic <text> sets it (if you have permission)
invite <nick> [#channel]Invite nick to channel (defaults to current channel)
channel #chan / chan #chan / c #chanSwitch to channel or DM by name
clearClear all messages in the current channel/DM buffer

Users

CommandDescription
nick <newnick>Change your nickname
away [message]Set away status with message; no message clears away
whois [nick]Show whois popup; omit nick to use the current DM target
kick [#channel] <nick> [reason]Kick user from channel (defaults to current channel)
ban [#channel] <mask>Set a ban mask (e.g. *!*@host or nick!*@*)
unban [#channel] <mask>Remove a ban mask
bans / banlist [#channel]Fetch and display the ban list in a scrollable popup
ignore <nick>Locally hide all messages from a nick in all buffers
unignore <nick>Remove a nick from the local ignore list
muteMute a nick in the current buffer only (hides their messages locally)
unmuteUnmute a previously muted nick in the current buffer
add-friend <nick>Add nick to the friends list (MONITOR for presence)
remove-friend <nick>Remove nick from the friends list

Account

CommandDescription
pass <password> / pass <service> <password>Identify with NickServ (default) or another service
register <email> <password>Register a new account (requires draft/account-registration); result shown via standard-replies
setname <realname>Change your IRC real name (requires setname cap)

Message Actions

CommandDescription
replyEnter reply-select mode — numbers 1–9, 0 appear on the last 10 messages; press a digit to set the reply target, then type and send
react <emoji>Send a reaction to the selected message (use r to select first, or :reply to pick one)
redact [msgid] [reason]Redact a message — omit msgid to redact your last sent message; or select with r first
edit <new text>Edit your last sent message; or edit msgid=<id> <new text> for a specific message (requires draft/message-edit)
delete [msgid]Delete your last sent message; or delete <msgid> for a specific message (requires draft/message-delete)
more / historyFetch older messages in current channel/DM (CHATHISTORY BEFORE)
searchOpen search popup over current buffer; type to filter, Enter to browse and jump

Metadata

CommandDescription
metadata listList all your own metadata keys
metadata get <key>Get a specific metadata key (own nick)
metadata set <key> <value>Set a metadata key/value
metadata clear [key]Clear one key or all metadata
metadata <target> listList all metadata for a target nick or channel
metadata <target> get <key>Get a specific metadata key for a target
metadata <target> set <key> <value>Set metadata on a target (if permitted)
metadata <target> clear [key]Clear metadata on a target

Metadata keys display-name, pronouns, and avatar are fetched automatically when you run :whois and shown in the popup.

Encrypted DMs & File Transfer

CommandDescription
secure [nick]Initiate an encrypted DM session (defaults to current DM target)
unsecure [nick]End an encrypted DM session
verify [nick]Display a 6-word SAS verification code for the current secure session
verified [nick]Mark the peer as verified after comparing codes out-of-band
sendfile [nick] [path]Send a file via magic wormhole; omit path to browse, omit nick to use current DM

UI & Other

CommandDescription
channel-panel show|hideShow or hide the channels pane
messages-panel show|hideShow or hide the messages (DMs) pane
user-panel show|hideShow or hide the users pane
friends-panel show|hideShow or hide the friends pane
channels / messages / users / friendsFocus the corresponding pane
highlightOpen the highlight words popup; add words that trigger mention-style alerts
notifications on|offEnable or disable desktop notifications
mute / unmuteMute or unmute notification sound globally
capsShow negotiated IRCv3 caps for the current server in the status bar
raw <IRC command>Send a raw IRC command (e.g. :raw MODE #chan +b *!*@badhost)
versionShow version in status bar
creditsShow credits popup
licenseShow license popup (scrollable)

Keybindings

NORMAL mode

KeyAction
iEnter INSERT mode (type messages)
:Enter COMMAND mode
rReply/select mode — numbers appear on last 10 messages; press 1–9 or 0 to pick, then i to type and Enter to send
cFocus channels pane
mFocus messages (DMs) pane
uFocus users pane
fFocus friends pane
k / j or ↑ / ↓Scroll message area (when focused on main)
Page Up / Page DownScroll message area by full page
EscReturn focus to main message area
Ctrl+CQuit the app

INSERT / COMMAND mode

KeyAction
EnterSend message / execute command
EscReturn to NORMAL mode
↑ / ↓Browse input history
TabTab-complete command names (after :)
Ctrl+A / HomeMove cursor to start of input
Ctrl+E / EndMove cursor to end of input
Ctrl+WDelete word to the left
Ctrl+UDelete to start of line
Ctrl+KDelete to end of line

Channels pane (focused)

KeyAction
k / j or ↑ / ↓Move selection
EnterSwitch to selected channel / server buffer
c / EscUnfocus pane

Messages pane (focused)

KeyAction
k / j or ↑ / ↓Move selection
EnterSwitch to selected DM
m / EscUnfocus pane

Users pane (focused)

KeyAction
k / j or ↑ / ↓Move selection
Type to filterFilter user list by nick
EnterOpen user action menu
u / EscUnfocus pane

User action menu: DM (open DM window), Kick, Ban, Unban, Op, Deop, Voice, Devoice, Halfop, Dehalfop (IRC mode commands on current channel), Mute (local hide), Whois (popup).

Friends pane (focused)

Names are colored by status: green = online, yellow = away, red = offline. Config offline_friends = "hide" removes offline friends from the list.

KeyAction
k / j or ↑ / ↓Move selection
EnterOpen DM with selected friend
f / EscUnfocus pane

Popups

  • :serversj/k or arrows to move, Enter to connect, Esc to close.
  • :list / :superlist — Type to filter; Enter toggles scroll mode; j/k + Enter to join; Esc to close.
  • :search — Type to filter; Enter browses results; j/k + Enter to jump to message; Esc to close.
  • :whois — Shows nick, host, server, channels, idle time, and metadata (display-name, pronouns, avatar). Esc / Enter / q to close.
  • :bans / :banlistj/k or arrows to scroll; Esc / Enter / q to close. $a:account extbans shown as [account: name].
  • :highlight — Type to add a word, Enter to add; j/k to select, x / Delete to remove; Esc to close.
  • :credits / :licensej/k / Page Up/Down to scroll license; Esc / Enter / q to close.
  • Secure session requesty / Enter to accept, n / Esc to reject. Red TOFU warning if the peer's identity key changed.
  • File receive offery / Enter to accept, n / Esc to reject.
  • File browserj/k to navigate; Enter to open directory (or select file for send); Backspace to go up; s to save here (receive); Esc / q to cancel.

Config

Config path: ~/.config/rvIRC/config.toml. If missing, the directory and a default config are created with 0600 permissions on Unix.

Security: config.toml may contain identify_password and server password. Ensure it is not world-readable (chmod 600 ~/.config/rvIRC/config.toml).

Files stored in the same config directory:

FileContents
config.tomlMain config: servers, nickname, preferences
identity.tomlPersistent X25519 identity keypair (auto-generated, 0600 on Unix)
known_keys.tomlTOFU key store: peer identity keys, verification status, timestamps
friends.tomlFriends list (nicks to MONITOR)
highlight.tomlHighlight word list
read_markers.tomlPer-buffer read position
sts.tomlPersisted STS upgrade policies (host → port + expiry)
username = "myuser"
nickname = "mynick"
# alt_nick = "mynick_"            # optional: fallback nick if primary is in use (433)
real_name = "My Name"
# download_dir = "~/Downloads/"   # optional: default save directory for received files
# render_images = true            # optional: set false to disable inline image display (default: true)
# offline_friends = "show"        # optional: "show" (red) or "hide" — hides offline friends (default: show)
# notifications = true            # optional: desktop notifications for other buffers (default: true)
# sounds = true                   # optional: terminal bell with notifications (default: true)

[[servers]]
name = "Libera"
host = "irc.libera.chat"
port = 6697
tls = true
# identify_password = "your_nickserv_password"   # optional: identify with NickServ after connect
# sasl_mechanism = "plain"                       # optional: "plain" or "external"
# auto_connect = "yes"                           # optional: connect on startup
# auto_join = "#rvirc, #rust"                    # optional: join after identify
# proxy_url = "socks5://127.0.0.1:1080"          # optional: SOCKS5 proxy

[[servers]]
name = "Hackint"
host = "irc.hackint.org"
port = 6697
tls = true

[[servers]]
name = "Local"
host = "127.0.0.1"
port = 6667
tls = false

Connect flow: TCP connect → TLS (if enabled) → CAP negotiation → SASL (if configured) → NickServ identify (if identify_password set) → auto-join channels.


Encrypted DMs

rvIRC supports end-to-end encrypted direct messages between rvIRC clients. This is an rvIRC-exclusive feature that works transparently over standard IRC without any server changes.

Workflow

  1. Open a DM with the target user and run :secure (or :secure <nick> from anywhere)
  2. A fresh ephemeral X25519 keypair is generated for this session
  3. Both clients exchange ephemeral + identity public keys via hidden protocol messages
  4. The recipient sees an accept/reject popup before completing the handshake
  5. TOFU checks the peer's identity key against known_keys.toml — a warning is shown if the key changed since last session
  6. In-chat status messages show handshake progress, key fingerprints, and TOFU result
  7. Once established, all messages in that DM are encrypted with ChaCha20-Poly1305 using directional keys
  8. A lock icon (🔒) appears next to the nick in the channel list; a checkmark (✔) if the peer is verified
  9. Run :verify to display a 6-word SAS code — compare out-of-band with your peer, then :verified to mark them trusted
  10. Run :unsecure to end the session

Security model

  • Persistent identity: X25519 keypair stored in identity.toml (0600). Used for TOFU and SAS — not for DH (each session uses a fresh ephemeral keypair).
  • Directional keys: The DH shared secret is expanded via HKDF-SHA256 into two independent keys (rvIRC-dm-init and rvIRC-dm-resp) assigned by lexicographic ordering of ephemeral public keys. Each side uses a different key to send, preventing (key, nonce) reuse.
  • TOFU: Peer identity keys are stored in known_keys.toml with nick, server, fingerprint, verification status, and timestamps. Key changes trigger a red warning and block the ACK path.
  • SAS verification: :verify derives a 6-word Short Authenticated String from the DH secret + both identity keys via HKDF. Both sides should see the same 6 words if there is no MITM. Run :verified to mark as trusted.
  • Replay protection: Received messages must arrive with monotonically increasing nonces. Out-of-order delivery (network splits, relays) may cause legitimate messages to be rejected — run :secure again to re-key.

Protocol messages use a [:rvIRC:] prefix, are intercepted before display, and never shown to the user. Inline image display works inside encrypted DMs.


File Transfer

rvIRC clients can send files to each other using magic-wormhole:

  1. Sender runs :sendfile <nick> /path/to/file, or :sendfile in a DM to open a file browser
  2. A wormhole code is generated and sent to the recipient via an rvIRC protocol message
  3. The recipient sees a popup: nick wants to send you file.txt (1.2 MB). Accept?
  4. If accepted: file is saved to download_dir if configured, otherwise a directory browser appears
  5. A progress popup shows transfer bytes during the wormhole relay

Works inside encrypted DM sessions (the wormhole code itself is encrypted in transit).


License

See LICENSE for details.