matterbox

A fast terminal client for Mattermost — a full TUI, a scriptable CLI, and every message you have read already on your own disk.

$ matterbox welcome

The first-run wizard — that is it running behind this page. Install →

scroll

What it looks like

Chat in a terminal sounds like a trade. Images preview inline, reactions render, threads open beside the conversation, drafts follow you to the webapp — none of it is missing, all of it is text.

╭─────╮╭─────────╮╭────────╮╭───────────╮╭─────────╮                                                                                                  
 DMs ││ Feed 1! ││ Search  Orbit [1]  Lab [2]                                                                                                   
├─────┴┴─────────┴┴───────┬┴┘           └┴─────────┴───────────────────────────────────┬┬────────────────────────────────────────────────────────────┐
Channels                 #releases                                                   Thread · 4 replies                                          
  #general 3                                                                         sofie  07:49  · root                                        
  #random 1                                                                            Cutting 0.9 tomorrow — anything still open?               
> #releases                🖼️ DSCF5311.jpg (4416×2944, 6.1MB)                      teun  07:57                                                 
  #terminal              jonas  07:53                                                  Only the docs pass, and that is not a blocker.            
                           The octant fallback for terminals without the new block     mira  08:03                                               
                           glyphs — small, up in an hour.                                It is if the rules examples are wrong — people copy     
                           👍 2                                                        those verbatim.                                         
                         mira  08:16                                                     teun  08:07                                             
                           Semantic search reindexes on every start since the              Fair. I'll take the rules page then.                  
                           embedding-dim change:                                     corne  09:15                                                
                                                                                       Fixed in docs/rules.md — every: 15m.                      
                           ```sh                                                                                                                 
                           matterbox embed --reset                                                                                               
                           ```                                                                                                                   
                         teun  08:25                                                                                                             
                           Confirmed on a cold cache: 12k messages, ~40s on CPU.                                                                 
                           Fine, just quiet about it.                                                                                            
                           👀 1                                                                                                                
                         deploybot  08:33                                                                                                        
                           pipeline #4812 passed on main — linux/amd64, linux/arm64,                                                             
                           darwin/arm64 built in 2m18s                                                                                           
                         sofie  08:44                                                                                                            
                           Nice. @corne did drag-and-drop get the macOS paste-is-a-                                                              
                           path check?                                                                                                           
                         corne  08:48                                                                                                            
                           Both now. A paste that is nothing but existing file paths                                                             
                           becomes an attachment; attach_on_drop: false opts out.                                                                
                           🎉 3                                                                                                                
                         jonas  08:59                                                                                                            
                           Then we're good. I'll tag once the octant patch lands.                                                                
                         mira  09:11                                                                                                             
                           One more: the rules docs still say every: takes seconds.                                                              
                         │▎ corne  09:15                                                                                                        
                         │▎ Fixed in docs/rules.md — every: 15m.                      ├────────────────────────────────────────────────────────────┤
                                                                                     > message…                                                  
└─────────────────────────┴────────────────────────────────────────────────────────────┘└────────────────────────────────────────────────────────────┘
i compose open thread/ search channelf filter? helpf1 command paletteI channel infoR reactalt+1…9 go to team           cornea mossy oak branch hung with ferns
╭─────╮╭─────────╮╭────────╮╭───────────╮╭─────────╮                                
 DMs ││ Feed 1! ││ Search  Orbit [1]  Lab [2]                                 
├─────┴┴─────────┴┴───────┬┴┘           └┴─────────┴───────────────────────────────┐
Channels                 #releases                                               
  #general 3             teun  08:25                                             
  #random 1                Confirmed on a cold cache: 12k messages, ~40s on      
> #releases                CPU. Fine, just quiet about it.                       
  #terminal                👀 1                                                
                         deploybot  08:33                                        
                           pipeline #4812 passed on main — linux/amd64,          
                           linux/arm64, darwin/arm64 built in 2m18s              
                         sofie  08:44                                            
                           Nice. @corne did drag-and-drop get the macOS paste-   
                           is-a-path check?                                      
                         corne  08:48                                            
                           Both now. A paste that is nothing but existing file   
                           paths becomes an attachment; attach_on_drop: false    
                           opts out.                                             
                           🎉 3                                                
                         jonas  08:59                                            
                           Then we're good. I'll tag once the octant patch       
                           lands.                                                
                         mira  09:11                                             
                           One more: the rules docs still say every: takes       
                           seconds.                                              
                         │▎ corne  09:15                                        
                         │▎ Fixed in docs/rules.md — every: 15m.                  
                         ────────────────────────────────────────────────────────
                         > message…                                              
└─────────────────────────┴────────────────────────────────────────────────────────┘
i compose open thread/ search channelf filter? help             corne

The feed

Everything unread across every team and DM in one list, newest first, each channel with a couple of already-read messages above the divider so you know what you are walking into. Muted channels stay out unless you ask.

