Skip to content

Repository files navigation

claude-telegram-interface

Talk to a Claude Code CLI from Telegram, with persistent per-chat memory.

A small, zero-dependency bridge: it long-polls Telegram, accepts messages only from user ids you allowlist, and for each one spawns claude -p --resume in a directory you choose — so your phone reaches a Claude that carries its own memory across turns and thinks with that directory's full context.

It is generic by construction: it knows nothing about any one project. Point it at a directory, lock it to your Telegram id, and go.

Built for the Claude Code CLI (and, gladly, for Anthropic) — that's the default and the intended target. But the CLI it spawns is just a configurable claude_binary: point it at any other agent CLI that speaks a compatible headless interface (a one-shot prompt, a JSON result, a resumable session id) and it drives that instead. Claude is what it's tuned for; it isn't locked to Claude.

You (Telegram)  ──►  long-poll getUpdates  ──►  allowlist gate  ──►  claude -p --resume
      ▲                                                                      │
      └──────────────  code-aware MarkdownV2 reply (paced, typing UX)  ◄─────┘

What you're wiring up (read this first)

This bridges your phone to a code-executing agent on your own machine, running as you. A Telegram message becomes a claude spawn in your working_dir — with unattended tool execution (bash, git, file writes) if you enable it. The trust model, in three lines:

  • Self-hosted, bring-your-own-bot. You run this on your own machine, with your own bot and your own Claude login. One install serves one person; there is deliberately no shared or multi-user mode — "multiple users" means each person runs their own copy.
  • The allowlist is the entire door. allowed_user_ids is the only authentication. An empty list refuses to start (fail-closed). Anyone on the list can make the agent do everything the agent can do — list only yourself, or people you trust at exactly that level.
  • The bot token is a password. It lives only in the gitignored config.toml. If it ever leaks, rotate it via @BotFather.

Full detail, including the write-jail and the bypass interlock, in SECURITY.md.

Features

  • Persistent memory — each chat maps to a stable Claude session id; replies use --resume, so context survives across messages and restarts (SQLite).
  • Allowlist is the only door — no inbound ports, no roles. Unlisted ids are silently dropped. See SECURITY.md.
  • Code-aware replies — Claude's fenced code blocks render as Telegram monospace; prose is MarkdownV2-escaped. Long replies are split on safe boundaries, paced at 1 msg/sec, with a live "typing…" indicator.
  • Optional write-jail — confine the spawn's file writes to your project: a Seatbelt profile on macOS, or the systemd unit itself on Linux — the installer generates either. Required (interlocked) if you enable unattended tool execution.
  • Zero dependencies — Python standard library only (urllib, sqlite3, tomllib). No pip install.

Requirements

  • Python ≥ 3.11 (needs tomllib).
  • The Claude Code CLI, installed and logged in (run claude once interactively first). The bot uses your existing CLI authentication — no API key is passed.
  • A Telegram account.
  • The core runs anywhere the Claude CLI runs. The optional write-jail ships for macOS (Seatbelt profile) and Linux/systemd (the unit in deploy/).

Quick start (~10 minutes)

1. Create a bot and get its token. In Telegram, message @BotFather/newbot → follow prompts. It gives you a token shaped like 123456789:AA....

2. Get your numeric user id. Message @userinfobot; it replies with your id (a number like 123456789).

3. Clone and configure.

git clone <your-fork-url> claude-telegram-interface
cd claude-telegram-interface

cp config.example.toml config.toml      # config.toml is gitignored

Edit config.toml:

bot_token        = "123456789:AA...your-token..."
allowed_user_ids = [123456789]           # YOUR id from step 2
working_dir      = "/absolute/path/to/the/project/claude/should/work/in"

4. Install the secret gate (recommended). Stops you ever committing the token:

cp scripts/pre-commit-secret-gate.sh .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

5. Run it.

python3 bot.py

You should see a boot line like bot @your_bot up | allowlist=[...] | jail=off | bypass=off. Now message your bot from Telegram. The first reply starts a fresh session; follow-ups resume it — ask it to recall something you said earlier to prove it.

Optional: copy examples/run.sh to the repo root for a launcher that pins cwd and lets you point at a specific Python (PYTHON=/path/to/python3 ./run.sh).

Configuration

All settings live in config.toml. Required: bot_token, allowed_user_ids, working_dir. Everything else has a default — see config.example.toml for the full annotated list.

