Control Claude Code and Codex CLI from Telegram. Live streaming, sessions, cron jobs, webhooks, Docker sandboxing.
Project description
ductor
ductor runs Claude Code and Codex CLI as your personal assistant on Telegram.
Persistent memory. Scheduled tasks. Live streaming. Docker sandboxing.
Uses only the official CLIs. Nothing spoofed, nothing proxied.
What is ductor?
ductor is a personal AI agent you talk to through Telegram. It runs on your machine, uses your existing Claude or Codex subscription, and remembers who you are between conversations. You give it a personality, teach it your preferences, and control it from your pocket - wherever you are.
No databases. No cloud services. No complicated setup. One Markdown file is the agent's entire memory. One folder (~/.ductor/) holds everything. Pure Python, pipx install, done.
You can set up cron jobs that work while you sleep, webhooks that react when something happens externally, and heartbeat prompts where the agent checks in on its own without being asked. The response streams live into your Telegram chat, and your session picks up right where you left it - even after a restart.
If you want a focused, single-user AI assistant that lives on your server and fits in your pocket, this is it.
Quick Start
pipx install ductor
ductor
The setup wizard walks you through the rest.
Why ductor?
You want to talk to Claude Code or Codex from your phone, from a tablet, or while you're away from your desk. Maybe you want scheduled tasks running overnight or webhooks that wake your agent when a GitHub PR lands. And you don't want to get your account banned in the process.
Other bots have gotten users suspended because they intercept OAuth tokens and forge API requests to impersonate the official CLI. ductor doesn't do that.
- Spawns the real CLI binary as a subprocess. No token interception, no request forging
- Uses only official rule files:
CLAUDE.mdandAGENTS.md - Memory is one Markdown file. No RAG, no vector stores
- One channel (Telegram), one Python package, one command
ductor makes the CLIs reachable through Telegram, gives them a memory they can actually use, and lets you automate things that would otherwise need you sitting at a terminal.
There are other bots. Why build another one?
I tried a bunch of CLI wrappers and Telegram bots for Claude and Codex. Most were either too complex to set up, too hard to modify, or got people banned because they spoofed headers and abused APIs. I wanted something that just uses the official CLIs the way they were meant to be used.
The agents are good enough now that you can steer them through their own rule files (CLAUDE.md, AGENTS.md). I don't need a RAG system to store memories - a single Markdown file that keeps track of what I like, what I don't, and what I'm working on is plenty. The agents still work the way they're supposed to, with their own skill sets, and I can reach them from Telegram instead of a terminal.
I picked Python because it's easy to modify. The agents can write their own automations in Python, receive webhooks (new email? parse it and ping me), set up scheduled tasks - really the only limit is your own creativity. All of that, controlled from a chat app on your phone. I like small comforts like the inline buttons the agents add to their replies. The rest I do by just talking to them.
That's it. That's why ductor exists.
Features
Core
- Responses stream in real-time. ductor edits the Telegram message live as text comes in
- Switch between Claude Code and Codex mid-conversation with
/model - Sessions survive bot restarts. Pick up where you left off
- Type
@opus explain thisto temporarily switch model without changing your default - Send images, PDFs, voice messages, or videos. ductor routes them to the right processing tool
- Agents can send
[button:Yes][button:No]inline keyboards back to you - Persistent memory across sessions, stored in one Markdown file the agent reads and writes
Automation
- Cron jobs - schedule recurring tasks with cron expressions and timezone support. Each job is its own subagent with a dedicated workspace, task description, and memory file. Results get posted back into your Telegram chat
- Webhooks - HTTP endpoints with Bearer or HMAC auth. Two modes: wake sends a prompt straight into your active chat (like you typed it yourself), cron_task runs an isolated task folder. Works with GitHub, Stripe, or anything that can send a POST request
- Heartbeat - the agent checks in periodically during active sessions. If it has an idea or a suggestion, it speaks up. Quiet hours are respected so it won't ping you at 3 AM
Example: a cron job
You tell the agent: "Check Hacker News every morning at 8 and send me the top AI stories."
ductor creates a task folder with everything the subagent needs:
~/.ductor/workspace/cron_tasks/hn-ai-digest/
CLAUDE.md # Agent rules (managed by ductor, don't edit)
TASK_DESCRIPTION.md # What the agent should do (you edit this)
hn-ai-digest_MEMORY.md # The subagent's own memory across runs
scripts/ # Helper scripts if needed
At 8:00 every morning, ductor starts a fresh agent session inside that folder. The subagent reads TASK_DESCRIPTION.md, does the work, writes what it learned to its own memory file, and posts the result into your Telegram chat. It runs completely isolated - no access to your main conversation or your main memory.
Example: a webhook wake call
Say your CI pipeline fails. You have a webhook in wake mode pointing at ductor. When the POST request arrives, ductor injects it as a message into your active chat - as if you typed it yourself. Your main agent sees it, has your full conversation history and memory, and responds right there in the chat.
POST /hooks/ci-failure -> "CI failed on branch main: test_auth.py::test_login timed out"
-> Your agent reads this, checks the code, and tells you what went wrong
Infrastructure
- Docker sandbox using Debian Bookworm. Both CLIs have full file system access by default, so running them in a container keeps your host safe. Auto-builds, persists auth, maps your workspace
/upgradechecks PyPI and updates with one click. Automatic restart after- Supervisor with PID lock. Exit code 42 means "restart me"
- Prompt injection detection, path traversal checks, per-user allowlist
Developer experience
- First-run wizard detects your CLIs, walks through config, seeds the workspace
- New config fields merge in automatically when you upgrade. Nothing breaks
/diagnosedumps recent logs,/statusshows session stats/stopkills whatever is running,/newclears the session
Prerequisites
| Requirement | Details |
|---|---|
| Python 3.11+ | python3 --version |
| pipx | pip install pipx (recommended) or use pip |
| One CLI installed | Claude Code or Codex CLI |
| CLI authenticated | claude auth or codex auth |
| Telegram Bot Token | Get one from @BotFather |
| Your Telegram User ID | Get it from @userinfobot |
| Docker (optional) | Recommended for sandboxed execution |
See Installation guide for detailed platform guides (Linux, macOS, WSL, Windows, VPS hosting).
How it works
You (Telegram)
|
v
ductor (aiogram)
|
├── AuthMiddleware (user allowlist)
├── SequentialMiddleware (per-chat lock)
|
v
Orchestrator
|
├── Command Router (/new, /model, /stop, ...)
├── Message Flow -> CLIService -> claude / codex subprocess
├── CronObserver -> Scheduled task execution
├── HeartbeatObserver -> Periodic background checks
├── WebhookObserver -> HTTP endpoint server
└── UpdateObserver -> PyPI version check
|
v
Streamed response -> Live-edited Telegram message
ductor spawns the CLI as a child process and parses its streaming output. The Telegram message gets edited live as text arrives. Sessions are stored as JSON. Background systems (cron, webhooks, heartbeat, update checks) run as asyncio tasks in the same process.
Your workspace
Everything lives in ~/.ductor/. One folder, nothing scattered.
~/.ductor/
config/config.json # Bot config (token, user IDs, model, Docker, timezone)
sessions.json # Active sessions per chat
cron_jobs.json # Scheduled task definitions
webhooks.json # Webhook endpoint definitions
CLAUDE.md # Agent rules (auto-synced)
AGENTS.md # Same rules for Codex (auto-synced)
logs/agent.log # Rotating log file
workspace/
memory_system/
MAINMEMORY.md # The agent's long-term memory about you
cron_tasks/ # One subfolder per scheduled job
hn-ai-digest/ # Example: each job has its own workspace
tools/
cron_tools/ # Add, edit, remove, list cron jobs
webhook_tools/ # Add, edit, remove, test webhooks
telegram_tools/ # Process files, transcribe audio, read PDFs
user_tools/ # Custom scripts the agent builds for you
telegram_files/ # Downloaded media, organized by date
output_to_user/ # Files the agent sends back to you
You can browse this folder at any time. Everything is plain text, JSON, or Markdown. No databases, no binary formats.
Configuration
Config lives in ~/.ductor/config/config.json. The wizard creates it on first run:
ductor # wizard creates config interactively
The important ones: telegram_token, allowed_user_ids, provider (claude or codex), default_model, docker.enabled, user_timezone. Full schema in docs/config.md.
Commands
| Command | Description |
|---|---|
/new |
Start a fresh session |
/stop |
Abort running CLI processes |
/model |
Switch AI model (interactive keyboard) |
/model opus |
Switch directly to a specific model |
/status |
Session info, tokens, cost, auth status |
/memory |
View persistent memory |
/cron |
List scheduled tasks |
/upgrade |
Check for updates and upgrade |
/restart |
Restart the bot |
/diagnose |
Show recent log output |
/help |
Command reference |
Documentation
| Document | Description |
|---|---|
| Installation guide | Platform-specific setup (Linux, macOS, WSL, Windows, VPS) |
| Automation quickstart | Cron jobs, webhooks, heartbeat - practical guide |
| Configuration | Full config schema and options |
| Architecture | System design and runtime flow |
| Module reference | Detailed docs for every subsystem |
Disclaimer
ductor runs the official CLI binaries as provided by Anthropic and OpenAI. It does not modify API calls, spoof headers, forge tokens, or impersonate clients. Every request originates from the real CLI process.
That said, Terms of Service can change at any time. Automating CLI interactions may fall into a gray area depending on how the providers interpret their rules. We built ductor to follow the intended usage patterns, but we cannot guarantee it won't lead to account restrictions.
Use at your own risk. Check the current ToS before deploying:
Not affiliated with Anthropic or OpenAI.
Contributing
git clone https://github.com/PleasePrompto/ductor.git
cd ductor
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest
ruff check .
mypy ductor_bot
Zero warnings, zero errors. See CLAUDE.md for conventions.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ductor-0.2.3.tar.gz.
File metadata
- Download URL: ductor-0.2.3.tar.gz
- Upload date:
- Size: 16.3 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cbf1350f056cc538e8ac9492ba82d60f41c82b9bd11adb37812b54637a136e0d
|
|
| MD5 |
8f5ba19d3eb0bfe62f8f46eb8b9a9b18
|
|
| BLAKE2b-256 |
1223911f89a49e374128c2689e56df86b420f10cba3cd6a04b170ecc27597582
|
Provenance
The following attestation bundles were made for ductor-0.2.3.tar.gz:
Publisher:
publish.yml on PleasePrompto/ductor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ductor-0.2.3.tar.gz -
Subject digest:
cbf1350f056cc538e8ac9492ba82d60f41c82b9bd11adb37812b54637a136e0d - Sigstore transparency entry: 928215823
- Sigstore integration time:
-
Permalink:
PleasePrompto/ductor@6925875966cf9aa7917ae801b44451d9b7f38c19 -
Branch / Tag:
refs/tags/v0.2.3 - Owner: https://github.com/PleasePrompto
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6925875966cf9aa7917ae801b44451d9b7f38c19 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ductor-0.2.3-py3-none-any.whl.
File metadata
- Download URL: ductor-0.2.3-py3-none-any.whl
- Upload date:
- Size: 16.3 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
33f33e7609534c175e613f4a4b0271b3301e680e078d9b9875f1f2d03650a35b
|
|
| MD5 |
c9d912e432f7baa55a9b523b45c373da
|
|
| BLAKE2b-256 |
2f723df1a27b9e8b20674c95a3803eedbc1f6044b1a1490039bb52c2952fa008
|
Provenance
The following attestation bundles were made for ductor-0.2.3-py3-none-any.whl:
Publisher:
publish.yml on PleasePrompto/ductor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ductor-0.2.3-py3-none-any.whl -
Subject digest:
33f33e7609534c175e613f4a4b0271b3301e680e078d9b9875f1f2d03650a35b - Sigstore transparency entry: 928215825
- Sigstore integration time:
-
Permalink:
PleasePrompto/ductor@6925875966cf9aa7917ae801b44451d9b7f38c19 -
Branch / Tag:
refs/tags/v0.2.3 - Owner: https://github.com/PleasePrompto
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6925875966cf9aa7917ae801b44451d9b7f38c19 -
Trigger Event:
push
-
Statement type: