Run your Oh My Pi (OMP) / Pi coding agent over Telegram — streaming replies, voice notes, scheduled prompts, and one-tap approvals.
pi-telegram-bridge is a standalone Bun/TypeScript service that connects a Telegram bot to your Oh My Pi coding agent session. Message it like you'd message a person — it streams edited replies back, keeps one session per chat (or per forum topic), and can talk to you, listen to voice notes, run on a schedule, and gate risky actions behind an Approve/Cancel tap.
- Features
- Requirements
- Install
- Running
- Configuration
- Slash commands
- Topics & forums
- Voice notes
- Voice replies / TTS
- Scheduler
- Security
- Troubleshooting
- Roadmap
- License
- Streaming edited replies — the bot's message is edited in place as the agent's answer streams in, instead of a wall of new messages.
- Tool-progress bubble — a single ephemeral message shows what the agent is doing (
off/new/all/verbose, cycled with/verbose), then disappears once text starts. - Forum topic routing — in a Telegram forum group, each topic gets its own agent session; messages are routed by
chatId:threadId. - Voice-note input — send a voice note and it's transcribed locally (faster-whisper) and fed to the agent as text.
- Voice replies (TTS) — optionally, replies are also spoken back as a voice message via a command you plug in (
/voice on|off). - Image & document intake — photos are attached as vision input and saved to disk for file tools; documents are downloaded so the agent can read them.
- Inline slash commands — a curated command menu plus the full OMP command surface (builtins, file/skill/extension commands), with an inline-keyboard
/modelpicker (provider → model). - Cron-like scheduler — fire prompts into any chat/topic at fixed times, with day-of-week or one-time dates, timezone-aware, restart-safe.
- One-tap approval gates — the agent can request explicit confirmation for an action; you get Approve ✓ / Cancel ✗ buttons.
- Pause / resume + message queueing —
/pauseholds new messages (current turn still finishes);/resumedrains them in order;/queueinspects what's held. - Allowlist auth — only numeric Telegram user IDs you list can talk to the bot.
- HTML formatting with plain-text fallback — rich formatting when Telegram accepts it, degrading gracefully otherwise.
- Built-in resilience — Telegram flood-wait handling, network retry with backoff on the poll loop, and bot-token redaction in every log line.
- Reboot announce — the bot pings its owners after a restart so a crash or redeploy is visible, not silent.
- Bun (runtime + package manager).
- An installed Oh My Pi (OMP) / Pi Agent SDK (
@oh-my-pi/pi-coding-agent) — this bridge assumes you already have OMP set up and usable. - Optional, for voice notes:
ffmpegonPATH, plus a Python virtualenv withfaster-whisperinstalled (see Voice notes). - Optional, for voice replies: any command-line TTS tool you point
OMP_TG_TTS_CMDat (see Voice replies / TTS);ffmpegis also used here to transcode to Opus/OGG for Telegram.
git clone https://github.com/<you>/pi-telegram-bridge.git
cd pi-telegram-bridge
bun install
# State directory — holds your .env, identity, sessions, schedule, etc.
mkdir -p ~/.omp-telegram
cp .env.example ~/.omp-telegram/.env-
Create a bot — message @BotFather on Telegram, run
/newbot, and copy the token intoTELEGRAM_BOT_TOKENin~/.omp-telegram/.env. -
Find your numeric user ID — start the bridge once with
TELEGRAM_ALLOWED_USER_IDSempty and message the bot; it replies with your user ID (see Security). Alternatively, message any "get my ID" bot (e.g.@userinfobot). -
Set the allowlist — put that ID (comma-separated for multiple people) into
TELEGRAM_ALLOWED_USER_IDSin.env, then restart the bridge. -
Give it an identity (optional but recommended) — copy
examples/identity.example.mdto~/.omp-telegram/SOUL.mdand edit it. This is injected into the agent's system prompt on every session, so it's the place to set tone, persona, and Telegram-specific habits.cp examples/identity.example.md ~/.omp-telegram/SOUL.md
Directly with Bun (loads .env from the current working directory automatically):
cd pi-telegram-bridge
bun run src/bridge.tsOr as a systemd --user service (recommended for anything long-running):
cp systemd/pi-telegram-bridge.service.example ~/.config/systemd/user/pi-telegram-bridge.service
# edit the ExecStart path to your actual clone location, then:
systemctl --user daemon-reload
systemctl --user enable --now pi-telegram-bridge
journalctl --user -u pi-telegram-bridge -fThe systemd unit loads config via EnvironmentFile=%h/.omp-telegram/.env, so keep your real .env there rather than in the repo.
All configuration is environment variables, read once at startup. Set them in ~/.omp-telegram/.env (auto-loaded by Bun when you run from the repo directory) or in the systemd unit's EnvironmentFile.
| Variable | Required | Default | Description |
|---|---|---|---|
TELEGRAM_BOT_TOKEN |
yes | — | Bot token from @BotFather. |
TELEGRAM_ALLOWED_USER_IDS |
yes | — | Comma-separated numeric Telegram user IDs allowed to use the bot. Empty = bootstrap mode (bot replies with your ID but does nothing else). |
OMP_TG_WORKDIR |
no | your home directory | Working directory the agent session runs in (its file-tool root, cwd). |
OMP_TG_MODEL |
no | OMP's default model | Model pattern to select for every session. |
OMP_TG_AUTO_APPROVE |
no | true |
Whether the agent auto-approves its own tool calls (false re-enables OMP's normal approval flow). |
OMP_TG_PROGRESS |
no | off |
Default tool-progress display mode: off, new, all, or verbose. Per-chat, cycled with /verbose. |
OMP_TG_THINKING |
no | OMP default | Reasoning effort for every session: off, minimal, low, medium, high, xhigh, auto. |
OMP_TG_TZ |
no | UTC |
IANA timezone the scheduler evaluates job times in (e.g. America/New_York). |
OMP_TG_STATE_DIR |
no | ~/.omp-telegram |
Directory for .env, SOUL.md, sessions.json, schedule.json, and all other runtime state. |
OMP_TG_BOOT_PING |
no | on |
Whether the bot announces itself to every allowed user after a (re)start. Set off to disable. |
OMP_TG_BOOT_MESSAGE |
no | "Bridge is back online after a reboot." |
Text sent on boot announce. |
OMP_TG_TTS_CMD |
no | unset (TTS off) | Path/command for text-to-speech. Invoked as <cmd> <output.wav> [voice] with the reply text on stdin; must write a WAV file. |
OMP_TG_TTS_VOICE |
no | unset | Optional voice name, passed as OMP_TG_TTS_CMD's second argument. |
OMP_TG_WHISPER_MODEL |
no | small.en |
faster-whisper model name used to transcribe voice notes. |
Telegram's command menu (populated via setMyCommands, capped at 30 entries) is a curated set of bridge + OMP commands, topped up with any of your own discovered commands (file/skill/extension) and then OMP builtins until the menu is full:
| Command | Description |
|---|---|
/new |
Start a fresh session |
/stop (/abort) |
Interrupt the current task now and drop the queue |
/pause |
Hold new messages until /resume (the current turn still finishes) |
/resume |
Resume and process held messages, in order |
/queue [clear] |
Show held + queued messages, or clear both |
/compact [instructions] |
Compact context (keeps the session) |
/autocompact [on|off] |
Toggle automatic compaction |
/model [name] |
Switch model — opens a two-step provider→model picker, or matches a name directly |
/advisor [on|off|status] |
Toggle OMP's second-model reviewer |
/think [level] |
Set reasoning effort: off, minimal, low, medium, high, xhigh, auto |
/verbose |
Cycle tool-progress display: off → new → all → verbose |
/voice [on|off] |
Toggle spoken (TTS) replies for this chat |
/status |
Show session status (model, context, paused/queue state, advisor) |
/usage |
Token usage & cost for this session |
/context |
Context-window breakdown |
/todo |
Show the agent's current task list |
/orchestrate <goal> |
Run a goal in multi-agent orchestration mode (the bare word orchestrate in any message does the same) |
/workflow <goal> |
Author a deterministic multi-subagent workflow for a goal |
/commands |
List every slash command actually available in this session |
/help |
Show usage help |
/id |
Show your Telegram user ID and this chat's ID (works even when unauthorized) |
Every other /command is forwarded exactly like an OMP CLI session would handle it: built-ins with no bridge override (e.g. /export, /mcp), file commands, skill commands, extension commands, and MCP-prompt commands all expand through OMP's own pipeline. An unrecognized /x is delivered to the model verbatim. A small number of TUI-only commands (/plan, /settings, /fork, /retry, …) don't exist over chat — /commands lists what's actually reachable.
Add the bot to a Telegram forum-mode group and it treats each topic as an independent chat: every topic gets its own agent session, keyed by chatId:threadId, with its own history, model, and tool-progress mode. Messages sent in the "General" topic (no message_thread_id) use the chat-level session. This makes a forum a natural way to run several parallel conversations (or scheduled jobs — see below) against one bot.
Voice notes are transcribed locally — audio never leaves your machine. Set up the venv once inside your state directory:
cd ~/.omp-telegram
cp /path/to/pi-telegram-bridge/src/transcribe.py .
python3 -m venv .venv
.venv/bin/pip install -r /path/to/pi-telegram-bridge/examples/requirements.txtThe bridge shells out to ~/.omp-telegram/.venv/bin/python ~/.omp-telegram/transcribe.py <audio-file> (paths derived from OMP_TG_STATE_DIR) for every voice note or audio message it receives, using faster-whisper on CPU with int8 quantization. The model (default small.en, override with OMP_TG_WHISPER_MODEL) is downloaded on first use and cached under ~/.omp-telegram/models/, so the first transcription is slower than the rest. Requires ffmpeg to decode Telegram's Opus/OGG voice format.
Spoken replies are entirely optional and off by default. Set OMP_TG_TTS_CMD to any command that:
- is invoked as
<cmd> <output.wav-path> [voice-name], - receives the reply text on stdin,
- and writes a valid WAV file to the given path (exit code
0on success).
The bridge then transcodes that WAV to Opus/OGG with ffmpeg and sends it as a Telegram voice message. Enable it per chat with /voice on (persisted in state; /voice off to disable again).
A minimal wrapper around a local TTS engine might look like:
#!/usr/bin/env bash
# examples-style wrapper: args are <out.wav> [voice]; text comes in on stdin.
set -euo pipefail
out="$1"
voice="${2:-default}"
text="$(cat)"
your-tts-engine --voice "$voice" --text "$text" --output "$out"Point OMP_TG_TTS_CMD at that script's path, and optionally set OMP_TG_TTS_VOICE to a voice name it understands.
The bridge can inject prompts into any chat (or forum topic) on a schedule — Telegram bots can't poll on their own, so this is how you get proactive/autonomous behavior (morning briefings, reminders, periodic checks). Jobs live in ~/.omp-telegram/schedule.json; see examples/schedule.example.json for a starting point. Each job:
A job fires at most once per matching day; fired state is persisted to schedule-state.json so a restart never double-fires. A job whose time has already passed on the day the bridge first starts is marked fired (not backfilled), so a fresh deploy doesn't spam old jobs.
- Single/small-owner model. This bridge isn't multi-tenant — it grants full agent access (file system, shell, whatever tools your OMP setup exposes) to every ID in
TELEGRAM_ALLOWED_USER_IDS. Only add people you'd trust with a terminal on this machine. - Keep your bot token secret. Anyone with the token can run their own poller against your bot (see 409 Conflict) or message your users as the bot. Treat it like a password; it lives only in
.env/ the systemdEnvironmentFile, never in the repo. - Token redaction. The bridge strips the bot token from every error string before it can reach logs — but a stray
echo/debug of your.envwon't be caught by that, so still keep the file out of version control (see.gitignore). OMP_TG_AUTO_APPROVE. Defaults totrue, meaning the agent approves its own tool calls without asking. Set it tofalseto fall back to OMP's normal approval flow, or rely on the agent's ownAPPROVE:gates (one-tap Approve ✓ / Cancel ✗ buttons) for specific risky actions it flags itself.- Bootstrap mode. With
TELEGRAM_ALLOWED_USER_IDSempty, the bot will tell anyone who messages it their numeric user ID and do nothing else — useful for onboarding, but set the allowlist and restart before leaving it running.
409 Conflictin the logs / poll errors mentioning "Conflict" — another process (a second bridge instance, a webhook, or someone else's poller) is callinggetUpdateswith the same bot token. Telegram allows only one long-poll consumer per token at a time. Stop the other process, or create a separate bot via @BotFather for the second use.- Bot never replies / "Unauthorized." — your numeric Telegram user ID isn't in
TELEGRAM_ALLOWED_USER_IDS. Message the bot with the allowlist empty (or check/idonce you are authorized elsewhere) to find your ID, add it, and restart. TELEGRAM_BOT_TOKEN is not seton startup —.envisn't being loaded. Confirm it's inOMP_TG_STATE_DIR(systemd) or the repo's working directory (bun run), and that the unit'sEnvironmentFilepath is correct.- Voice notes fail to transcribe — confirm
~/.omp-telegram/transcribe.pyand~/.omp-telegram/.venvexist (see Voice notes) and thatffmpegis installed. - Repeated network errors on startup — the poll loop retries with exponential backoff (capped at 30s) and logs each attempt; transient network issues resolve on their own once connectivity returns.
See ROADMAP.md for deferred ideas and how to contribute.
MIT © 2026 pi-telegram-bridge contributors
{ "id": "unique-job-id", // required, stable identifier "enabled": true, // optional, default true "time": "07:30", // required, 24h "HH:MM" in OMP_TG_TZ "days": ["mon", "tue", "wed"], // optional; array of weekday abbrevs, or "daily" (default) "date": "2026-12-25", // optional; one-time job on this exact date, overrides "days" "chatId": 123456789, // required, target chat "threadId": 42, // optional, forum topic id — omit for DM/non-topic chats "prompt": "Give me a quick morning summary.", "silent": false // optional; true runs with tool-progress off and posts nothing on an empty reply }