Pilot Claude Code from your phone via Telegram.
MetroClaude bridges Telegram and Claude Code through tmux. Send prompts, approve permissions, resume sessions — all from your phone. Your Mac does the work.
1 Telegram topic = 1 tmux window = 1 Claude Code session
Phone (Telegram) Mac (MetroClaude) tmux
+-----------+ long +-------------+ send-keys +--------+
| Topic A | ---------> | bot.py | ------------> | claude |
| | <--------- | monitor.py | <------------ | (JSONL)|
+-----------+ reply +-------------+ byte-offset +--------+
You can switch between tmux attach and Telegram without losing context.
- Interactive UI — permission prompts, AskUserQuestion, plan mode as inline keyboards
- Tool pairing — tool_use messages update with checkmark/cross when result arrives
- Smart queue — auto-merge consecutive messages, split at 4096 chars, rate-limit retries
- Markdown —
telegramify-markdownwith expandable blockquotes and plain text fallback - Security — user whitelist, input sanitization, tmux injection prevention, rate limiting
- Session management — persist across restarts, resume recent sessions, stale cleanup
- Typing indicator — shows "typing..." while Claude works, stops on response
- Crash detection — detects Claude exit, offers restart button with
--resume - Forward commands —
/clear,/compact,/costforwarded to Claude automatically
- macOS or Linux
- Python 3.11+
- tmux (
brew install tmux) - Claude Code CLI (
claudein your PATH) - A Telegram bot token (@BotFather)
- A Telegram group with Topics enabled
git clone https://github.com/EncrEor/metroclaude.git
cd metroclaude
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[markdown,dev]"cp .env.example .envEdit .env:
TELEGRAM_BOT_TOKEN=your-token-from-botfather
ALLOWED_USERS=your-telegram-user-idGet your user ID from @userinfobot on Telegram.
python -m metroclaudeOr if installed:
metroclaude| Command | Description |
|---|---|
/start |
Welcome message |
/new [path] |
Start new Claude session in this topic |
/stop |
Stop session and kill tmux window |
/status |
List all active sessions |
/resume |
Resume a recent session (inline keyboard) |
/screenshot |
Capture current terminal content |
Any unrecognized /command (like /clear, /compact, /cost) is forwarded directly to Claude.
- You type in a Telegram topic
- MetroClaude sanitizes the input and sends it to the matching tmux window via
send-keys - Claude Code processes the prompt and writes to
~/.claude/projects/.../session.jsonl - The JSONL monitor detects new bytes (byte-offset tracking, 2s polling)
- The parser extracts text, tool_use, and tool_result events
- The message queue formats, merges, and sends to Telegram with markdown
When Claude asks for permission or a question:
- The status poller captures the terminal every 2s
- Regex detects permission prompts, AskUserQuestion, plan mode
- An inline keyboard is sent to Telegram
- Your button press sends the keystrokes back to tmux
/newcreates a tmux window, startsclaude, waits for the SessionStart hook to register the session ID- The hook script writes to
~/.metroclaude/session_map.json(with file locking) - The monitor starts polling the JSONL file
/stopkills the window and cleans up- Stale sessions (dead tmux windows) are automatically cleaned every 30s
| Variable | Required | Default | Description |
|---|---|---|---|
TELEGRAM_BOT_TOKEN |
Yes | -- | Bot token from @BotFather |
ALLOWED_USERS |
Yes | -- | Comma-separated Telegram user IDs |
TMUX_SESSION_NAME |
No | metroclaude |
tmux session name |
CLAUDE_COMMAND |
No | claude |
Claude CLI command |
MONITOR_POLL_INTERVAL |
No | 2.0 |
JSONL polling interval (seconds) |
LOG_LEVEL |
No | INFO |
DEBUG, INFO, WARNING, ERROR |
WORKING_DIR |
No | ~/Documents/Joy_Claude |
Default project directory |
metroclaude/
__main__.py # CLI entry point
config.py # Pydantic Settings (.env)
bot.py # Telegram polling, routing, event dispatch
tmux.py # Async libtmux wrapper
monitor.py # JSONL byte-offset polling
parser.py # JSONL event parser
session.py # Session state + JSON persistence
hooks.py # Claude Code SessionStart hook registration
hooks_session_start.py # Hook script (runs in tmux pane)
exceptions.py # Typed exception hierarchy
handlers/
commands.py # /new, /stop, /status, /resume, /screenshot
message.py # Text messages + forward commands
interactive.py # Inline keyboards for permissions/questions
callback_data.py # Callback encoding/decoding
status.py # Typing manager + terminal detection
security/
auth.py # User whitelist
input_sanitizer.py # Tmux injection prevention
rate_limiter.py # Per-user + per-window rate limiting
audit.py # Structured audit logging (9 event types, 4 risk levels)
utils/
queue.py # Message queue with tool pairing
markdown.py # Markdown -> Telegram formatting
tests/
test_parser.py # JSONL parser (10 tests)
test_queue.py # Message queue (11 tests)
test_interactive.py # Interactive UI (16 tests)
test_security.py # Auth + sanitization + audit (30 tests)
test_tmux.py # Tmux manager (18 tests)
test_rate_limiter.py # Rate limiting (11 tests)
test_session.py # Session manager (8 tests)
MetroClaude runs locally on your Mac. No cloud servers, no API proxying.
- User whitelist — only
ALLOWED_USERScan interact with the bot - Input sanitization — control characters, backtick injection,
$(...)patterns stripped before tmux - Path validation —
/newonly accepts directories under$HOME - Rate limiting — 20 messages/minute per user, 1s minimum between tmux sends
- Generic errors — tracebacks logged, not exposed to Telegram
- File locking —
fcntl.flock()on session_map.json for concurrent access
MetroClaude is a "best of" from 7 open-source projects:
| Project | What we took |
|---|---|
| ccbot | tmux bridge, JSONL monitor, message queue, interactive UI |
| claude-code-telegram | 5-layer security, session management |
| claudecode-telegram | Typing indicator, blocked commands, markdown |
| claude-telegram-bot | Session resume, crash recovery |
| bot-on-anything | Channel abstraction concept |
| vibeIDE | Agent SDK patterns |
| Claude-Code-Remote | Hook-based architecture |
# Install with dev dependencies
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[markdown,dev]"
# Run tests (104 tests)
pytest tests/ -v
# Run with debug logging
LOG_LEVEL=DEBUG python -m metroclaudeMIT