TeleCodex is a Telegram bridge for the OpenAI Codex CLI SDK. It keeps a Codex thread alive from your phone, streams agent responses and tool output in real time, and lets you hand the thread back to the CLI whenever you want.
- Per-context sessions — each Telegram chat or forum topic gets its own independent Codex session with separate thread, model, and busy state
- Streaming responses — agent text edits in-place as Codex generates it
- Full tool visibility — shell commands, file changes, web searches, MCP calls, and error items shown with configurable verbosity
- Live plan display — Codex's todo list rendered as a separate message and updated as steps complete
- Voice transcription — send a voice message or audio file; TeleCodex transcribes it (local parakeet-coreml or OpenAI Whisper) and forwards the text to Codex
- Image input — send a photo (with optional caption) to pass screenshots or images directly to Codex
- File ingest & artifacts — send a document to stage it for Codex; generated files are delivered back as Telegram documents
- Session browser —
/sessionslists recent threads from~/.codex, grouped by workspace; tap to switch - Telegram login —
/loginauthenticates against the Codex CLI via device auth flow, no terminal needed - Launch profiles —
/launch_profilesselects the sandbox + approval mode for new or reattached threads in the current Telegram context (/launchremains an alias) - Model picker —
/modelshows available models and lets you switch for new threads - Reasoning effort —
/effortlets you dial fromminimaltoxhighfor new threads - Optional message reactions — 👀 while processing, 👍 on success when enabled; silently degrades in chats without reaction support
- Friendly errors — common SDK and network errors are translated to actionable messages with command hints
- Token usage — session token totals shown on
/session, with optional per-turn footer in replies - Handback flow —
/handbackprints a ready-to-runcodex resume <id>command (copied to clipboard on macOS) - User allowlist — only configured Telegram user IDs can interact with the bot
- Docker-friendly — workspace auto-detected (
/workspacein containers,cwdotherwise)
- Node.js 22+
- A Telegram bot token from @BotFather
- The Codex CLI installed and authenticated on the host:
- API key auth: set
CODEX_API_KEY - ChatGPT login:
codex loginon the machine, or use/loginfrom Telegram
- API key auth: set
- (Optional)
ffmpeg— required for local voice transcription via parakeet-coreml - (Optional)
OPENAI_API_KEY— enables OpenAI Whisper as a voice transcription fallback
-
Install dependencies:
npm install
-
Copy the example environment file:
cp .env.example .env
-
Fill in
.env:Variable Required Description TELEGRAM_BOT_TOKEN✅ Bot token from @BotFather TELEGRAM_ALLOWED_USER_IDS✅ Comma-separated Telegram user IDs CODEX_API_KEY— API key for Codex (alternative to ChatGPT login) CODEX_MODEL— Default model, e.g. gpt-5.4,o3CODEX_SANDBOX_MODE— read-only,workspace-write(default),danger-full-accessCODEX_APPROVAL_POLICY— never(default),on-request,on-failure,untrustedCODEX_LAUNCH_PROFILES_JSON— Optional JSON array of named launch profiles for /launch_profilesCODEX_DEFAULT_LAUNCH_PROFILE— Default launch profile id (defaults to default)ENABLE_UNSAFE_LAUNCH_PROFILES— Set to trueto allow extradanger-full-accesslaunch profilesTOOL_VERBOSITY— all,summary(default),errors-only,noneSHOW_TURN_TOKEN_USAGE— Show the per-turn in/cached/outfooter in final replies (falseby default)MAX_FILE_SIZE— Max upload size in bytes (default 20971520= 20 MB)ENABLE_TELEGRAM_LOGIN— Allow /loginand/logoutfrom Telegram (trueby default)ENABLE_TELEGRAM_REACTIONS— Enable Telegram emoji reactions like 👀 / 👍 ( falseby default)OPENAI_API_KEY— Enables OpenAI Whisper voice transcription fallback -
Start the bot:
npm run dev
| Command | Description |
|---|---|
/start |
Welcome & status (concise for returning users) |
/help |
Grouped command reference |
/new |
Start a fresh thread (workspace picker if multiple workspaces) |
/session |
Current thread ID, workspace, model, effort, and token totals |
/sessions |
Browse recent threads grouped by workspace; tap to switch |
/switch <id> |
Switch directly to a thread by ID |
/retry |
Resend the last prompt |
/abort |
Cancel the current turn |
/launch_profiles |
Select launch profile for new or reattached threads (/launch alias kept) |
/model |
View and change the model |
/effort |
Set reasoning effort: minimal · low · medium · high · xhigh |
/auth |
Check authentication status |
/login |
Start Codex device-auth flow from Telegram |
/logout |
Sign out of Codex |
/voice |
Check voice transcription backend status |
/handback |
Print codex resume <id> for CLI handoff |
/attach <id> |
Bind an existing Codex thread to this forum topic |
- Voice / audio — send any voice message or audio file; TeleCodex transcribes it and sends the result to Codex
- Photos — send a photo with an optional caption; the image is forwarded to Codex as visual input
- Documents — send a file (with optional caption); TeleCodex stages it in the workspace, runs Codex, and delivers any generated files back as Telegram documents
| Mode | What you see |
|---|---|
all |
Every tool start, streaming output, and result |
summary (default) |
A short grouped footer such as Tools used: 3x bash, 2x subagents, web_fetch |
errors-only |
Only failed tool calls |
none |
Silent |
Per-turn token usage is hidden by default. Set SHOW_TURN_TOKEN_USAGE=true if you want the in / cached / out footer appended to final replies.
- TeleCodex always provides a built-in
defaultprofile synthesized fromCODEX_SANDBOX_MODEandCODEX_APPROVAL_POLICY - Built-in Telegram-visible presets are:
DefaultRead OnlyReviewFull AccesswhenENABLE_UNSAFE_LAUNCH_PROFILES=true
Workspace Writeis not listed separately because it is already the default behavior in the shipped config- Optional extra profiles can be configured with
CODEX_LAUNCH_PROFILES_JSON, for example:[ { "id": "readonly", "label": "Read Only", "sandboxMode": "read-only", "approvalPolicy": "never" }, { "id": "review", "label": "Review", "sandboxMode": "workspace-write", "approvalPolicy": "on-request" } ] /launch_profileschanges only future thread creation or reattachment in the current chat/topic context; it does not mutate an already active thread in place- Extra
danger-full-accessprofiles are blocked unlessENABLE_UNSAFE_LAUNCH_PROFILES=true - Selecting a
danger-full-accessprofile from Telegram requires an explicit confirmation step
Each Telegram chat or forum topic is identified by a context key — the chat ID alone for private chats, or chatId:threadId for forum topics. This means every topic in a supergroup gets its own independent Codex session.
The SessionRegistry maps context keys to CodexSessionService instances:
┌───────────────────┐ ┌───────────────────────────────┐
│ Private Chat A │─────▶│ CodexSessionService (thread X) │
│ key: "111" │ └───────────────────────────────┘
├───────────────────┤ ┌───────────────────────────────┐
│ Group B / Topic 1 │─────▶│ CodexSessionService (thread Y) │
│ key: "222:1" │ └───────────────────────────────┘
├───────────────────┤ ┌───────────────────────────────┐
│ Group B / Topic 2 │─────▶│ CodexSessionService (thread Z) │
│ key: "222:2" │ └───────────────────────────────┘
└───────────────────┘
- First message in a context → creates a new
CodexSessionService→ starts a new Codex thread - Subsequent messages → same context key → same session → conversation continues
/new→ replaces the thread within the same context (optionally picking a workspace first)/sessions→ lists all Codex threads from~/.codex, lets you switch within the current context/attach <id>→ resumes a specific Codex CLI thread (useful for picking up work started in the terminal)
Session metadata (thread ID, workspace, launch profile, model, effort) is persisted to .telecodex/contexts.json and restored on restart so threads survive bot reboots.
Each context has independent busy-state tracking, so a running prompt in one topic doesn't block another.
- Run
/handbackin Telegram - TeleCodex replies with:
cd '/path/to/project' && codex resume 'thread-abc123'
- Paste and run in your terminal
On macOS the command is also copied to the clipboard automatically.
Telegram ←→ Grammy bot (auto-retry, HTML formatting, inline keyboards)
|
v
SessionRegistry ──→ per-context CodexSessionService instances
|
├── @openai/codex-sdk ──→ spawns Codex CLI subprocess
│ └── ThreadEvents (agent text, commands, file changes,
│ MCP calls, web searches, todo lists,
│ reasoning, errors, token usage)
├── CodexStateReader ──→ ~/.codex/state_*.sqlite (threads)
│ ──→ ~/.codex/models_cache.json (models)
├── CodexAuth ──→ codex login/logout subprocess
├── Attachments ──→ .telecodex/inbox/<turnId>/ (staged files)
├── Artifacts ──→ .telecodex/outbox/<turnId>/ (generated files)
└── VoiceTranscriber ──→ parakeet-coreml (local)
──→ OpenAI Whisper (cloud fallback)
TeleCodex/
├── src/
│ ├── index.ts — startup, signal handling, polling loop
│ ├── bot.ts — Telegram bot, all commands and handlers
│ ├── bot-ui.ts — pure render helpers (/help, /start, session labels)
│ ├── codex-launch.ts — launch profile parsing, validation, and formatting
│ ├── codex-session.ts — CodexSessionService wrapping the SDK
│ ├── codex-state.ts — SQLite reader for thread/model discovery
│ ├── codex-auth.ts — Codex CLI auth (login status, device auth, logout)
│ ├── session-registry.ts — per-context session map with persistence
│ ├── context-key.ts — Telegram chat/topic → context key derivation
│ ├── attachments.ts — file staging (sanitization, size limits)
│ ├── artifacts.ts — generated file collection and Telegram delivery
│ ├── error-messages.ts — SDK/network error → user-friendly translation
│ ├── voice.ts — voice transcription (parakeet / Whisper)
│ ├── config.ts — environment loading and validation
│ └── format.ts — Markdown → Telegram HTML conversion
├── test/ — 15 test files, 180+ tests (vitest)
├── .env.example
├── Dockerfile
├── docker-compose.yml
├── tsconfig.json
└── vitest.config.ts
docker compose up --buildThe compose file:
- loads environment from
.env - mounts
~/.codexfor auth state and persisted threads - mounts
./workspaceas/workspace - runs as a non-root user
npm run dev # run with tsx (no build step)
npm run build # compile TypeScript
npm test # run vitestTeleCodex does not yet use the TelePi npm release pipeline, but the exact Trusted Publishing process has been documented so it can be adopted here.
See:
docs/npm-trusted-publishing.md
That playbook covers:
- making the package publishable on npm
- adding a tag-driven GitHub Actions workflow
- configuring npm Trusted Publishing
- the maintainer release flow (
npm version ...+git push --follow-tags)
- Only users in
TELEGRAM_ALLOWED_USER_IDScan interact with the bot - Default sandbox mode is
workspace-write— Codex can read and write within the working directory - Use
danger-full-accessonly if you fully trust the user and the host environment - The built-in
Full Accessprofile and any extradanger-full-accesslaunch profiles are opt-in viaENABLE_UNSAFE_LAUNCH_PROFILES=true - Default approval policy is
never— suited for headless/automated use /launch_profilesonly selects from validated configured profiles; Telegram users cannot submit arbitrary sandbox or approval valuesCODEX_API_KEY(agent auth) andOPENAI_API_KEY(voice transcription) are separate credentials/loginand/logoutcan be disabled by settingENABLE_TELEGRAM_LOGIN=false- Files uploaded via Telegram are sanitized (name, size, type) before staging in the workspace
- All Markdown output is sanitized before being sent as Telegram HTML