xtc
XtermChat
v1.0.0 Stable

XtermChat
Documentation

Self-hosted terminal chat. Your server, your data, your rules. Setup in under 10 minutes.

~5 min
Server setup
~30 MB
RAM usage
2 deps
pip packages
MIT
License

01 Introduction

XtermChat is a lightweight, self-hosted chat system built for the terminal. Unlike third-party platforms, XtermChat stores nothing on external servers. You deploy the server on your own VPS, connect with the CLI client or a browser, and all messages stay on infrastructure you control.

It is designed for developers and sysadmins who want something simple that just works — without the overhead of Matrix, the enterprise focus of Mattermost, or the data concerns of Slack.

XtermChat Matrix Mattermost Slack
Self-hosted✓ Yes✓ Yes✓ Yes✗ No
Open source✓ MIT✓ Yes⚠ Partial✗ No
Setup time~5 min~2 hrs~30 minInstant
RAM usage~30 MB~1–2 GB~300 MB
Terminal-first✓ Yes✗ No✗ No✗ No
Your data✓ Yes✓ Yes✓ Yes✗ No

02 How it works

XtermChat has two parts: a server you deploy on a VPS, and a client your team installs on their machines. They communicate over a simple REST API with JSON payloads.

architecture
┌─────────────────────────────────────────────────┐ │ YOUR VPS (:8080) │ │ xtc-server Python + Flask + SQLite │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ rooms │ │ messages │ │ users │ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ xtc.db (single file) │ └──────────────────┬──────────────────────────────┘ │ REST API over HTTP / HTTPS ┌─────────┼──────────┐ │ │ │ ┌──────▼───┐ ┌───▼────┐ ┌───▼───────┐ │ CLI Mac │ │Browser │ │ CLI Linux │ │ xtc-cli │ │ :5000 │ │ xtc-cli │ └──────────┘ └────────┘ └───────────┘
🐍
Backend

Python + Flask. Two pip packages. SQLite3 — no external database needed.

⌨️
CLI Client

Built with prompt_toolkit. Full TUI with sidebar, emoji, link detection, and copy.

🌐
Web Client

Flask serves static HTML. Same API as CLI — web and terminal users share rooms.

🔑
Auth

CLI uses hardware UUID (device-bound). Web uses a 5-digit PIN. No accounts.

03 Requirements

🖥️
Server
  • Ubuntu 20.04+ / Debian 11+
  • Python 3.8+ and pip
  • Port 8080 open (TCP)
  • 512 MB RAM minimum
  • Any $4–6/month VPS
💻
CLI Client
  • macOS 11+ or Linux
  • Python 3.8+ and pip
  • make for install
  • UTF-8 terminal
  • xclip or xsel (Linux copy)
🪟
Windows
  • Python 3.10+ from python.org
  • Windows Terminal recommended
  • Run python xtc.py directly
  • Clipboard via clip built-in
💡

The cheapest VPS tier (DigitalOcean, Hetzner, Vultr — $4–6/month, 512 MB RAM) is more than enough for a team of 2–50 people.

04 Server setup

SSH into your VPS and follow these steps. The entire process takes about 5 minutes.

1

Clone the server repository

git clone https://github.com/dnysaz/xtc-server.git
cd xtc-server
2

Install dependencies

pip3 install flask flask-cors werkzeug
3

Open port 8080

sudo ufw allow 8080
sudo ufw enable
sudo ufw status
⚠️

If your VPS has a separate firewall panel (DigitalOcean, Vultr), also open TCP 8080 there.

4

Start the server

python3 server.py start
# [*] Server started in background (PID: 12345)
5

Verify it's running

curl http://localhost:8080
# {"service": "XtermChat Gateway", "status": "online", "version": "1.1"}

File structure

xtc-server/
xtc-server/ ├── server.py Flask app, all API routes ├── db.py Database init and connection ├── room.py Room CRUD, password hashing ├── connection.py Message save and retrieval ├── check_db.py Interactive database inspector ├── xtc.db SQLite database (auto-created) ├── server.log Output log (auto-created) └── server.pid Process ID file (auto-created)

