Use Telegram to control a local AI coding engine. The same codebase now supports two single-engine deployment modes:
codex: bridges tocodex app-servergemini: bridges togemini -p --output-format stream-json
The runtime model is fixed:
- one bot = one engine
- one instance = one
.env.*file - one topic = one bot
- no runtime engine switching inside a topic
- Private chat or topic-aware group control for one allowed Telegram user
- Separate Codex and Gemini instances on the same host
- Sticky chat/topic binding, queued follow-ups, staged attachments, and restart-safe recovery
- Guided plan flow for Codex bots with confirm-or-revise gating
- Provider-aware
/help,/status,/settings, and slash-command registration - User-service deployment for Linux (
systemd --user), macOS (launchd), and Windows (WinSW)
Both engines share the same Telegram shell, but the capabilities are intentionally not identical.
| Capability | Codex bot | Gemini bot |
|---|---|---|
| Plain-text turns | Yes | Yes |
| Attachment staging | Yes | Yes |
| Queue and interrupt | Yes | Yes |
/threads and /open |
Yes | No |
/reveal into local desktop host |
Yes | No |
| Guided plan mode | Full | No |
| Approval controls | Full | No |
Rate-limit snapshot in /status |
Yes | No |
/reconnect |
Yes | No |
The bridge does not try to fake missing Gemini features. Unsupported commands are hidden from Gemini slash menus and rejected with a clear reply if called directly.
- Linux, macOS, or Windows
- Node.js 24+
- One Telegram bot token per instance
- Your Telegram numeric user id
- For
codexinstances:- authenticated
codexCLI
- authenticated
- For
geminiinstances:- authenticated
geminiCLI
- authenticated
Windows services are installed through the bundled PowerShell scripts. The installer writes your current profile paths into the service environment so existing Codex or Gemini CLI auth can still resolve from the same Windows user profile. Foreground mode is still useful for first-run diagnostics, but production Windows deployments should use the service install flow.
Recommended naming:
linux144-codexlinux144-gemini
Recommended bot topology in one Telegram group:
Linux144bot-> Codex topicGlinux144bot-> Gemini topic
Each topic should belong to exactly one bot. Do not share a topic across bots.
Install dependencies once:
npm installCreate one env file per instance:
cp .env.example .env.codex
cp .env.example .env.geminiBuild and run a foreground instance:
ENV_FILE=.env.codex npm run build
ENV_FILE=.env.codex npm run doctor
ENV_FILE=.env.codex npm run serveFor Gemini:
ENV_FILE=.env.gemini npm run build
ENV_FILE=.env.gemini npm run doctor
ENV_FILE=.env.gemini npm run serveWindows PowerShell foreground start:
$env:ENV_FILE=".env.codex"
npm run build
$env:ENV_FILE=".env.codex"
npm run doctor
$env:ENV_FILE=".env.codex"
npm run serveFor Gemini on Windows:
$env:ENV_FILE=".env.gemini"
npm run build
$env:ENV_FILE=".env.gemini"
npm run doctor
$env:ENV_FILE=".env.gemini"
npm run serveIf the Codex CLI was installed through the Microsoft Store and the default codex command is not directly executable from Node, set CODEX_CLI_BIN in your env file to a directly runnable codex.exe path.
Codex bot:
BRIDGE_ENGINE=codex
BRIDGE_INSTANCE_ID=linux144-codex
TG_BOT_TOKEN=123456:codex-token
TG_ALLOWED_USER_ID=123456789
TG_ALLOWED_CHAT_ID=-1001234567890
TG_ALLOWED_TOPIC_ID=101
DEFAULT_CWD=/home/ubuntu/dev
CODEX_CLI_BIN=/home/ubuntu/.local/bin/codex
CODEX_APP_AUTOLAUNCH=false
CODEX_APP_SYNC_ON_OPEN=true
CODEX_APP_SYNC_ON_TURN_COMPLETE=falseGemini bot:
BRIDGE_ENGINE=gemini
BRIDGE_INSTANCE_ID=linux144-gemini
TG_BOT_TOKEN=123456:gemini-token
TG_ALLOWED_USER_ID=123456789
TG_ALLOWED_CHAT_ID=-1001234567890
TG_ALLOWED_TOPIC_ID=202
DEFAULT_CWD=/home/ubuntu/dev
GEMINI_CLI_BIN=/home/ubuntu/.local/bin/gemini
GEMINI_DEFAULT_MODEL=gemini-2.5-pro
GEMINI_MODEL_ALLOWLIST=gemini-2.5-pro,gemini-2.5-flash
GEMINI_HEADLESS_TIMEOUT_MS=300000Service install is supported on Linux, macOS, and Windows.
Linux user service:
ENV_FILE=.env.codex ./scripts/service/install.sh
ENV_FILE=.env.gemini ./scripts/service/install.shUseful service commands:
ENV_FILE=.env.codex ./scripts/service/status.sh
ENV_FILE=.env.codex ./scripts/service/logs.sh
ENV_FILE=.env.codex ./scripts/service/restart-safe.sh
ENV_FILE=.env.gemini ./scripts/service/status.sh
ENV_FILE=.env.gemini ./scripts/service/logs.sh
ENV_FILE=.env.gemini ./scripts/service/restart-safe.shrestart-safe.sh and restart-safe.ps1 are instance-aware. They build, restart only the targeted service, wait for running=true and connected=true, then send a Telegram callback back to the latest active scope.
macOS install uses the same ENV_FILE pattern:
ENV_FILE=.env.codex ./scripts/service/install.shWindows PowerShell service install:
$env:ENV_FILE=".env.codex"
npm run build
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\service\install.ps1Run the installer from an elevated PowerShell session.
Useful Windows service commands:
$env:ENV_FILE=".env.codex"
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\service\status.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\service\logs.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\service\restart-safe.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\service\stop.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\service\uninstall.ps1The Windows installer downloads WinSW-x64.exe from the official WinSW GitHub releases on first install. Set WINDOWS_SERVICE_WRAPPER_PATH to use a pre-downloaded wrapper binary instead, or WINDOWS_SERVICE_WRAPPER_URL to pin a specific release URL.
- Create one bot token per engine with
@BotFather. - Put each token into its own env file.
- Set the same
TG_ALLOWED_USER_IDfor both instances if one operator controls both. - If you use a shared group:
- set the same
TG_ALLOWED_CHAT_ID - give each bot its own
TG_ALLOWED_TOPIC_ID
- set the same
- Disable privacy mode and re-add the bot if natural-language group messages do not arrive.
Shared commands:
/help/status/new/models/settings/queue/where/interrupt/restart
Codex-only commands:
/threads/open/guide/permissions/mode/plan/reconnect/reveal/tier/fast/effort
Codex bots can run a Telegram-adapted guided plan loop:
- Draft a plan without executing.
- Render one live plan card as the plan evolves.
- Ask you to
Continue,Revise, orCancel. - After confirmation, continue execution and request focused structured input when needed.
- Recover pending plan/input/approval cards after restart.
Gemini bots do not expose this flow in v1.
Telegram attachments are staged first instead of being forced into the same turn immediately.
- Albums are merged into one saved batch.
- The bridge replies with saved paths and metadata.
- Your next plain-text message can consume that batch automatically.
- You can also trigger
Analyze nowor clear the batch.
This behavior is shared by Codex and Gemini.
- One topic should contain one bot only.
- Keep Codex and Gemini in separate topics.
- Do not reuse one topic when you replace a bot unless you also update the env binding and restart that instance.
- If several bots share one group, prefer topic names that already show the engine, for example:
linux144-codexlinux144-gemini
Per instance:
/statusshows the correct engine and instance id./helpexposes only the commands that engine supports.- Plain-text turn works.
/interruptstops an active turn.- A follow-up message queues and resumes.
- A Telegram image or file is staged and then consumed on the next message.
/restartrestarts only that instance.
Extra Codex checks:
/threadsand/openwork./modeenters plan mode.- Guided plan confirmation, revise, and cancel all work.
/reconnectrefreshes the Codex session.
Extra Gemini checks:
- Gemini topic responds only through the Gemini bot.
- Codex-only commands are hidden from slash suggestions.
- Calling a Codex-only command manually returns a clear unsupported message.
Shared-group checks:
- Codex bot ignores the Gemini topic.
- Gemini bot ignores the Codex topic.
- Both services can run at the same time without sharing state files.
To discover TG_ALLOWED_CHAT_ID and TG_ALLOWED_TOPIC_ID:
- Stop the target instance.
- Send a message in the target group or topic.
- Open:
https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates
- Read:
message.chat.id->TG_ALLOWED_CHAT_IDmessage.message_thread_id->TG_ALLOWED_TOPIC_ID
If the instance is still polling, it may consume the update before you inspect it.