Target: a Linux arm64 (or x86_64) host that runs always-on as the user's
single source of truth. macOS works as an interactive client via ssh -t <host> tmux attach -t ccbot.
- Python 3.11+ (3.14 confirmed working)
uvfor dependency managementtmux(≥3.0)claudeCLI authenticated againstclaude.ai(Max x20 subscription) —claude auth statusmust succeed for the user that owns the botffmpegifVOICE_BACKEND=whisperwhisper-cliplusggml-medium-q8_0.binifVOICE_BACKEND=whisper
sudo install -d -m 755 /opt/ccbot
sudo chown $USER:$USER /opt/ccbot
git clone https://github.com/Time4Mind/ccbot.git /opt/ccbot
cd /opt/ccbot
uv sync --all-extras
# Provision the env file.
sudo install -d -m 750 /etc/ccbot
sudo install -m 640 .env.example /etc/ccbot/ccbot.env
sudo chown $USER:$USER /etc/ccbot/ccbot.env
$EDITOR /etc/ccbot/ccbot.env # set TELEGRAM_BOT_TOKEN, ALLOWED_USERS, etc.
# Install the systemd template — replace USER with the bot's owner login.
sudo install -m 644 scripts/ccbot.service /etc/systemd/system/ccbot@.service
sudo systemctl daemon-reload
sudo systemctl enable --now ccbot@$USER.servicesystemctl status ccbot@$USER.service
journalctl -u ccbot@$USER.service -n 100 --no-pagerOpen a DM with the bot in Telegram. Send any text. The bot should:
- Announce that no active session exists and present the directory browser.
- After you pick a directory, create a tmux window, register a Session, activate it, and forward your text to claude.
- Subsequent assistant turns appear in chat with the inline session switcher under the latest content message.
On systemctl restart ccbot@…:
resolve_stale_ids()re-binds persisted window IDs against live tmux windows (the tmux server itself survives the bot restart because we pinTMUX_TMPDIRto/run/ccbot).reconcile_sessions_with_tmux()flips any Session whose window vanished into theloststate and clears the user'sactive_sessionspointer if it pointed at a lost record. Lost sessions are surfaced via/archive --allwith a Restore button that runsclaude --resume <session-id>in the original workdir.- Idle and archive sweeps resume from
last_event_at/archived_attimestamps instate.json.
api.telegram.org is unreachable from many residential and hosting
networks (notably RU IP ranges). The bot supports an outbound HTTP or
SOCKS proxy via TG_PROXY_URL — long-poll and Bot API requests both
use it.
Set TG_PROXY_URL to any reachable HTTP/SOCKS5 proxy. Examples:
TG_PROXY_URL=http://127.0.0.1:1081 # local HTTP proxy / SSH tunnel
TG_PROXY_URL=socks5://127.0.0.1:1080 # local SOCKS proxy
TG_PROXY_URL=http://user:pass@host:port # remote authenticated HTTP proxy
Common patterns:
- Run a SOCKS5 proxy on a VPS in an unblocked region (sing-box, 3proxy, dante) and SSH-forward its port to the host running ccbot.
- Use any commercial HTTP proxy that supports CONNECT.
- Tunnel through your own VPN if you already have one.
Verify before starting the bot:
curl -s --max-time 8 -x "$TG_PROXY_URL" \
"https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/getMe"
# -> {"ok":true,...} means TG is reachable through the proxyVOICE_BACKEND=auto(default) → Apple Speech on Darwin, whisper.cpp elsewhere. Apple currently delegates to whisper.cpp; adjust once a pure-Python AVSpeechRecognizer wrapper proves stable.VOICE_BACKEND=whisper→ requiresWHISPER_BIN(defaultwhisper-cli) andWHISPER_MODEL_PATH(default$CCBOT_DIR/models/ggml-medium-q8_0.bin, ~785MB).WHISPER_THREADS(default 6),WHISPER_LANG_DEFAULT(defaultru) andWHISPER_LANG_MODEL_PATHtune the language-detect pre-pass.VOICE_BACKEND=off→ reject voice messages.
~/.ccbot/state.json— sessions, active pointers, switcher trace.~/.ccbot/session_map.json— written by claude'sSessionStarthook.~/.ccbot/monitor_state.json— JSONL byte offsets.~/.ccbot/codex_quota_day.json— Codex daily quota baseline and allocation.~/.codex/— Codex credentials, hooks, and rollout JSONL when Codex is used.~/.ccbot/models/— whisper model (only if VOICE_BACKEND=whisper).<workdir>/.ccbot-inbox/— uploaded photos/documents per session; pruned every hour pastINBOX_TTL_HOURS(default 24h).- Archived Session records expire after
ARCHIVE_PURGE_AFTER(default 14d). Transcripts on disk are kept for audit.
Когда хост — это chroot, контейнер без init, или вообще что-то, где
systemctl недоступен, у systemd-юнита нет шансов. Та же роль
закрывается через scripts/ccbot-supervisor.sh — это foreground-цикл,
который:
- ждёт reachable Telegram API (curl до
api.telegram.org, либоCCBOT_NET_PROBE_URL), отстреливая VPN-flap'ы поCCBOT_NET_RETRY_SECсекунд между попытками; - запускает
uv run ccbot; - на любом выходе (TimedOut, KeyboardInterrupt, crash) — спит
CCBOT_RESTART_BACKOFFсекунд и возвращается к шагу 1.
Скрипт — обычный bash, не привязан к OS. Подойдёт для любого хоста без systemd: chroot, докер-без-tini, ручной запуск из tmux на macOS, и т.п. Запуск:
Просто в tmux-пейне. Самый прямой путь — стартануть супервайзер как foreground-команду в фиксированном tmux-окне:
tmux new-session -d -s ccbot -n __main__ -c "$HOME"
tmux send-keys -t ccbot:__main__ '/path/to/ccbot/scripts/ccbot-supervisor.sh' EnterЧерез сервис-менеджер. Если на хосте есть runit / s6 / supervisord,
просто положи в их service-директорию run-скрипт со строкой
exec /path/to/ccbot/scripts/ccbot-supervisor.sh 2>&1. Сервис-менеджер
сам поднимет супервайзер после рестарта.
Авто-вход на shell-логин. Резервный путь — повесить
/etc/profile.d/ccbot-autostart.sh (или
/etc/profile.d/zz-ccbot.sh), который идемпотентно поднимает супервайзер
если он ещё не крутится:
#!/bin/sh
if ! pgrep -f ccbot-supervisor.sh > /dev/null 2>&1; then
tmux has-session -t ccbot 2>/dev/null \
|| tmux new-session -d -s ccbot -n __main__ -c "$HOME"
tmux send-keys -t ccbot:__main__ \
"$HOME/ccbot/scripts/ccbot-supervisor.sh" Enter
fiТогда любой shell-вход в систему (включая SSH) идемпотентно поднимет бот, если он по какой-то причине упал.
If you'd rather run the bot on your Mac (e.g. for personal use) instead of a Linux VPS, use the included LaunchAgent template:
# 1. Edit the template — launchd doesn't expand ${HOME}.
cp scripts/com.ccbot.plist ~/Library/LaunchAgents/com.ccbot.plist
sed -i '' "s|\${HOME}|$HOME|g" ~/Library/LaunchAgents/com.ccbot.plist
# 2. Load + start. KeepAlive will restart the bot on crash.
launchctl load -w ~/Library/LaunchAgents/com.ccbot.plist
# 3. Tail the log.
tail -f ~/.ccbot/logs/bot.log
# Stop / unload:
launchctl unload ~/Library/LaunchAgents/com.ccbot.plistWhisper.cpp model installer:
# Default downloads ggml-medium-q8_0.bin (~785MB) + ggml-tiny.bin (~75MB)
# into ~/.ccbot/models/.
./scripts/install_whisper_model.sh
# Or pick a smaller model:
MODEL=small ./scripts/install_whisper_model.shAfter the model is in place, set VOICE_BACKEND=whisper in .env.
The bot does not run a second instance on the Mac. Instead:
ssh -t <linux-host> 'TMUX_TMPDIR=/run/ccbot tmux attach -t ccbot'The Telegram side and the live tmux session share state, so anything you type at the terminal is also seen by claude in the same session.