Custom port

Default is 8080. Edit the bottom of server.py to change it:

elif command == "run_internal":
    app.run(host='0.0.0.0', port=9000)  # ← change here
else:
    app.run(host='0.0.0.0', port=9000)  # ← and here

05 Client setup

macOS / Linux

1

Clone the client

git clone https://github.com/dnysaz/xtc-client.git
cd xtc-client
2

Install — creates global xtc command

make install
3

Verify installation

xtc

Windows (native — no WSL needed)

1

Install Python 3.10+ from python.org — check "Add to PATH" during install

2

Install Windows Terminal from the Microsoft Store for best Unicode and emoji support

3

Clone and install

git clone https://github.com/dnysaz/xtc-client.git
cd xtc-client
pip install -r requirements.txt
4

Run

python xtc.py
ℹ️

Configuration saved to ~/.xtc_config.json. Managed by xtc connect and xtc disconnect.

06 First time use

bash
# 1. Connect to your server $ xtc connect @103.45.67.89:8080 [*] Configuration saved [*] Linked to gateway: http://103.45.67.89:8080 # 2. Verify connection $ xtc status GATEWAY : http://103.45.67.89:8080 SERVICE : XtermChat Gateway v1.1 STATUS : ONLINE (HTTP 200) LATENCY : 42 ms # 3. Create a room $ xtc create:room @general [*] SUCCESS: Room '@general' is live (public 🌐) # 4. Start chatting $ xtc start:chat @general

07 CLI commands

CommandArgsDescription
xtc connect@IP:PORTSave server address to config
xtc disconnect@IP:PORTRemove saved server config
xtc statusCheck connection and latency
xtc list:roomsList all rooms on the server
xtc create:room@name [pass]Create a room. No args = interactive
xtc delete:room@nameDelete a room (creator only)
xtc start:chat@nameOpen interactive chat TUI
xtc start:webStart web UI at localhost:5000
# Connect to server
xtc connect @103.45.67.89:8080

# Create a private room with password
xtc create:room @devteam mypassword

# Interactive room creation
xtc create:room

# Join a private room (password prompted automatically)
xtc start:chat @devteam

08 Chat interface

Running xtc start:chat opens a full terminal UI with three panels and a dual-focus mode system.

chat UI layout
XtermChat-CLI | NODE: mac-dana | 103.45.67.89 | [ INPUT ] ┌──────────────────┬──────────────────────────────────┬─────────────────┐ │ ID CHANNEL KETUT now You hello everyone #GENERAL 2m AGUNG hey! GATEWAY 5m You https://x.com CREATOR 103.45.67.89 KETUT AUTH LOCKED 14 Mar 2026 └──────────────────┴──────────────────────────────────┴─────────────────┘ ✏️ INPUT MODE │ ENTER: SEND │ TAB: → CHAT │ Ctrl+X: EXIT Message ❯❯❯ _

Focus modes

✏️ INPUT MODE (default)

Cursor in message box. Type and send. Commands :clear, :purge, :e active.

📋 CHAT MODE

Cursor in chat history. Scroll, select text, copy, open links. Auto-scroll pauses.

Keyboard shortcuts

Global

TabSwitch INPUT ↔ CHAT mode EscapeReturn to INPUT mode / close emoji panel Ctrl+XExit chat PageUpScroll chat up 12 lines PageDownScroll chat down 12 lines

INPUT MODE

EnterSend message Ctrl+CExit chat :clearClear local chat display (no server effect) :purgeDelete all messages from server (creator only) :eToggle emoji shortcuts panel :qQuit chat

CHAT MODE (after pressing Tab)

Scroll line by line HomeJump to top of chat EndJump to bottom, resume auto-scroll
Shift+Select character by character Shift+Select line by line Ctrl+Shift+Select next word Ctrl+Shift+Select previous word
Ctrl+CCopy selected text to clipboard Ctrl+LOpen link at cursor in browser

09 Web interface

XtermChat includes a browser-based UI. Start it with:

