food-market/docs/telegram-bridge.md
nurdotnet d455087bc8 feat(ops): Telegram <-> tmux bridge + local docker-registry unit
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>
2026-04-23 10:53:45 +05:00

4.5 KiB
Raw Blame History

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. Создайте её как обычно:

tmux new-session -d -s claude
tmux attach -t claude   # и запустите внутри `claude` (или что там у вас)

Сервис стартует даже без сессии — в лог упадёт warning, но send-keys / capture-pane начнут работать как только сессия появится. Имя сессии можно переопределить через env TMUX_SESSION=other в юните.

3. Старт сервиса

sudo systemctl enable --now food-market-telegram-bridge.service

В ответ бот пришлёт ✅ bridge up … — это индикатор успеха.

Эксплуатация

Логи

sudo journalctl -u food-market-telegram-bridge.service -f
sudo journalctl -u food-market-telegram-bridge.service --since '10 min ago'

Перезапуск

sudo systemctl restart food-market-telegram-bridge.service

Остановить

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, без входящих портов) — никакого проброса портов не нужно.