https://github.com/nkschmidt/agentgram
Telegram bot that proxies chat messages to CLI coding agents (Claude Code, opencode) with streaming responses, voice input, and per-user sessions.
https://github.com/nkschmidt/agentgram
ai-agent ai-agents claude claude-code opencode telegram telegrambot
Last synced: 12 days ago
JSON representation
Telegram bot that proxies chat messages to CLI coding agents (Claude Code, opencode) with streaming responses, voice input, and per-user sessions.
- Host: GitHub
- URL: https://github.com/nkschmidt/agentgram
- Owner: nkschmidt
- Created: 2026-05-28T21:25:23.000Z (26 days ago)
- Default Branch: master
- Last Pushed: 2026-05-28T22:32:40.000Z (26 days ago)
- Last Synced: 2026-05-28T23:20:23.116Z (26 days ago)
- Topics: ai-agent, ai-agents, claude, claude-code, opencode, telegram, telegrambot
- Language: Go
- Homepage:
- Size: 81.1 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# agentgram — Telegram bot wrapper for CLI agents
[](https://go.dev/) [](LICENSE)
A Telegram bot that hooks [Claude Code](https://claude.ai/code) and [OpenCode](https://github.com/opencode-ai/opencode) into Telegram — chat with AI agents straight from your messenger and drive your projects from any device.
## What is it?
The bot turns Telegram into a console for CLI agents. Send messages, get streamed responses, just like working in a terminal:
```
You: Refactor the auth middleware, extract it into a separate package
Bot: ⏳ Working...
📖 Read: middleware.go
📖 Read: auth.go
✏️ Edit: middleware.go
Bot: Done. Moved authentication into package auth, updated imports, all 12 tests pass.
```
## Features
### AI backends
- **Claude Code** — per-message CLI invocation with `--resume` for multi-turn sessions. Supports thinking, tool_use, result.
- **OpenCode** — HTTP API to a local `opencode serve` with SSE streaming and lazy server startup.
- **Modular architecture** — adding a new backend = one package implementing the `Backend` interface.
### In-chat streaming
- A single message updates as chunks arrive (typing + edit).
- An `⏹ Stop` button — soft interrupt (SIGINT / `/abort`).
- The final answer is formatted as HTML with a plain-text fallback.
### Attachments
- **Documents and photos** — downloaded to the server, the path is passed to the agent.
- **Voice messages** — transcribed via whisper.cpp right inside the bot.
### Agent ↔ user channel (MCP)
The bot runs a local [MCP](https://modelcontextprotocol.io/) server that gives agents three extra tools, so they can reach **back** to your chat — not just reply with text:
- **`send_photo`** — send an image inline as a photo (with optional caption).
- **`send_document`** — send any file as a downloadable attachment.
- **`ask_user`** — ask a question mid-task; options are shown as tappable buttons, and you can also just reply with text.
```
You: Plot the sales data from report.csv and send me the chart
Bot: 📊 Built the chart from 1,240 rows.
[photo: sales_q3.png] ← delivered via send_photo
You: Deploy the new version
Bot: ❓ Which environment?
[ Staging ] [ Production ] ← ask_user buttons (or type your own answer)
```
Routing is per-user: the bot mints a Bearer token per user and wires it into each backend's MCP config, so a tool call always lands in the right chat. `ask_user` blocks until you answer (button tap or typed reply); `/new_session` and `/restart` always work as an escape hatch. Works for both backends:
- **Claude Code** — token passed per invocation via `--mcp-config` + `--allowedTools`; the agent is nudged toward these tools via `--append-system-prompt`.
- **OpenCode** — registered as a remote MCP server in a per-workdir `opencode.json` (the bot writes/merges it on session start); nudged via the per-message `system` instruction.
### Settings
- **Working directories** — per-user, folder browser via inline keyboard, up to 100 subdirectories per screen.
- **Whitelist** — access by `user_id`; the first writer is added automatically.
- **Voice** — paths to whisper-cli and the model are configured via `/settings`, with autodetect on startup.
### Commands
| Command | Description |
|---------|-------------|
| `/settings` | Allowed users, working directory, voice |
| `/new_session` | New session with backend selection |
| `/restart` | Restart the bot (in-place exec) |
## Quick start
### Requirements
- **Go 1.25+**
- **Telegram Bot Token** — get one from [@BotFather](https://t.me/botfather)
- **Claude Code CLI** — [install](https://claude.ai/code) (for the claude backend)
- **OpenCode** — [install](https://github.com/opencode-ai/opencode) (for the opencode backend)
- **whisper.cpp** — optional, for voice transcription
### Install
```bash
git clone https://github.com/nkschmidt/agentgram.git
cd agentgram
```
### Configure
```bash
cp .env.example .env
# Edit .env:
```
```env
TELEGRAM_BOT_TOKEN=
```
On first run the bot will automatically:
1. Detect paths to `whisper-cli` and the model (if present on the system).
2. Save them to `settings.json`.
3. Add the first user to the whitelist.
### Run
```bash
go run ./cmd/bot
```
Write to the bot on Telegram — the first user is added to the whitelist automatically.
## Project structure
```
cmd/bot/main.go — entry point
internal/settings — runtime settings (JSON, RWMutex)
internal/router — routing: Message → command / state / backend
internal/backend — Backend interface + factory registry
claude — per-message CLI with --resume
opencode — HTTP API + SSE, lazy server
toolfmt — tool-call rendering
internal/session — per-user sessions, auto-stop, chunk handler
internal/command — commands, streaming, callbacks, state machine
internal/bot — tgbotapi glue: service, replier, composer, sender
internal/mcp — local MCP server: send_photo / send_document
internal/asr — transcription (whisper.cpp + noop)
```
## Architecture
### Backend abstraction
Every AI backend implements a single interface:
```go
type Backend interface {
Start(ctx context.Context) error
Send(input string) error
Recv() <-chan Chunk
Interrupt() error
Stop() error
}
```
Factories are registered in a global registry by name (`claude`, `opencode`). A new backend = a new package without touching the core.
### Sessions
`session.Manager` keeps one active session per user. When a new message is sent the previous session is stopped automatically. Chunks are delivered to the chat via `StreamCoordinator`.
### Settings
Stored in `settings.json` (bot cwd). Keys:
```json
{
"allowed_users": [150041509],
"work_dirs": {
"150041509": "/path/to/project"
},
"whisper_bin": "/opt/homebrew/bin/whisper-cli",
"whisper_model": "/path/to/ggml-base.bin"
}
```
- `allowed_users` — empty list = autoseed (the first user is added).
- `work_dirs` — when the directory changes, the user's session is restarted.
- `whisper_*` — autodetected on startup, changed via `/settings`.
## Deployment
### Local
```bash
go build -o bot ./cmd/bot
./bot
```
### VPS (systemd)
```bash
sudo tee /etc/systemd/system/agentgram.service > /dev/null <