A lightweight terminal chat with real-time messaging over WebSockets, optional E2E encryption, and a flexible plugin ecosystem. Built for developers who prefer the command line.
Quick start: QUICKSTART.md for a single-page walkthrough (install, server, client, and where to read next).
E2E in one sentence: Optional chat encryption uses a shared symmetric key (ChaCha20-Poly1305) distributed out of band (MARCHAT_GLOBAL_E2E_KEY, keystore, or equivalent). It is not pairwise Signal-style key exchange: anyone who holds the key can decrypt message and file payloads. See SECURITY.md and PROTOCOL.md.
Good fit: self-hosted teams, terminal-first chat, SQLite or Postgres/MySQL, and optional plugins. Poor fit: hosted SaaS you do not run, or a threat model that requires per-user forward secrecy without a shared secret.
| If you are... | Start here |
|---|---|
| Trying marchat | QUICKSTART.md |
| Running a server in production | QUICKSTART.md, deploy/CADDY-REVERSE-PROXY.md, PACKAGING.md |
| Writing a plugin | PLUGIN_ECOSYSTEM.md, plugin/README.md |
| Contributing code | CONTRIBUTING.md, TESTING.md, ARCHITECTURE.md |
| Reviewing security | SECURITY.md, PROTOCOL.md |
| Prefer a graphical client | marchat_flutter, marchat-gui |
marchat stays terminal-first in this repository, with the TUI as the reference client.
Both clients follow the same wire format documented in PROTOCOL.md.
Latest tagged release: v1.3.0 (2026-07-06). Narrative history: CHANGELOG.md. Assets and tags: GitHub releases.
main branch: may include changes not yet in that tag (for example items under Unreleased in CHANGELOG.md). Feature descriptions elsewhere in this README match the tree you build from source; compare your binary’s -doctor / -version output to the release page when in doubt.
Screen recordings of a current build (GIF autoplay depends on the viewer).

marchat-server -doctor)