Key Default Purpose
bot_token From @BotFather. <id>:<secret>.
allowed_user_ids Numeric Telegram ids allowed to use the bot. The only gate.
working_dir Directory the Claude CLI runs in (its context loads from here).
claude_binary claude Path/name of the Claude Code CLI. Use an absolute path under a service.
model CLI default e.g. claude-opus-4-8.
sandbox_profile (none) Path to a macOS Seatbelt profile (write-jail).
linux_confined false Attests the bot runs under the confining systemd unit from deploy/ (the Linux write-jail).
bypass_permissions false Run tools without prompts. Requires a jail: sandbox_profile (macOS) or linux_confined (Linux).
poll_timeout 40 Long-poll seconds for getUpdates.
spawn_timeout 600 Max seconds for one spawn.
drain_hours 8 Idle this long → next message starts a fresh session.
db_path / log_path .claude-telegram-interface/… State DB and log file.

Running unattended (tools without prompts)

By default the headless spawn runs in Claude's normal permission mode, so actions that would need interactive approval (writes, bash) are denied — the bot is effectively read-and-converse. To let it act (run bash/git/etc.) without a prompt, set bypass_permissions = true.

This is interlocked for safety: the bot refuses to start with bypass_permissions = true unless a write-jail is in place.

  • macOS: run ./install.sh and answer Y to tool execution — it generates the Seatbelt profile and points sandbox_profile at it. (Manual path: copy examples/example.sb, replace the placeholder paths.) The jail confines writes to your project (and ~/.claude, which --resume needs) while leaving network/Keychain open.
  • Linux: the systemd unit in deploy/ is the jail (unprivileged User= + ProtectSystem=strict + scoped ReadWritePaths). Set linux_confined = true to attest it's in place; the bot additionally refuses to run bypass as root.

Read SECURITY.md before enabling this.

Keeping it running

A long-poll bot needs its host awake and on the network. The guided installer detects your OS and sets up its native service:

# macOS — no sudo; installs a per-user LaunchAgent:
./install.sh

# Linux — root places the systemd unit; everything else stays in your account:
sudo ./install.sh

install.sh is one readable bash file — inspect it before running it (on Linux, root is used only to place the systemd unit; everything else stays in your account). Both paths verify the Claude CLI actually runs under the service's exact environment (catching the "works in my shell, dies under the service manager" class at install time), ask for your bot token (verified live against Telegram) and user id, write the config, and install.

The installer copies the whole app into ~/.claude-telegram-interface/ and the service runs from there — the clone/ZIP you ran it from is disposable afterwards (a config.toml you already created in step 3 is migrated in). Re-run later from the installed copy — ~/.claude-telegram-interface/install.sh (macOS) or sudo ~/.claude-telegram-interface/install.sh (Linux) — e.g. after editing the config. Updating = download the new version anywhere and run its install.sh: code is refreshed in place, your config.toml is kept.

What gets installed:

  • macOS: a LaunchAgent in ~/Library/LaunchAgents (survives logout/crash; caffeinate -s keeps the Mac awake on AC power — on battery the bot pauses with the Mac's sleep and resumes on wake). Enabling tool execution also generates the Seatbelt write-jail and wires it into the config.
  • Linux: a systemd service that doubles as the write-jail. Full walkthrough: deploy/DEPLOY-LINUX.md.

For true always-on, a Linux VPS is the better host. Prefer manual setup? examples/ has a hand-fill LaunchAgent plist and Seatbelt profile.

Make it shine (optional)

The bridge is complete standalone. But working_dir decides what the agent is when a message wakes it: pointed at a directory built for persistent AI partnership, the same bot becomes a full working seat — your projects and shared memory, reachable from a phone. The recommended setup (the Flow methodology + an episodic memory CLI, by Phill Clapham) and the reasoning behind it live in docs/RECOMMENDED-SETUP.md.

Scope

Ships: run-anywhere core, write-jail on macOS (Seatbelt) and Linux (systemd unit + guided installer), TOML config, single-allowlist auth, code-aware MarkdownV2, reconnect + 429 backoff, typing UX.

Out of scope (v1): multi-user roles, plugins, web dashboard, inline buttons/rich UX. Generic-but-minimal on purpose.

Status, support & affiliation

Maintained as-is by a solo developer: issues and PRs are welcome, response times are not guaranteed, and there is no SLA. This is an independent project, not affiliated with or endorsed by Anthropic or Telegram. A Claude subscription covers individual use — this bridge is shaped for exactly that (your bot, your login, your machine, your own traffic only) — but verifying your own plan's terms is on you.

License

MIT — see LICENSE.

About

Self-hosted Telegram bridge to the Claude Code CLI — chat with your own machine's Claude from your phone. Bring your own bot; allowlist-of-one; sandboxed writes on macOS + Linux.

Topics

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages