A chat client for the Beeper messaging platform that runs in both the terminal (TUI via OpenTUI) and the browser (React DOM). Same codebase, same React components, two renderers.
Features β’ Installation β’ Usage β’ Architecture β’ Development β’ Keybindings
Note: lazybeeper is still in very early development. Features and UI are subject to change.
- π₯οΈ Dual Renderer: Same React components render in terminal (OpenTUI) and browser (react-dom)
- π¨ 10 Built-in Themes: Catppuccin, Tokyo Night, Dracula, Nord, Gruvbox, One Dark, and more
- β¨οΈ Vim-style Navigation:
j/k,g/G,h/lfor fast keyboard-driven workflow - π Live Polling: Automatic chat/message updates with idle backoff
- π¦ Mock Mode: Full UI exploration without an API token
- βοΈ Persistent Config: TOML config file (terminal) or
localStorage(browser) - π§© MVVM Architecture: Clean separation with
useReducer, discriminated unions, and typed actions
git clone https://github.com/rdrkr/lazybeeper.git
cd lazybeeper
bun install# Mock mode (no token required)
bunx tsx src/index.tsx --token ""
# With API token (live mode)
bunx tsx src/index.tsx --token <your-beeper-token>
# Custom API URL
bunx tsx src/index.tsx --token <token> --url http://localhost:23373
# Select a theme
bunx tsx src/index.tsx --token "" --theme dracula# Build and serve the web version
mise run serve:web
# Or build only (output in dist-web/)
mise run build:webThen open http://localhost:3000. The web version uses
mock data by default and persists settings to localStorage.
| Variable | Description | Default |
|---|---|---|
BEEPER_TOKEN |
Beeper API authentication token | (none, enables mock mode) |
BEEPER_URL |
Beeper Desktop API base URL | http://localhost:23373 |
LAZYBEEPER_THEME |
Color theme name | catppuccin-mocha |
| Flag | Description |
|---|---|
--token |
API token (overrides BEEPER_TOKEN) |
--url |
Base URL (overrides BEEPER_URL) |
--theme |
Theme name (overrides LAZYBEEPER_THEME) |
Built-in themes: catppuccin-mocha (default), catppuccin-macchiato, catppuccin-frappe,
catppuccin-latte, tokyo-night, tokyo-night-storm, dracula, nord, gruvbox-dark, one-dark.
Press t at runtime to open the theme selector. Changes are persisted to the config file
(terminal) or localStorage (browser).
Terminal: lazybeeper stores persistent settings in a TOML file at
$XDG_CONFIG_HOME/lazybeeper/config.toml (defaults to ~/.config/lazybeeper/config.toml).
The file is created automatically on first run.
theme = "catppuccin-mocha"Press r to reload the config file at runtime.
Browser: Settings are stored in localStorage under the key lazybeeper-config.
lazybeeper/
βββ src/
β βββ index.tsx # Terminal entry point (OpenTUI renderer)
β βββ web/ # Browser-specific modules
β β βββ entry.tsx # Web entry point (react-dom)
β β βββ build.ts # β scripts/build-web.ts (Bun build with shim plugins)
β β βββ jsx-runtime.ts # Custom JSX runtime: TUI elements β HTML
β β βββ opentui-core.ts # TextAttributes web shim
β β βββ opentui-react.ts # useKeyboard/useTerminalDimensions web shims
β β βββ config-file.ts # localStorage-backed config persistence
β β βββ kitty-stub.ts # Browser avatar rendering via DOM overlays
β βββ domain/ # Pure types, config, text utilities
β β βββ types.ts # Account, Chat, Message interfaces
β β βββ repository.ts # Repository interface
β β βββ config.ts # Configuration (env vars + CLI flags)
β β βββ config-file.ts # TOML config file (XDG_CONFIG_HOME)
β β βββ textutil.ts # String utilities
β βββ data/ # API client and mock data
β β βββ client.ts # Beeper Desktop API client
β β βββ mock/ # Mock data for development
β β βββ poller.ts # Polling with idle backoff
β βββ ui/ # Shared React UI layer
β βββ app.tsx # Root component (keyboard routing)
β βββ layout.ts # Responsive layout calculation
β βββ terminal.ts # Synchronized output (anti-flicker)
β βββ theme/ # Theme system (10 built-in themes)
β βββ viewmodel/ # State, actions, keybindings, reducer
β βββ panel/ # Panel components
β βββ popup/ # Popup dialogs
βββ public/
β βββ index.html # HTML shell for web build
βββ scripts/
β βββ build-web.ts # Bun build script with module aliasing plugins
βββ tests/ # Unit tests (mirrors src structure)
βββ e2e/ # Playwright end-to-end tests
βββ playwright.config.ts # Playwright configuration
βββ package.json
βββ tsconfig.json # Terminal build config
βββ tsconfig.web.json # Web build config
βββ eslint.config.mjs
βββ vitest.config.ts
- Dual renderer: Same React components render via OpenTUI (terminal) or react-dom (browser)
using a custom JSX runtime that maps
<box>,<text>,<span>to HTML<div>/<span> - Build-time aliasing: Bun build plugin swaps
@opentui/*imports for web shims - MVVM via useReducer: UI delegates to reducer for state logic
- Repository interface: Decouples data access from UI
- Discriminated unions: Type-safe action dispatch
- React.memo: Prevents unnecessary re-renders for flicker-free tmux support
- Synchronized output: DEC 2026 markers for atomic terminal frame updates
- Theme system: 10 built-in themes with React context
Install mise for task running:
brew install mise# Run the terminal app (mock mode by default)
mise run run
# Build and serve the web version
mise run serve:web
# Build only the web version (output: dist-web/)
mise run build:web
# Format code
mise run fmt
# Run linter
mise run lint
# Run tests
mise run test
# Build the terminal version
mise run build
# Full check (format + lint + test + build)
mise run check
# Run Playwright end-to-end tests (headless)
mise run e2e
# Run e2e tests with visible browser
mise run e2e:headed
# Clean build artifacts (terminal + web)
mise run clean
# Check for outdated packages
mise run outdated
# Upgrade all dependencies
mise run upgrade| Key | Action |
|---|---|
q / Ctrl+C |
Quit |
Tab / Shift+Tab |
Cycle panels |
1-4 |
Jump to panel |
h / l |
Left / right panel |
/ |
Search chats |
? |
Help popup |
t |
Theme selector |
r |
Reload config |
| Key | Action |
|---|---|
j / k |
Next / previous |
g / G |
Top / bottom |
Enter |
Select |
| Key | Action |
|---|---|
a |
Archive / unarchive |
m |
Mute / unmute |
p |
Pin / unpin |
| Key | Action |
|---|---|
j / k |
Scroll down / up |
g / G |
Top / bottom |
Enter |
Focus input |
| Key | Action |
|---|---|
Enter |
Send message |
Esc |
Exit to messages |
- Fork the repository
- Create a feature branch
- Run
mise run checkbefore submitting - Open a pull request
Made with β€οΈ by Ronen Druker