xtc start:web
# URL: http://localhost:5000

Open http://localhost:5000. Enter server IP, port, username, and a 5-digit PIN to connect.

ℹ️

Web PIN is a 5-digit number you choose — unlike CLI which uses hardware UUID automatically. Both clients share the same rooms and messages.

Web vs CLI

CLIWeb
PIN typeHardware UUID (auto)5-digit (user-chosen)
Device-bound✓ Yes✗ No
InstallationRequiredNone (browser only)
Mobile support✗ No✓ Yes

10 Room management

# Public room — anyone on the server can join
xtc create:room @general

# Private room — password required
xtc create:room @team secretpassword

# Interactive mode — prompts for name, password, description
xtc create:room

Private rooms appear in xtc list:rooms with a LOCKED label. Passwords are stored as bcrypt hashes — the raw password is never saved.

Creator privileges

When you create a room, your hardware UUID is recorded as creator_pin. Only you (from the same machine) can:

  • Delete the room: xtc delete:room @roomname
  • Purge all messages: type :purge inside chat
⚠️

:purge deletes all messages permanently from the server. The room itself remains. This action cannot be undone.

11 Emoji shortcuts

Type any shortcut in a message — it auto-converts when you press Enter. Type :e to open the reference panel inside the chat.

ShortcutEmojiShortcutEmojiShortcutEmoji
:fire🔥:check:robot🤖
:rocket🚀:warn⚠️:bug🪲
:nice👍:heart❤️:coffee
:cool😎:star:beer🍺
:laugh😂:ghost👻:globe🌐
:smile😊:party🎉:key🔑
:pray🙏:100💯:skull💀
:muscle💪:zap:lock🔒
:cloud☁️:box📦:top🔝

12 Identity & security

How CLI identity works

identity flow
machine UUID (IOPlatformUUID / /etc/machine-id) CLI_PIN (hardware-bound, auto-generated) sent with every message and room operation server: users table → username:PIN mapping mismatch → 403 Identity mismatch. Wrong PIN.

Server-side validation

ActionValidation
Send messageUsername + PIN must match registered record
Create roomPIN stored as creator_pin
Purge messagesRequester PIN must match creator_pin
Delete roomUsername + PIN must match creator
Join private roomRoom password (verified via bcrypt)
Web loginUsername + PIN must match registered record

Default setup uses HTTP, acceptable for private/internal networks. For public-facing deployments use HTTPS — see the next section.

13 HTTPS setup

No client code changes needed. Just point your client at an https:// URL after setup.

Option A — Caddy (recommended)

Automatically obtains and renews SSL certificates from Let's Encrypt.

sudo apt install caddy -y

# /etc/caddy/Caddyfile
yourdomain.com {
    reverse_proxy localhost:8080
}

sudo systemctl reload caddy

Option B — Nginx + Let's Encrypt