:theme)
marchat-client -doctor)
read_receipt when the transcript is scrolled to the newest messagesMARCHAT_GLOBAL_E2E_KEY), including file transfersdocker-compose.yml for local dev; optional TLS reverse proxy via Caddy (guide)/health and /health/simple endpoints with system metricsmarchat-client -doctor and marchat-server -doctor (or -doctor-json) summarize environment, resolved paths, and configuration healthmarchat started as a fun weekend project for father-son coding sessions and has evolved into a lightweight, self-hosted terminal chat application designed specifically for developers who love the command line. It supports SQLite by default and can also run against PostgreSQL or MySQL for larger deployments.
Key Benefits: - Self-hosted: No external services required - Cross-platform: Linux, macOS, Windows, and Android/Termux - Secure: Optional E2E encryption with ChaCha20-Poly1305 (global symmetric key) - Extensible: Plugin ecosystem for custom functionality - Lightweight: Minimal resource usage, perfect for servers
openssl rand -hex 32
Option A: Environment Variables (Recommended)
export MARCHAT_ADMIN_KEY="your-generated-key"
export MARCHAT_USERS="admin1,admin2"
./marchat-server
# With admin panel
./marchat-server --admin-panel
# With web panel
./marchat-server --web-panel
Option B: Interactive setup (first run, missing required config)
./marchat-server --interactive
Runs a guided wizard only when MARCHAT_ADMIN_KEY or MARCHAT_USERS is not set. If they are already in the environment or config/.env, the server starts normally and --interactive does nothing extra.
# Admin connection
./marchat-client --username admin1 --admin --admin-key your-key --server ws://localhost:8080/ws
# Regular user
./marchat-client --username user1 --server ws://localhost:8080/ws
# Or use interactive mode
./marchat-client
Tables created by the server (dialect-aware DDL for SQLite, PostgreSQL, and MySQL):
- messages: Core message storage with message_id, encryption fields, edit/delete/pin flags, recipient, and persisted channel
- user_message_state: Per-user last_seen timestamp after handshake (last_message_id column is legacy/unused for replay)
- ban_history: Ban/unban event tracking for history gaps
- message_reactions: Durable emoji reactions (unique per message + user + emoji)
- user_channels: Last channel per user, persisted across reconnects
- read_receipts: Per-user read receipt state tracking
Binary Installation:
# Linux (amd64)
wget https://github.com/Cod-e-Codes/marchat/releases/download/v1.3.0/marchat-v1.3.0-linux-amd64.zip
unzip marchat-v1.3.0-linux-amd64.zip && chmod +x marchat-*
# macOS (amd64)
wget https://github.com/Cod-e-Codes/marchat/releases/download/v1.3.0/marchat-v1.3.0-darwin-amd64.zip
unzip marchat-v1.3.0-darwin-amd64.zip && chmod +x marchat-*
# Windows - PowerShell
iwr -useb https://raw.githubusercontent.com/Cod-e-Codes/marchat/main/install.ps1 | iex
Package managers:
# Homebrew (macOS / Linux): https://github.com/Cod-e-Codes/homebrew-marchat
brew tap cod-e-codes/marchat
brew install marchat
# Scoop (Windows): https://github.com/Cod-e-Codes/scoop-marchat
scoop bucket add marchat https://github.com/Cod-e-Codes/scoop-marchat
scoop install marchat
winget: winget install Cod-e-Codes.Marchat (listed on winget). Maintainer notes and checksum alignment: PACKAGING.md.
See PACKAGING.md and packaging/ for Homebrew, Scoop, winget, Chocolatey, and AUR: canonical manifest templates here, installs available on those ecosystems, and how each release lines up with downstream publishes.
Docker:
docker pull codecodesxyz/marchat:v1.3.0
docker run -d -p 8080:8080 \
-e MARCHAT_ADMIN_KEY=$(openssl rand -hex 32) \
-e MARCHAT_USERS=admin1,admin2 \
codecodesxyz/marchat:v1.3.0
Docker Compose (local development):
The server service loads config/.env first, then a project-root .env (both optional and gitignored). Put MARCHAT_ADMIN_KEY and MARCHAT_USERS in either file (see Essential Environment Variables). Compose also sets MARCHAT_DB_PATH=/data/marchat.db so SQLite uses the attached volume.
Example snippet for config/.env or .env (generate a strong key for anything reachable from a network):
MARCHAT_ADMIN_KEY=your-secret-here
MARCHAT_USERS=admin1,admin2
Then:
docker compose up -d
TLS reverse proxy (Caddy, optional): To terminate TLS in front of a host-native marchat-server (plain HTTP on port 8080), use docker-compose.proxy.yml, deploy/caddy/Caddyfile, and deploy/caddy/proxy.env.example plus optional gitignored deploy/caddy/proxy.env for MARCHAT_CADDY_EXTRA_HOSTS (public IP/DNS on tls internal). Published port 8443 maps to HTTPS/WebSocket inside the container; clients use wss://localhost:8443/ws (with --skip-tls-verify while using Caddy’s internal CA). The proxy stack must be running whenever you use that URL. Full steps, helper scripts (scripts/build-windows.ps1 / scripts/build-linux.sh, scripts/connect-local-wss.ps1 / scripts/connect-local-wss.sh), source changes, and breaking notes: deploy/CADDY-REVERSE-PROXY.md.
From Source:
git clone https://github.com/Cod-e-Codes/marchat.git && cd marchat
go mod tidy
go build -o marchat-server ./cmd/server
go build -o marchat-client ./client
Prerequisites for source build:
- Go 1.25.11 or later (download)
- Linux clipboard support: sudo apt install xclip (Ubuntu/Debian) or sudo yum install xclip (RHEL/CentOS)
Terminal colors: The server startup banner and the client’s pre-chat output (connection, E2E status, profile picker tags such as [Admin] / [E2E], and auth prompts) use Lip Gloss v2 (charm.land/lipgloss/v2) for emphasis. Set NO_COLOR=1 (or NO_COLOR) in the environment to disable colors on plain stdout/stderr.
| Variable | Required | Default | Description |
|---|---|---|---|
MARCHAT_ADMIN_KEY |
Yes | - | Admin authentication key |
MARCHAT_USERS |
Yes | - | Comma-separated admin usernames |
MARCHAT_PORT |
No | 8080 |
Server port |
MARCHAT_DB_PATH |
No | ./config/marchat.db |
Database path/DSN. Supports SQLite file path, postgres://..., or mysql:... |
MARCHAT_TLS_CERT_FILE |
No | - | TLS certificate (enables wss://) |
MARCHAT_TLS_KEY_FILE |
No | - | TLS private key |
MARCHAT_GLOBAL_E2E_KEY |
No | - | Base64 32-byte global E2E key (server and/or client). On the client, if set, it overrides the key from keystore.dat for that run only; the keystore file is not updated. See E2E Encryption. |
MARCHAT_MAX_FILE_BYTES |
No | 1048576 |
Max file size in bytes (1MB default) |
MARCHAT_MAX_FILE_MB |
No | 1 |
Max file size in MB (alternative to bytes) |
MARCHAT_ALLOWED_USERS |
No | - | Username allowlist (comma-separated) |
MARCHAT_ALLOWED_ORIGINS |
No | - | Extra WebSocket Origin values (comma-separated URLs or hostnames) |
MARCHAT_TRUSTED_PROXIES |
No | - | Reverse-proxy IPs/CIDRs; when set, X-Forwarded-For / X-Real-IP are honored for client IP and admin login rate limits |
Additional variables: MARCHAT_LOG_LEVEL, MARCHAT_CONFIG_DIR, MARCHAT_BAN_HISTORY_GAPS, MARCHAT_PLUGIN_REGISTRY_URL
MARCHAT_DB_PATH accepts either a SQLite pa
$ claude mcp add marchat \
-- python -m otcore.mcp_server <graph>