A Telegram bot gateway that connects to local Claude Code sessions via the Agent SDK V2, enabling AI-assisted development through Telegram with multi-project routing.
- Native Claude Code Integration - Uses the Agent SDK V2 for direct session management
- Multi-Project Routing - Different Telegram groups/chats map to different project directories
- Proactive Heartbeat - Scheduled check-ins with configurable intervals per project
- Tool Approval Workflow - Approve/deny tool calls via Telegram inline buttons
- Model Provider Abstraction - Support for Claude Code native models and OpenRouter
- Session Persistence - Conversations persist across bot restarts via PostgreSQL
- Health Monitoring - Built-in health check endpoints for monitoring
- Quick Start
- Prerequisites
- Detailed Setup Guides
- Configuration
- Running the Gateway
- Architecture
- API Reference
- Troubleshooting
# 1. Clone and install
git clone https://github.com/your-repo/claude-bot.git
cd claude-bot
pnpm install && pnpm build
# 2. Set up PostgreSQL (Docker)
docker run -d --name claudebot-db \
-e POSTGRES_USER=claudebot \
-e POSTGRES_PASSWORD=your-secure-password \
-e POSTGRES_DB=claudebot \
-p 5432:5432 postgres:16
# 3. Authenticate Claude Code
claude auth login
# 4. Configure (copy and edit)
cp config.example.json config.json
cp .env.example .env
# Edit config.json with your Telegram bot token, user IDs, and project paths
# 5. Run migrations and start
pnpm --filter @claude-bot/storage db:migrate
pnpm --filter @claude-bot/gateway start| Requirement | Version | Purpose |
|---|---|---|
| Node.js | 20+ | Runtime |
| pnpm | 9+ | Package manager |
| PostgreSQL | 14+ | Session and message storage |
| Claude Code CLI | Latest | AI agent backend |
| Telegram Bot | - | User interface |
- Open Telegram and search for
@BotFather - Start a chat and send
/newbot - Choose a name (display name, can have spaces):
My Claude Assistant - Choose a username (must end in
bot):my_claude_assistant_bot - Copy the token - looks like:
123456789:ABCdefGHIjklMNOpqrsTUVwxyz
With BotFather, send these commands:
/setdescription - Add a description for your bot
/setabouttext - Add about text
/setcommands - Set command menu:
start - Start the bot
help - Show help
status - Check connection status
- Search for
@userinfoboton Telegram - Start a chat and send any message
- Copy your numeric user ID (e.g.,
123456789)
For group-based project routing:
- Create a Telegram group for your project
- Add your bot to the group
- Send a message in the group
- Get the chat ID using one of these methods:
Method A: Using the API
curl "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates" | jq '.result[-1].message.chat.id'Method B: Using @RawDataBot
- Add
@RawDataBotto your group temporarily - Send a message - it will reply with the chat ID
- Remove the bot after getting the ID
Group IDs are negative numbers (e.g., -1001234567890).
# Create and start container
docker run -d \
--name claudebot-db \
-e POSTGRES_USER=claudebot \
-e POSTGRES_PASSWORD=your-secure-password \
-e POSTGRES_DB=claudebot \
-p 5432:5432 \
-v claudebot-pgdata:/var/lib/postgresql/data \
postgres:16
# Verify it's running
docker ps | grep claudebot-db
# View logs if needed
docker logs claudebot-dbCreate docker-compose.yml:
version: '3.8'
services:
postgres:
image: postgres:16
container_name: claudebot-db
environment:
POSTGRES_USER: claudebot
POSTGRES_PASSWORD: your-secure-password
POSTGRES_DB: claudebot
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U claudebot"]
interval: 10s
timeout: 5s
retries: 5
volumes:
pgdata:Run with:
docker-compose up -d# macOS with Homebrew
brew install postgresql@16
brew services start postgresql@16
createdb claudebot
# Ubuntu/Debian
sudo apt install postgresql postgresql-contrib
sudo -u postgres createuser -P claudebot # Set password when prompted
sudo -u postgres createdb -O claudebot claudebotAfter PostgreSQL is running:
pnpm --filter @claude-bot/storage db:migrateThe gateway uses the Claude Code CLI under the hood for AI capabilities.
# Install globally
npm install -g @anthropic-ai/claude-code
# Or use with npx (no install)
npx @anthropic-ai/claude-code --version# Interactive login
claude auth login
# This opens a browser for authentication
# Follow the prompts to authorize# Check auth status
claude auth status
# Test with a simple prompt
claude -p "Say hello"If authentication fails:
# Clear existing auth
claude auth logout
# Re-authenticate
claude auth login
# Check config location
ls -la ~/.claude/OpenRouter provides access to alternative models. This is optional - you can use Claude Code's native models only.
- Go to openrouter.ai
- Sign up or log in
- Navigate to API Keys
- Create a new key and copy it
Add to your config.json:
{
"openRouter": {
"apiKey": "sk-or-YOUR_API_KEY",
"defaultModel": "anthropic/claude-3.5-sonnet"
}
}Or use environment variable:
export OPENROUTER_API_KEY="sk-or-YOUR_API_KEY"anthropic/claude-3.5-sonnet # Claude 3.5 Sonnet
anthropic/claude-3-opus # Claude 3 Opus
google/gemini-pro # Google Gemini Pro
openai/gpt-4-turbo # OpenAI GPT-4 Turbo
| File | Purpose | Required |
|---|---|---|
config.json |
Main configuration | Yes |
.env |
Environment variables (secrets) | Recommended |
Create .env from the example:
cp .env.example .env| Variable | Required | Description |
|---|---|---|
TELEGRAM_BOT_TOKEN |
Yes* | Bot token from BotFather |
DATABASE_URL |
Yes* | PostgreSQL connection string |
OPENROUTER_API_KEY |
No | OpenRouter API key |
GATEWAY_PORT |
No | Health server port (default: 3000) |
LOG_LEVEL |
No | Logging level (default: info) |
NODE_ENV |
No | Environment (development/production) |
*Can also be set in config.json
{
"$schema": "./config.schema.json",
"gateway": { /* Gateway settings */ },
"database": { /* Database connection */ },
"telegram": { /* Telegram bot settings */ },
"openRouter": { /* OpenRouter settings (optional) */ },
"projects": [ /* Project definitions */ ]
}| Option | Type | Default | Description |
|---|---|---|---|
port |
number | 3000 | Health check server port |
logLevel |
string | "info" | Log level: trace, debug, info, warn, error |
prettyLogs |
boolean | true | Enable colored, formatted logs |
| Option | Type | Default | Description |
|---|---|---|---|
url |
string | - | PostgreSQL connection URL |
poolSize |
number | 10 | Connection pool size |
queryTimeout |
number | 30000 | Query timeout in ms |
| Option | Type | Default | Description |
|---|---|---|---|
botToken |
string | - | Bot token from BotFather |
allowedUsers |
string[] | [] | User IDs allowed to use the bot |
allowedGroups |
string[] | [] | Group IDs allowed to use the bot |
usePolling |
boolean | true | Use polling (true) or webhooks (false) |
| Option | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Unique identifier (alphanumeric + hyphens) |
name |
string | Yes | Display name |
workspacePath |
string | Yes | Absolute path to project directory |
telegramGroupId |
string | Yes | Telegram chat ID for this project |
models.default |
string | Yes | Default model for conversations |
models.heartbeat |
string | No | Model for heartbeat (uses default if not set) |
permissions |
object | No | Tool permission settings |
heartbeat |
object | No | Heartbeat configuration |
Control which Claude tools are auto-approved, require confirmation, or are denied:
{
"permissions": {
"autoApprove": ["Read", "Glob", "Grep", "LS"],
"requireConfirmation": ["Bash", "Write", "Edit"],
"deny": ["rm", "sudo"]
}
}| Permission Level | Behavior |
|---|---|
autoApprove |
Tool executes immediately without user input |
requireConfirmation |
User must approve via Telegram button |
deny |
Tool is blocked entirely |
| (not listed) | Defaults to requireConfirmation |
Available Tools:
Read- Read file contentsWrite- Write/create filesEdit- Edit existing filesBash- Execute shell commandsGlob- Find files by patternGrep- Search file contentsLS- List directory contents
Enable proactive check-ins for a project:
{
"heartbeat": {
"enabled": true,
"intervalMinutes": 30,
"prompt": "Check for pending tasks, failing tests, or issues that need attention."
}
}| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | false | Enable heartbeat for this project |
intervalMinutes |
number | 30 | Check-in interval (1-1440 minutes) |
prompt |
string | - | Custom prompt for heartbeat checks |
# With hot reload
pnpm --filter @claude-bot/gateway start:dev# Build and start
pnpm build
pnpm --filter @claude-bot/gateway start# Install PM2
npm install -g pm2
# Start with PM2
pm2 start pnpm --name "claude-bot" -- --filter @claude-bot/gateway start
# View logs
pm2 logs claude-bot
# Monitor
pm2 monit
# Restart on file changes
pm2 start pnpm --name "claude-bot" --watch -- --filter @claude-bot/gateway startCreate /etc/systemd/system/claude-bot.service:
[Unit]
Description=Claude Bot Gateway
After=network.target postgresql.service
[Service]
Type=simple
User=your-username
WorkingDirectory=/path/to/claude-bot
Environment=NODE_ENV=production
ExecStart=/usr/bin/pnpm --filter @claude-bot/gateway start
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.targetEnable and start:
sudo systemctl enable claude-bot
sudo systemctl start claude-bot
sudo systemctl status claude-botclaude-bot/
├── packages/
│ ├── core/ # Shared types, utilities, errors, logger
│ │ └── src/
│ │ ├── types/ # Common type definitions
│ │ ├── errors/ # Error classes and handling
│ │ ├── logger/ # Pino-based logging
│ │ └── utils/ # Helper functions
│ │
│ ├── config/ # Configuration management
│ │ └── src/
│ │ ├── schema.ts # Zod schemas for validation
│ │ └── loader.ts # Config loading and helpers
│ │
│ ├── storage/ # Database layer
│ │ └── src/
│ │ ├── schema.ts # Drizzle schema definitions
│ │ ├── client.ts # Database client
│ │ └── repositories/
│ │ ├── sessions.ts
│ │ └── messages.ts
│ │
│ ├── channels/
│ │ └── telegram/ # Telegram integration
│ │ └── src/
│ │ ├── bot.ts # grammY bot setup
│ │ ├── handlers.ts # Message handlers
│ │ └── formatter.ts # Message formatting
│ │
│ ├── agents/ # Claude Agent SDK wrapper
│ │ └── src/
│ │ ├── agent.ts # Agent class
│ │ ├── session.ts # Session management
│ │ └── types.ts # Agent types
│ │
│ ├── models/ # Model provider abstraction
│ │ └── src/
│ │ ├── providers/
│ │ │ ├── claude-code.ts
│ │ │ └── openrouter.ts
│ │ └── router.ts # Provider routing
│ │
│ └── scheduler/ # Job scheduling
│ └── src/
│ ├── scheduler.ts # Main scheduler
│ └── jobs/
│ └── heartbeat.ts
│
└── apps/
└── gateway/ # Main application
└── src/
├── gateway.ts # Main Gateway class
├── conversation.ts # Message flow handling
├── project-manager.ts # Per-project agent management
└── health.ts # Health check endpoints
Telegram User
│
▼
┌─────────────┐
│ grammY Bot │ (packages/channels/telegram)
└─────────────┘
│
▼
┌─────────────┐
│ Gateway │ (apps/gateway)
│ │
│ ┌─────────┐ │
│ │ Project │ │ Routes messages to correct project
│ │ Manager │ │
│ └─────────┘ │
└─────────────┘
│
▼
┌─────────────┐
│ Agent │ (packages/agents)
│ │
│ Claude SDK │ Manages AI sessions
│ V2 │
└─────────────┘
│
▼
┌─────────────┐
│ Storage │ (packages/storage)
│ │
│ PostgreSQL │ Persists sessions & messages
└─────────────┘
The gateway exposes HTTP endpoints on the configured port (default: 3000).
Full health status with component checks.
curl http://localhost:3000/healthResponse:
{
"status": "healthy",
"timestamp": "2024-01-15T10:30:00Z",
"components": {
"telegram": { "status": "up", "lastCheck": "2024-01-15T10:29:55Z" },
"database": { "status": "up", "lastCheck": "2024-01-15T10:29:58Z" },
"scheduler": { "status": "up" }
}
}Simple liveness probe for container orchestration.
curl http://localhost:3000/liveResponse: 200 OK with {"status": "ok"}
Readiness probe - checks if the gateway can accept traffic.
curl http://localhost:3000/readyResponse: 200 OK if ready, 503 Service Unavailable if not.
Version information.
curl http://localhost:3000/versionResponse:
{
"name": "@claude-bot/gateway",
"version": "1.0.0",
"nodeVersion": "v20.10.0"
}- Check bot token: Verify token in config matches BotFather
- Check allowed users: Ensure your user ID is in
allowedUsers - Check logs:
pnpm --filter @claude-bot/gateway startshows errors - Test bot directly: Send
/startto your bot
# Check PostgreSQL is running
docker ps | grep postgres
# or
systemctl status postgresql
# Test connection
psql postgresql://claudebot:password@localhost:5432/claudebot -c "SELECT 1"
# Check connection string format
# Format: postgresql://USER:PASSWORD@HOST:PORT/DATABASE# Check auth status
claude auth status
# Re-authenticate if needed
claude auth logout
claude auth login
# Test Claude directly
claude -p "Hello"- Check telegramGroupId: Must match the chat ID exactly
- Group IDs are negative: e.g.,
-1001234567890 - User chats are positive: e.g.,
123456789
- Bot needs admin in groups: For inline buttons to work in groups
- Check callback handling: View logs for callback errors
- Check enabled:
heartbeat.enabledmust betrue - Check interval: Must be between 1 and 1440 minutes
- Check scheduler logs: Look for heartbeat scheduling messages
- Reduce pool size: Lower
database.poolSize - Check for memory leaks: Monitor with
node --inspect - Restart periodically: Use PM2 with
--max-memory-restart
| Error | Cause | Solution |
|---|---|---|
ECONNREFUSED |
Database not running | Start PostgreSQL |
401 Unauthorized |
Invalid bot token | Check token with BotFather |
403 Forbidden |
User not in allowedUsers | Add user ID to config |
ETIMEDOUT |
Network issues | Check firewall, DNS |
# All tests
pnpm test
# Specific package
pnpm --filter @claude-bot/core test
# Watch mode
pnpm test -- --watch
# Coverage
pnpm test -- --coveragepnpm typecheckpnpm lint
pnpm lint:fix# Build all packages
pnpm build
# Build specific package
pnpm --filter @claude-bot/core buildMIT