tuilegram
A real Telegram client in your terminal.
Pure Go. No tdlib. No Electron. No CGo.
Features ·
Install ·
First run ·
Keys ·
Security ·
Architecture ·
FAQ
What it is
A keyboard-driven Telegram user client (not a bot) that runs in any terminal with truecolor support. Built on:
- gotd/td — pure-Go MTProto implementation. No tdlib, no CGo, single static binary.
- Charmbracelet — bubbletea (Elm-style runtime), lipgloss (styling), bubbles (widgets), huh (forms).
Authenticates as a regular user account → access to full chat history, groups, channels, media. Same RPCs as the official desktop client.
Status
Active development. Implementation follows a 34-step incremental pipeline. Steps 1–33 done, Step 34 (style revamp) in review. All steps end with a runnable build.
Use it. Don't trust it with your only Telegram account yet — test with a secondary number first.
Disclosure
Partially built with AI assistance. Architecture, decisions, and reviews are mine; AI helped with parts of the implementation. Full design trail (pipeline, ADRs, formal specs) under docs/design/.
Features
Messaging
- Send / reply / edit / delete (single + batch) / forward
- Cursor-based message selection, multi-select with
Space
- Inline reactions display
- Reply quote rendering
- Pinned message bar
- Link detection + open in browser (
gx chord)
- Forward picker with destination chooser
- Service messages (joins, pins, etc.) styled separately
Navigation
- Vim-style:
j/k, g/G, Ctrl+D/U, gg, gu (jump unread), zz (center), gi (chat info)
- Mouse: scroll wheel, click chat, click SEND
- Tab bar for Telegram cloud folders (
1-9 to switch)
- Folder sidebar (
F toggle) — uses server-side DialogFilters
- Compact / Wide layout — auto-switches at 100 columns
Search
- Global search (
/) — fuzzy, debounced, jump-to-message
- In-chat search (
Ctrl+F) — highlight + next/prev navigation
Overlays (Crush-style — modal composited on background, base view never blanks)
- Command palette (
Ctrl+P) with logout entry
- Keybindings help (
?)
- Which-key hints for chord prefixes
Theming
- TOML themes with hot-reload (atomic swap on file change)
- 8-color sender palette per group chat (deterministic by sender ID)
- Default theme = Crush-inspired (magenta + violet + dark surface)
- Legacy theme preserved for rollback
Connection
- Health monitor with live status indicator
- Reconnect detection
- Typing indicators (per peer)
- Real-time updates via gotd
UpdateDispatcher
Auth
- Phone + verification code + 2FA password
- Session persisted to
session.json (auth key, never committed)
- Logout from command palette → revokes server-side + removes local session
Install
From source
git clone https://github.com/strawberry-code/tuilegram.git
cd tuilegram
make build
./tuilegram
Requirements
- Go 1.21+
- Terminal with truecolor (most modern terminals: iTerm2, Alacritty, kitty, WezTerm, recent gnome-terminal)
- A Telegram account
First run
You need an app_id and app_hash from Telegram. Get them at https://my.telegram.org/apps (takes 1 minute).
Two options:
Option 1 — env vars (recommended for development)
export TELEGRAM_APP_ID=12345
export TELEGRAM_APP_HASH=abcdef0123456789
make run
Option 2 — embedded constants
Edit internal/telegram/config.go and set the constants directly. Useful for personal builds.
On first launch you'll be asked for your phone number, then the verification code Telegram sends, then your 2FA password if enabled. After that, session.json is written and subsequent runs skip auth.
Keys
| Key |
Action |
j / k |
Move cursor down/up |
g g / G |
Top / bottom |
Ctrl+D / Ctrl+U |
Half-page scroll |
Enter |
Open chat / send message |
i / Tab |
Focus message input |
Esc |
Cancel / close overlay |
r |
Reply to selected message |
e |
Edit selected message |
D |
Delete (single or selected batch) |
f |
Forward |
Space |
Toggle multi-select |
/ |
Global search |
Ctrl+F |
Search in current chat |
Ctrl+P |
Command palette |
? |
Keybindings help |
F |
Toggle folder sidebar |
1–9 |
Switch folder tab |
g u |
Jump to next unread chat |
g i |
Chat info |
z z |
Center current message |
g x |
Open first link in selected message |
Ctrl+Q |
Quit |
? opens the full keybindings reference inside the app.
Security
The session file (session.json) contains the 256-byte MTProto auth key = full account impersonation. Treat it like an SSH private key.
.gitignore excludes *.session, session.json, *.session.json
- The auth key is never logged, printed, or sent anywhere except to Telegram's servers
- Logout from command palette calls
auth.LogOut server-side AND removes the local file
If your terminal scrollback is shared, persisted, or piped — be aware that messages are rendered there too.
Architecture
%%{init: {'theme':'base','themeVariables':{
'background':'#0A0E27',
'primaryColor':'#221D2E',
'primaryTextColor':'#D4D4E0',
'primaryBorderColor':'#C661FE',
'lineColor':'#7B61FE',
'secondaryColor':'#3A3450',
'tertiaryColor':'#221D2E',
'fontFamily':'JetBrainsMono, Menlo, monospace'
}}}%%
flowchart LR
User(("⌨️ You")):::user
Cloud[("☁️ Telegram MTProto Cloud")]:::cloud
subgraph Process[" 📦 cmd/tuilegram (single static binary) "]
direction LR
UI["<b>tea.Program</b><br/><i>UI loop · Elm Architecture</i><br/>Model → Update → View"]:::ui
Bridge["<b>telegram.Bridge</b><br/><i>gotd/td goroutine</i><br/>client.Run(ctx, fn)"]:::bridge
UI -. "<b>tea.Cmd</b><br/>SendMessage<br/>LoadDialogs<br/>Logout …" .-> Bridge
Bridge == "<b>p.Send(updateMsg)</b><br/>NewMessage<br/>Connected<br/>FoldersLoaded …" ==> UI
end
User == "🎹 keys · 🖱️ mouse" ==> UI
UI -- "✨ View() string" --> User
Bridge <== "🔐 MTProto over TLS" ==> Cloud
classDef user fill:#221D2E,stroke:#C661FE,stroke-width:2px,color:#D4D4E0
classDef cloud fill:#0A0E27,stroke:#7B61FE,stroke-width:2px,color:#A88FFE
classDef ui fill:#221D2E,stroke:#C661FE,stroke-width:3px,color:#D4D4E0
classDef bridge fill:#221D2E,stroke:#7B61FE,stroke-width:3px,color:#D4D4E0
style Process fill:#0A0E27,stroke:#3A3450,stroke-width:1px,color:#6B6982,stroke-dasharray:4 4
Two concurrent loops, message-passing only. No shared state, no mutexes.
- bubbletea —
Model → Update → View cycle. Sub-models composed by embedding.
- gotd/td —
client.Run(ctx, fn) blocks; UpdateDispatcher fires typed callbacks on incoming events; events are forwarded to the TUI via tea.Program.Send().
- Bridge — single struct that owns the
tg.Client, exposes domain methods (SendMessage, LoadMessages, Logout, ...).
The design was modeled formally before any code:
| Document |
Content |
| TUI Design |
UX decisions, ASCII mockups, color spec |
| Statechart |
Hierarchical state machine for the entire UI |
| TLA+ spec |
Concurrency model with safety/liveness invariants |
| ADRs |
Architecture Decision Records |
Tech stack
| Layer |
Library |
Role |
| Telegram |
gotd/td |
MTProto, auth, dispatcher |
| TUI runtime |
bubbletea |
Elm Architecture event loop |
| TUI widgets |
bubbles |
viewport, textarea, textinput, spinner |
| TUI styling |
lipgloss |
colors, layout, borders |
| TUI forms |
huh |
auth flow forms |
| ANSI ops |
x/ansi |
width, wrap, cut (used for modal overlay compositing) |
Development
make build # build binary → ./tuilegram
make run # go run ./cmd/tuilegram
make rerun # remove session + run (forces fresh login)
make test # go test ./...
make lint # golangci-lint (auto-falls-back to go vet)
make lint-install # install golangci-lint v1.62.2
make vet # go vet ./...
make tidy # go mod tidy
make loc-check # audit 120 LOC/file rule
Codebase rules:
- 120 LOC per file — enforced by
make loc-check
- SOLID per file — single responsibility, short functions
- Design-first — formal models in
docs/design/phase-*/ before implementation
- Pipeline-driven — 34 atomic steps, each runnable, each reviewed before merge
FAQ
Does it support media uploads?
Receive yes, send no (yet). Photos/voice/files render as [type] filename (size) placeholders.
Voice waveforms? Stickers?
Stickers render as their first emoji. Waveforms not yet.
Bot accounts?
Designed for user accounts. A bot would technically work but the UX assumes user-account features (folders, drafts, reactions).
Will this get me banned?
Same RPCs as the official client. The included middlewares handle flood-wait correctly. Don't spam, don't hammer the API in custom scripts on top of it. No reports of bans during development.
Wayland / Linux / macOS / Windows?
Pure Go terminal app. Runs anywhere Go runs. Tested on macOS + Linux. Windows should work in WSL or modern terminals (Windows Terminal).
Why another Telegram TUI?
Most existing TUI Telegram clients wrap telegram-cli (deprecated) or tdlib (C++ dependency, requires CGo). tuilegram has no CGo and no external runtime — go build produces a single static binary that you can scp around.
Contributing
Contributions welcome. PRs, issues, feature requests, bug reports — all open.
Before opening a PR:
- Run
make build && make vet && make test && make loc-check — must pass clean
- Respect codebase rules: 120 LOC per file, SOLID, design-first
- Follow Conventional Commits (Italian or English):
feat(area): description
- Open a draft PR early if the change is non-trivial — easier to align on direction
For larger features, the project follows a step-pipeline with formal models (statecharts, TLA+, ADRs) before code. Discuss in an issue first if you want to add a new step.
Good first contributions:
- Media upload (photos, files, voice)
- Sticker rendering improvements
- New themes (TOML files in
internal/theme/)
- Tests (especially
internal/ui/views/)
- Docs / wiki / examples
Credits
- Charm — terminal UI ecosystem
- gotd — pure-Go MTProto
- Telegram team for the open API
License
MIT — see LICENSE.