⚠️ 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.

cargo build --release
./target/release/rvirc
| Feature | Description |
|---|
| Vim-style modes | NORMAL, INSERT, COMMAND with distinct status bar colors (blue, green, orange) |
| Multi-server | Connect 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-reconnect | After unexpected disconnect, retries up to 3 times (5s, 15s, 30s). Manual :connect or :quit cancels |
| Encrypted DMs | End-to-end encrypted DM via :secure. X25519 + ChaCha20-Poly1305, directional keys, TOFU, SAS verification. Lock icon and checkmark in channel list |
| File transfer | Send files via :sendfile using magic-wormhole. Accept/reject popup; in-chat progress |
| Inline images | Inline display of image URLs in chat via ratatui-image (Sixel, Kitty, iTerm2). Animated GIFs supported |
| Notifications | Desktop notifications and optional sound for unviewed buffers. :notifications on|off, :mute / :unmute |
| Friends list | Track 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 formatting | Bold, 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 |
rvIRC requests and implements the full deployed IRCv3 capability surface. Capabilities are negotiated per-server; :caps shows what each server acked.
| Capability | What rvIRC does |
|---|
| sasl | SASL PLAIN and EXTERNAL authentication (set sasl_mechanism in server config) |
| multi-prefix | User list shows all mode prefixes per nick (e.g. @+nick) |
| away-notify | Friends pane updates yellow/green in real time as nicks go away and come back |
| account-notify | Tracks nick→account mapping changes (login/logout) |
| account-tag | Shows account name alongside messages where available |
| extended-join | Account name and real name received on JOIN; used for account display |
| invite-notify | Notifies you in the status bar when someone else is invited to a channel you're in |
| chghost | Updates the ident@host cache when a nick changes host (no QUIT+JOIN needed) |
| cap-notify | Handles CAP NEW/DEL: dynamically updates the displayed cap list |
| userhost-in-names | User list displays nick!user@host; userhosts stored for whois and display |
| server-time | All messages display the timestamp provided by the server (time tag) |
| message-tags | Full message-tag support: typing indicators, reactions, replies, bot flags, and more |
| batch | Batches chathistory delivery; netsplit/netjoin batches collapsed into one summary line |
| echo-message | Server echoes sent messages (ensures correct msgids for threading and editing) |
| labeled-response | Tags requests to correlate server responses |
| standard-replies | FAIL/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 |
| Capability | What rvIRC does |
|---|
| extended-monitor | Extended MONITOR for friend presence tracking; :add-friend / :remove-friend manage the list |
| draft/chathistory | Fetches last N messages on channel join; :more / :history fetches older messages (CHATHISTORY BEFORE) |
| draft/read-marker | Sends MARKREAD when switching buffers; incoming MARKREAD from other clients syncs read state |
| draft/multiline | Pasting multi-line text sends it as a draft/multiline BATCH instead of separate messages |
| draft/pre-away | Sends AWAY before CAP END on reconnect so you're immediately away if you were before disconnect |
| draft/no-implicit-names | Suppresses the server's automatic NAMES reply on JOIN (rvIRC requests it explicitly) |
| draft/channel-rename | RENAME 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-isupport | Requests 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] |
| Tag / Spec | What rvIRC does |
|---|
| +reply | :reply — r 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-context | DMs from bots that include a channel context appear in the channel buffer, not a DM window |
| +typing | Typing indicator: rvIRC sends active/done events as you type; displays other users' typing status below the input bar |
| Token | What rvIRC does |
|---|
| UTF8ONLY | Shows 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 |
| WHOX | Extended WHO on join fetches each user's account name for account-extban display |
| STS | Persists STS upgrade policies; forces TLS on future connections to that host |
Type : to enter COMMAND mode, or / in the input bar (e.g. /join #rust). Commands are case-insensitive. Tab-completion works on command names.
| Command | Description |
|---|
connect <name> / server <name> | Connect to a server by config name |
servers | Show server list from config; pick one to connect |
reconnect | Reconnect 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 |
| Command | Description |
|---|
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 |
superlist | Fetch channel list from all connected servers; combined filterable window |
msg <nick> <text> / query / message | Send a private message and open a DM window |
me <action> | Send a CTCP ACTION (/me) to the current channel or DM |
topic | Show 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 #chan | Switch to channel or DM by name |
clear | Clear all messages in the current channel/DM buffer |
| Command | Description |
|---|
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 |
mute | Mute a nick in the current buffer only (hides their messages locally) |
unmute | Unmute 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 |
| Command | Description |
|---|
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) |
| Command | Description |
|---|
reply | Enter 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 / history | Fetch older messages in current channel/DM (CHATHISTORY BEFORE) |
search | Open search popup over current buffer; type to filter, Enter to browse and jump |
| Command | Description |
|---|
metadata list | List 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> list | List 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.
| Command | Description |
|---|
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 |
| Command | Description |
|---|
channel-panel show|hide | Show or hide the channels pane |
messages-panel show|hide | Show or hide the messages (DMs) pane |
user-panel show|hide | Show or hide the users pane |
friends-panel show|hide | Show or hide the friends pane |
channels / messages / users / friends | Focus the corresponding pane |
highlight | Open the highlight words popup; add words that trigger mention-style alerts |
notifications on|off | Enable or disable desktop notifications |
mute / unmute | Mute or unmute notification sound globally |
caps | Show 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) |
version | Show version in status bar |
credits | Show credits popup |
license | Show license popup (scrollable) |
| Key | Action |
|---|
i | Enter INSERT mode (type messages) |
: | Enter COMMAND mode |
r | Reply/select mode — numbers appear on last 10 messages; press 1–9 or 0 to pick, then i to type and Enter to send |
c | Focus channels pane |
m | Focus messages (DMs) pane |
u | Focus users pane |
f | Focus friends pane |
k / j or ↑ / ↓ | Scroll message area (when focused on main) |
| Page Up / Page Down | Scroll message area by full page |
| Esc | Return focus to main message area |
| Ctrl+C | Quit the app |
| Key | Action |
|---|
| Enter | Send message / execute command |
| Esc | Return to NORMAL mode |
| ↑ / ↓ | Browse input history |
| Tab | Tab-complete command names (after :) |
| Ctrl+A / Home | Move cursor to start of input |
| Ctrl+E / End | Move cursor to end of input |
| Ctrl+W | Delete word to the left |
| Ctrl+U | Delete to start of line |
| Ctrl+K | Delete to end of line |
| Key | Action |
|---|
k / j or ↑ / ↓ | Move selection |
| Enter | Switch to selected channel / server buffer |
c / Esc | Unfocus pane |
| Key | Action |
|---|
k / j or ↑ / ↓ | Move selection |
| Enter | Switch to selected DM |
m / Esc | Unfocus pane |
| Key | Action |
|---|
k / j or ↑ / ↓ | Move selection |
| Type to filter | Filter user list by nick |
| Enter | Open user action menu |
u / Esc | Unfocus 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).
Names are colored by status: green = online, yellow = away, red = offline. Config offline_friends = "hide" removes offline friends from the list.
| Key | Action |
|---|
k / j or ↑ / ↓ | Move selection |
| Enter | Open DM with selected friend |
f / Esc | Unfocus pane |
- :servers —
j/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 / :banlist —
j/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 / :license —
j/k / Page Up/Down to scroll license; Esc / Enter / q to close.
- Secure session request —
y / Enter to accept, n / Esc to reject. Red TOFU warning if the peer's identity key changed.
- File receive offer —
y / Enter to accept, n / Esc to reject.
- File browser —
j/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 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:
| File | Contents |
|---|
config.toml | Main config: servers, nickname, preferences |
identity.toml | Persistent X25519 identity keypair (auto-generated, 0600 on Unix) |
known_keys.toml | TOFU key store: peer identity keys, verification status, timestamps |
friends.toml | Friends list (nicks to MONITOR) |
highlight.toml | Highlight word list |
read_markers.toml | Per-buffer read position |
sts.toml | Persisted 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.
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.
- Open a DM with the target user and run
:secure (or :secure <nick> from anywhere)
- A fresh ephemeral X25519 keypair is generated for this session
- Both clients exchange ephemeral + identity public keys via hidden protocol messages
- The recipient sees an accept/reject popup before completing the handshake
- TOFU checks the peer's identity key against
known_keys.toml — a warning is shown if the key changed since last session
- In-chat status messages show handshake progress, key fingerprints, and TOFU result
- Once established, all messages in that DM are encrypted with ChaCha20-Poly1305 using directional keys
- A lock icon (🔒) appears next to the nick in the channel list; a checkmark (✔) if the peer is verified
- Run
:verify to display a 6-word SAS code — compare out-of-band with your peer, then :verified to mark them trusted
- Run
:unsecure to end the session
- 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.
rvIRC clients can send files to each other using magic-wormhole:
- Sender runs
:sendfile <nick> /path/to/file, or :sendfile in a DM to open a file browser
- A wormhole code is generated and sent to the recipient via an rvIRC protocol message
- The recipient sees a popup:
nick wants to send you file.txt (1.2 MB). Accept?
- If accepted: file is saved to
download_dir if configured, otherwise a directory browser appears
- A progress popup shows transfer bytes during the wormhole relay
Works inside encrypted DM sessions (the wormhole code itself is encrypted in transit).
See LICENSE for details.