Telegram forum-topic bridge for Codex CLI. Each Telegram topic maps to one persisted Codex session; messages in the same topic resume the same session, while different topics can run independently.
tmux is useful for attaching to terminal UI processes, but it is the wrong source of truth for this bridge. The bot uses codex exec --json for normal text turns by default, can switch normal text turns to codex app-server with CODEX_TEXT_RUNNER=app-server, and uses codex app-server for native realtime voice input. The durable mapping is chat_id + message_thread_id -> codex_session_id.
- Create a Telegram bot with BotFather and add it to a supergroup with topics enabled.
- Copy
.env.exampleto.envand setTELEGRAM_BOT_TOKEN. Ifapi.telegram.orgis not reachable directly, setTELEGRAM_PROXY, for examplehttp://127.0.0.1:2333. - Install and run:
npm install
npm run build
npm startFor local development:
npm run devUse launchd, not tmux:
cp .env.example .env
# edit .env and fill TELEGRAM_BOT_TOKEN
bash scripts/install-launchd.shStop and remove the LaunchAgent:
bash scripts/uninstall-launchd.shLogs are written to logs/launchd.out.log and logs/launchd.err.log.
/helpshows usage./statusshows the current topic mapping and busy state./sessionshows the Codex session id for this topic./goalshows the current Codex thread goal./goal <objective>sets or replaces it, and/goal pause,/goal unpause,/goal clearcontrol it./resumelists recent Codex sessions for the current workspace in a Telegram-friendly format./resume --alllists recent Codex sessions across all workspaces./resume <short-id|session-id|thread-name> [prompt]binds this topic to an existing Codex session. Short ids are the 8-character prefixes shown by/resume. If a prompt is supplied, it is sent immediately./resume --last [prompt]binds this topic to the latest Codex session in the current workspace. Add--allto ignore workspace filtering./cwd [path]shows or switches this topic's Codex workspace. Switching workspace starts the next message in a fresh Codex session because existing Codex sessions cannot be safely moved to another cwd./new [prompt]starts a fresh Codex session in this topic. If a prompt is supplied, it is sent immediately./resetforgets this topic's Codex session but keeps the topic workspace; the next normal message creates a new session./cancelterminates the currently running Codex process for this topic.
- The bot should receive normal messages in topic threads. In a topic-enabled supergroup, Telegram includes
message_thread_id; the bridge uses it as the session boundary. - Set
CODEX_TEXT_RUNNER=app-serverto route normal text messages,/new <prompt>, and/resume <id> <prompt>through app-serverturn/startinstead ofcodex exec. Leave it asexecfor the older CLI runner. - Messages in one topic are serialized. If Codex is still running, later messages in the same topic wait their turn.
- Codex can keep producing Markdown, but long Telegram replies are normalized before sending: headings, links, lists, inline code, emphasis, and fenced code blocks are converted to a simpler Telegram-readable text form.
- While Codex is running, assistant text events are shown as a throttled Telegram preview message; the final complete answer is still sent after the run finishes.
- Images generated by Codex are sent back to the Telegram topic as photos when the CLI reports a saved image path.
/goaluses Codex's persisted thread-goal state database, so it affects the same session goal that Codex itself reads on later turns.- Telegram voice messages are downloaded, converted with
ffmpegto Codex realtime PCM16 mono 24 kHz frames, and sent through app-serverthread/realtime/*. This requires Codex realtime API-key auth and therealtime_conversationfeature, which the bridge enables when starting app-server. - Telegram photos/files are not bridged yet. The next production-worthy extension is downloading Telegram photos/files and passing them to Codex with
--imageor app-serverLocalImage.
MIT