A secure, terminal-based user interface for the Cryptic end-to-end encrypted messaging system.
Terminal interface for Cryptic's end-to-end encrypted messaging system (X3DH + Double Ratchet).
- Four-tab layout: Chat, Admin, Status, Help
- Keyboard-driven: Tab navigation, Emacs-style text editing (Ctrl+A/E/B/F)
- Message scrolling: Infinite scroll with lazy loading, PageUp/PageDown navigation
- Date separators: Automatic "Today", "Yesterday", relative dates in conversation view
- Global status bar: System notifications (3-second minimum display) and server connection state (🟢/🔴)
- Real-time chat: Send/receive encrypted messages with automatic decryption
- Persistent history: Browse full conversation history loaded on demand
- Text formatting: Emoji replacement (
:-)→ 😊), markdown (bold, italic,`code`) - Online indicators: Live user presence (green dot), auto-refreshed every 10 seconds
- User management: Register users with GPG fingerprints, revoke access
- Certificate viewing: Browse X.509 certificates for registered users
- Scrollable forms: Multi-field input with Tab navigation
- Distributed Erlang: Native connection to Erlang nodes via distributed protocol
- Event-driven: Real-time event subscription from Erlang event bus
- Async I/O: Built on smol runtime for efficient concurrency
- Secure memory: Automatic zeroization of passphrases and sensitive data
- Structured logging: Per-user/server log directories with daily rotation
Cryptic TUI is a Rust client that connects to an Erlang-based Cryptic client node via the distributed Erlang protocol. It uses RPC calls for commands and receives real-time events through a bridge process that forwards events from the Cryptic event bus.
┌─────────────────────┐ ┌──────────────────────┐
│ Cryptic TUI │ Distributed │ Cryptic Client │
│ (Rust/smol) │ Erlang Protocol │ (Erlang/OTP) │
│ │◄──────────────────►│ │
│ - UI Rendering │ │ - cryptic_engine │
│ - Event Handling │ RPC Calls │ - cryptic_ws_client │
│ - dist_node │ send_message() │ - cryptic_event_bus │───┐
│ (receives msgs) │ online_users() │ - cryptic_tui_bridge│ │
└─────────────────────┘ └──────────────────────┘ │
▲ │ │
│ │ Events │
│ ▼ │
│ ┌─────────────────┐ │
└──────────────────────────────────│ Event Forward │ │
{tui_event, JSON} │ to Rust Node │ │
└─────────────────┘ │
│
Cryptic Protocol │
WebSocket/mTLS │
▼
┌──────────────────┐
│ Cryptic Server │
│ (Erlang/OTP) │
└──────────────────┘
Message Flow:
- Outbound: Rust → RPC call → cryptic_engine → encryption → WebSocket → Server
- Inbound: Server → WebSocket → cryptic_engine → decrypt → event_bus → bridge → Rust
- Rust 1.70+ (2021 edition)
- An Erlang node running the Cryptic client
git clone https://github.com/etnt/cryptic-tui.git
cd cryptic-tui
cargo build --release# Connect with explicit cookie
cargo run -- --node admin@localhost --cookie mycookie
# Use cookie from ~/.erlang.cookie
cargo run -- --node admin@localhost
# Run in mock mode (no Erlang connection)
cargo runCtrl+Q- Quit applicationTab- Next tab (Chat → Admin → Status → Help)Shift+Tab- Previous tabCtrl+H- Previous tabCtrl+L- Next tab
↑/↓- Select userEnter- Open chat with selected user / Send messageCtrl+U/PageUp- Scroll up to load older messages (on MacBook:Fn+↑)Ctrl+D/PageDown- Scroll down (newer messages) (on MacBook:Fn+↓)Home- Jump to beginning of conversationEnd- Jump to latest messagesEsc- Clear input
↑/↓- Navigate menu or user listEnter- Select menu item or view user detailsCtrl+U/PageUp- Scroll up in content area (on MacBook:Fn+↑)Ctrl+D/PageDown- Scroll down in content area (on MacBook:Fn+↓)Esc- Return to menu / Cancel formTab- Switch between form fields (when in forms)r- Register new userv- Revoke user access
Ctrl+U/PageUp- Scroll up (on MacBook:Fn+↑)Ctrl+D/PageDown- Scroll down (on MacBook:Fn+↓)
Ctrl+U/PageUp- Scroll up (on MacBook:Fn+↑)Ctrl+D/PageDown- Scroll down (on MacBook:Fn+↓)
Ctrl+A- Move to beginning of lineCtrl+E- Move to end of lineCtrl+B/←- Move cursor leftCtrl+F/→- Move cursor rightBackspace- Delete character before cursorDelete- Delete character at cursor- Type to compose message (supports uppercase with Shift)
The application reads the Erlang cookie from:
--cookiecommand-line argument (highest priority)~/.erlang.cookiefile
Logs are written to daily rolling files organized by user and server connection:
~/.cryptic/<username>/<server>_<port>/logs/cryptic-tui.log.YYYY-MM-DD
The log location is determined by environment variables set by the bin/cryptic startup script:
CRYPTIC_USERNAME- Username for the connectionCRYPTIC_SERVER_HOST- Server hostnameCRYPTIC_SERVER_PORT- Server port
View logs in real-time:
tail -f ~/.cryptic/$CRYPTIC_USERNAME/${CRYPTIC_SERVER_HOST}_${CRYPTIC_SERVER_PORT}/logs/cryptic-tui.log.$(date +%Y-%m-%d)Example log paths:
~/.cryptic/alice/localhost_8443/logs/cryptic-tui.log.2025-11-22~/.cryptic/bob/server.example.com_9443/logs/cryptic-tui.log.2025-11-22
The global status bar at the bottom of the screen displays:
-
System Messages - Important notifications from the server (shown with ℹ icon on cyan background)
- System messages display for a minimum of 3 seconds
- Multiple messages are queued and shown sequentially
- Messages can include system codes like
server_connection_uporserver_connection_down
-
Server Connection State - When no system messages are active:
🟢 Server up!- Connection to Cryptic server is active🔴 Server down!- Connection to Cryptic server is lost
The server connection state is automatically detected from incoming messages (online_users, deliver_message, etc.) or explicitly set via system_message events with sys_code field.
cryptic-tui/
├── src/
│ ├── main.rs # Entry point, event loop, terminal lifecycle
│ ├── app.rs # Application state and data structures
│ ├── cli.rs # Command-line argument parsing
│ ├── erlang.rs # Erlang RPC connection and message history
│ ├── dist_node.rs # Distributed Erlang message receiver
│ ├── formatting.rs # Emoji and markdown text formatting
│ └── ui.rs # Terminal UI rendering with Ratatui
├── cryptic/ # Erlang cryptic submodule
│ └── src/
│ └── cryptic_tui_bridge.erl # Event bus bridge for TUI
├── Cargo.toml # Rust dependencies and build config
└── docs/ # Documentation and implementation guides
├── MESSAGE_HISTORY_DESIGN.md # Message history implementation
└── AGENTS.md # Architecture for AI assistants
Online Documentation: https://etnt.github.io/cryptic-tui/
Documentation is automatically built and deployed to GitHub Pages on every push to the main branch via GitHub Actions.
Local Documentation:
# Generate and open docs in browser
cargo doc --no-deps --open
# Just generate docs
cargo doc --no-depsLocal documentation will be available at target/doc/cryptic_tui/index.html
See .github/workflows/README.md for details on the CI/CD pipeline.
cargo testThe project follows a phased development approach:
- Phase 0 ✅ - UI skeleton with mock data
- Phase 1 ✅ - Erlang RPC connectivity
- Phase 2 ✅ - Event bus integration and distributed Erlang messaging
- Phase 3 ✅ - Message sending and receiving (encryption handled by Erlang)
- Phase 4 ✅ - Message history with infinite scrolling and date navigation
- Phase 5 🚧 - Additional features (search, date picker, media, etc.)
See docs/MESSAGE_HISTORY_DESIGN.md and IMPLEMENTATION-PLAN.md for details.
- ratatui - Terminal UI framework
- crossterm - Cross-platform terminal control
- smol - Lightweight async runtime
- chrono - Date and time formatting
- erl_dist - Distributed Erlang protocol
- erl_rpc - RPC client for Erlang
- eetf - Erlang External Term Format
- clap - CLI argument parsing
- tracing - Structured logging
- anyhow - Error handling
- serde_json - JSON serialization
- async-channel - Async message passing
- zeroize - Secure memory clearing
- Passphrases are stored in memory with automatic zeroization
- All sensitive data structures implement
DropwithZeroize - Logs do not contain plaintext message content
- Cookie authentication for Erlang node connections
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure
cargo testandcargo clippypass - Submit a pull request
See LICENSE file.