╭─────╮╭─────────╮╭────────╮╭───────────╮╭─────────╮                                                                                                  
 DMs  Feed 1!  Search ││ Orbit [1] ││ Lab [2]                                                                                                   
├─────┴┘         └┴────────┴┴───────────┴┴─────────┴─────────────────────────────────────────────────────────────────────────────────────────────────┐
Unread Feed  3 channels  ·  ↵ open · R reply · m mark read · r refresh                                                            Mark all read (A) 
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│┌─ Orbit › #general · 3 new ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐  
││ teun  07:42  Coffee machine is fixed. That is the whole announcement.                                                                            
││ mira  08:06  Heroic. Who do we thank?                                                                                                            
││ ─ new ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────   
││ teun  08:53  Whoever left the descaling kit on my desk, anonymously.                                                                             
││ sofie  09:05  Heads up: the office router reboots at 19:00, so `listen` drops its socket for a minute.                                           
││ jonas  09:14  It reconnects on its own — you lose the WebSocket, not the cache.                                                                  
│└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘  
                                                                                                                                                    
┌─ DMs › @sofie · 2 new · 1 mention ─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐  
 corne  06:17  Sent you the branch — the octant table is the only interesting part.                                                               
 ─ new ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────   
 sofie  09:08  Read it. One question about the tie-breaking and then I am happy.                                                                  
 sofie  09:13  @corne why keep the earlier family on an exact tie?                                                                                
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘  
                                                                                                                                                    
┌─ Orbit › #random · 1 new ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐  
 mira  04:17  My terminal now has more fonts than my laptop has RAM.                                                                              
 ─ new ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────   
 teun  08:30  That is not a problem, that is a hobby.                                                                                             
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘  
                                                                                                                                                    
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
↑/k up↓/j down openm mark read? helpR reply in threadr refreshM show/hide muted channelstab focus next                corne

What it does

Everything below runs against a stock Mattermost server. Nothing here needs a plugin, an admin, or anyone else to install anything.

A terminal UI, not a wrapper

Teams, channels, DMs, threads, reactions, attachments, image and video previews, per-channel drafts synced with the webapp — live over WebSocket, in a client that starts instantly and never asks you to wait for a spinner.

A scriptable CLI

send, read, search, react, unread, digest — most with --json, all with shell completion. Chat becomes something cron and pipes can reach.

Your history, on your disk

Every message you see lands in a local SQLite database, so reopening a channel is instant and history stays searchable with the network off. There is a read-only SQL tab if you would rather ask it yourself.

Search that finds it

FTS5 keyword search over the cache, optionally blended with vector search from a local embeddings model. End a query with ? and a local model digs through your channels to answer it. Nothing leaves the machine.

A rules engine

The listen daemon reacts to what happens on your server: match on team, channel, author, text or mention and notify, run a command, POST a webhook, post back, react — or fire on a cron. It stays quiet about whatever you are reading.

Jira and GitLab inline

Press v on an issue link or a merge request and a side panel opens. Edit status, priority, points and assignee; check the pipeline; approve or merge — without leaving the terminal.

Install

Pure Go. Two commands from a clone to a running client.

From source

$ git clone https://github.com/cornedor/matterbox
$ cd matterbox
$ make install   # builds, installs to ~/.local/bin, sets up completion

Optional extras need C libraries; make compiles in whatever your machine already has and make tags says what it picked. Plain Go works too, without them:

$ go build -o matterbox .

Releases ship prebuilt Linux and macOS binaries for amd64 and arm64 on the releases page — pure Go, so they need no toolchain, but they carry neither optional feature. Drop one in ~/.local/bin.

First run

Point it at your server and log in. Running matterbox with no arguments launches this wizard on first run — it is the screen behind this page.

$ matterbox welcome

Or write ~/.config/matterbox/config.yaml yourself; the only required key is server_url. Then matterbox login opens your browser for SSO and saves the token.

The CLI

Run matterbox with no arguments for the TUI. The subcommands are for scripts. Channels are addressed as team/channel, or @username for a DM.

CommandWhat it does
matterbox send <channel> [msg]Post a message (--file to attach, repeatable)
matterbox reply <post-id> [msg]Reply in a thread, recording which message you answered
matterbox read [channel]Print recent messages (--since, --from, --thread, --wait, --json)
matterbox unreadList unread messages grouped by channel
matterbox search <query>Search the local cache (--semantic, --channel, --context)
matterbox channelsList every team and channel with its address
matterbox listenBackground daemon: keeps the cache warm, runs your rules
matterbox rules testSay which rules a message fires, and why the rest do not
matterbox keysEvery keyboard action, its default key, and your overrides

The optional parts

Summaries, semantic search and agentic search talk to any OpenAI-compatible endpoint. Point them at a local llama.cpp server and nothing leaves your machine — or leave them off entirely and matterbox never asks again.

The listen daemon is optional too. It holds a WebSocket open, writes every message into the cache, and — if you configure it — bridges your mentions and DMs to Telegram, quietly skipping anything you are already reading on screen.