Telegram bridge lets me drive the local Claude Code tmux session from my phone — inbound messages are typed into the 'claude' session, pane diffs are streamed back as plain Telegram messages (TUI noise, tool-call blocks, echoed user input and already-sent lines are filtered so only the assistant's actual reply reaches the chat). Deployed as food-market-telegram-bridge.service, reads creds from /etc/food-market/telegram.env (not committed). Also committing the local docker-registry unit for reproducibility — registry:2 on 127.0.0.1:5001, data persisted in /opt/food-market-data/docker-registry. Setup docs in docs/telegram-bridge.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
87 lines
4.5 KiB
Markdown
87 lines
4.5 KiB
Markdown
# Telegram ↔ tmux bridge
|
||
|
||
Управление локальной сессией Claude Code с телефона через Telegram-бота. Входящее сообщение от whitelisted `chat_id` набирается в tmux-сессию `claude` как будто вы сами печатаете; ответный вывод пайнa каждые ~2.5 с отправляется обратно в чат.
|
||
|
||
## Как это выглядит в работе
|
||
|
||
- Вы пишете боту: `запусти тесты`
|
||
- Бот делает `tmux send-keys -t claude -l "запусти тесты" && tmux send-keys -t claude Enter` — текст попадает в поле ввода Claude
|
||
- Фоновый поллер раз в 2.5 с снимает `tmux capture-pane`, сравнивает с предыдущим снапшотом, присылает новые строки как `<pre>…</pre>`-блок
|
||
|
||
Команды бота:
|
||
- `/ping` — живой ли, какая сессия и интервал
|
||
- `/snapshot` — выслать полный текущий пайн (полезно после длинного молчания или после рестарта)
|
||
|
||
## Один раз — настройка
|
||
|
||
### 1. Креды
|
||
|
||
Положите в `/etc/food-market/telegram.env`:
|
||
```
|
||
TELEGRAM_BOT_TOKEN=<токен от @BotFather>
|
||
TELEGRAM_CHAT_ID=<ваш личный chat_id, целое число>
|
||
```
|
||
|
||
Узнать `chat_id` — напишите `@userinfobot` в Telegram, он ответит с вашим id. Файл доступен только владельцу (`chmod 600`).
|
||
|
||
Только сообщения от этого **одного** chat_id будут обработаны — всё остальное молча игнорируется.
|
||
|
||
### 2. tmux-сессия `claude`
|
||
|
||
Бот ожидает существующую сессию с именем `claude`. Создайте её как обычно:
|
||
```bash
|
||
tmux new-session -d -s claude
|
||
tmux attach -t claude # и запустите внутри `claude` (или что там у вас)
|
||
```
|
||
Сервис стартует даже без сессии — в лог упадёт warning, но `send-keys` / `capture-pane` начнут работать как только сессия появится. Имя сессии можно переопределить через env `TMUX_SESSION=other` в юните.
|
||
|
||
### 3. Старт сервиса
|
||
|
||
```bash
|
||
sudo systemctl enable --now food-market-telegram-bridge.service
|
||
```
|
||
|
||
В ответ бот пришлёт `✅ bridge up …` — это индикатор успеха.
|
||
|
||
## Эксплуатация
|
||
|
||
### Логи
|
||
```bash
|
||
sudo journalctl -u food-market-telegram-bridge.service -f
|
||
sudo journalctl -u food-market-telegram-bridge.service --since '10 min ago'
|
||
```
|
||
|
||
### Перезапуск
|
||
```bash
|
||
sudo systemctl restart food-market-telegram-bridge.service
|
||
```
|
||
|
||
### Остановить
|
||
```bash
|
||
sudo systemctl stop food-market-telegram-bridge.service # до ребута
|
||
sudo systemctl disable food-market-telegram-bridge.service # и после ребута
|
||
```
|
||
|
||
### Поменять интервал/сессию
|
||
Отредактируйте `/etc/systemd/system/food-market-telegram-bridge.service`, добавьте в секцию `[Service]`:
|
||
```
|
||
Environment=POLL_INTERVAL_SEC=1.5
|
||
Environment=TMUX_SESSION=other-session
|
||
Environment=CAPTURE_HISTORY_LINES=400
|
||
```
|
||
Затем `sudo systemctl daemon-reload && sudo systemctl restart food-market-telegram-bridge`.
|
||
|
||
## Раскладка
|
||
|
||
- Скрипт: `/opt/food-market-data/telegram-bridge/bridge.py`
|
||
- venv (Python 3.12, `python-telegram-bot 21.x`): `/opt/food-market-data/telegram-bridge/venv/`
|
||
- Креды: `/etc/food-market/telegram.env` (owner `nns`, mode `0600`)
|
||
- systemd unit: `/etc/systemd/system/food-market-telegram-bridge.service`
|
||
|
||
## Что хорошо знать
|
||
|
||
- `disable_notification=True` стоит на фоновых сообщениях пайна — не будет жужжать при каждом diff'e.
|
||
- Telegram-лимит 4096 символов; длинные пайн-блоки режутся на куски по ~3800 символов.
|
||
- Если после долгого молчания в чате слишком много истории, шлите `/snapshot` — бот обнуляет baseline и присылает текущий экран целиком.
|
||
- Бот заходит в Telegram long-polling (исходящее к api.telegram.org, без входящих портов) — никакого проброса портов не нужно.
|