sudo apt install nginx certbot python3-certbot-nginx -y
sudo certbot --nginx -d yourdomain.com
server {
    listen 443 ssl;
    server_name yourdomain.com;
    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

After setup, update your client:

xtc connect @yourdomain.com

14 Server management

Start / stop

python3 server.py start    # start in background
python3 server.py stop     # stop
python3 server.py          # run in foreground (debug)

Check status & logs

cat server.pid
ps aux | grep server.py
tail -f server.log
grep "ERROR\|Exception" server.log

systemd service (auto-restart on reboot)

# /etc/systemd/system/xtermchat.service
[Unit]
Description=XtermChat Server
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/xtc-server
ExecStart=/usr/bin/python3 /home/ubuntu/xtc-server/server.py run_internal
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable xtermchat
sudo systemctl start xtermchat

Update & backup

# Update
cd xtc-server && python3 server.py stop && git pull && python3 server.py start

# Backup — entire state is one file
cp xtc.db xtc_backup_$(date +%Y%m%d_%H%M).db

15 Database

Everything is stored in xtc.db — a single SQLite file auto-created on first run.

TableColumnsPurpose
usersusername PK, pinIdentity registry
roomsname UNIQUE, creator, password, description, created_at, creator_pinRoom config & ownership
messagesid AI, room, sender, pin, content, timestampMessage history
# Inspect with check_db.py
python3 check_db.py

# Direct SQLite
sqlite3 xtc.db
.tables
SELECT * FROM rooms;
SELECT COUNT(*) FROM messages WHERE room='general';
SELECT * FROM messages ORDER BY id DESC LIMIT 20;
.quit

# Full reset
python3 server.py stop && rm xtc.db && python3 server.py start

16 API reference

Base URL: http://YOUR_SERVER:8080 — all endpoints return JSON.

GET / Health check
{"status": "online", "service": "XtermChat Gateway", "version": "1.1"}
POST /login Register or verify identity
// Request
{"user": "ketut", "pin": "12345"}

// 200 — PIN matches    {"status": "success", "message": "Welcome back"}
// 201 — new user       {"status": "success", "message": "New identity registered"}
// 403 — PIN mismatch   {"status": "failed", "message": "Identity locked to another device/PIN."}
GET /rooms List all rooms
{"status": "success", "count": 2, "rooms": [
  {"name": "general", "has_password": false, "creator": "KETUT",
   "description": "General chat", "created_at": 1741234567}
]}
POST /send Send a message
{"room": "general", "password": "", "user": "KETUT", "content": "Hello world", "pin": "UUID"}
// 201 → ok | 403 → PIN mismatch | 400 → empty or too long (max 4000 chars)
GET /messages/<room> Get messages
GET /messages/general?password=
// Response: [{"sender": "KETUT", "content": "Hello", "pin": "UUID", "timestamp": "2026-03-14 08:30:00"}]
// 401 → password required
POST /purge-chat Delete all messages (creator only)
{"room": "general", "user": "ketut", "pin": "UUID"}
// 200 → ok | 403 → Unauthorized: Hardware ID mismatch
POST /delete-room Delete room permanently (creator only)
{"room": "general", "user": "ketut", "pin": "UUID"}
// 200 → ok | 403 → Unauthorized: Hardware ID mismatch

17 Troubleshooting

Connection refused

curl http://YOUR_IP:8080       # test direct access
sudo ufw status               # check firewall
cat server.pid                # check if server running
ps aux | grep server.py

Identity locked to another device

Username registered from a different machine. Options:

# Option 1 — use a different system username
# Option 2 — admin removes the old entry
sqlite3 xtc.db "DELETE FROM users WHERE username='yourname';"

Access denied on purge or delete

You must be on the same machine that created the room. Hardware PIN is device-specific.

Server won't start — port in use

sudo lsof -i :8080
sudo kill -9 <PID>
rm server.pid && python3 server.py start

Copy not working (Linux)

sudo apt install xclip   # or: sudo apt install xsel

Chat scroll not working

Minimum terminal size is 80×24. Press Tab to enter CHAT MODE first, then use arrow keys or PageUp/PageDown.

Server crashed / not responding

tail -50 server.log
rm server.pid && python3 server.py start

18 FAQ

Can multiple people be in the same room at the same time?

Yes. Messages are polled every 2 seconds. All connected users see new messages automatically.

Are messages stored permanently?

Until the creator runs :purge (wipes messages, keeps room) or xtc delete:room (removes everything). Delete xtc.db to reset completely.

Can I run server and client on the same machine?

xtc connect @localhost:8080

What if I reinstall my OS or switch machines?

Hardware PIN changes. Admin can reset:

sqlite3 xtc.db "DELETE FROM users WHERE username='yourname';"

Can web and CLI users chat together?

Yes. Both use the same API and see each other's messages in real time.

How many users can XtermChat handle?

SQLite handles millions of rows comfortably. Fine for 2–50 people on the cheapest VPS. For very high traffic, migrate to PostgreSQL by modifying db.py.

How do I update?

# Server
cd xtc-server && python3 server.py stop && git pull && python3 server.py start

# Client
cd xtc-client && git pull && make install

Database and config are preserved through updates.