Telegram
Send, read, and respond to Telegram messages through a Claudicle Telegram bot. This skill is the thin Claude-facing layer over the python-telegram-bot v22 polling adapter at ~/.claudicle/adapters/telegram/. Send scripts use REST directly (via requests), not asyncio, so they work reliably inside any subprocess or skill invocation.
Portability
This skill is machine-agnostic. It depends only on:
- The Claudicle adapter directory at
~/.claudicle/adapters/telegram/(set up
by the Claudicle framework / the claude-code-minoan distribution).
- A Telegram bot token in
~/.config/env/secrets.env. - Python 3.9+ for the REST-based send/status/callback scripts; Python 3.10+
for the listener.
All paths resolve from Path.home(). No machine-specific hardcoded paths. The Mazkir integration and the launchd deployment notes below are concrete examples of how one operator (Tom) runs the skill — they describe a pattern, not a requirement. The skill is fully usable without any of that.
When to Use This Skill
- Send a Telegram message (text, Markdown, HTML, reply threading)
- Build an approval gate with inline keyboard buttons (tap-to-approve/reject flows)
- Check the shared inbox for unhandled Telegram messages
- Read pending callback queries from tapped buttons
- Answer a callback query (clear the spinner, show a toast to the user)
- Check bot identity and listener health before debugging a failed send
- Load Telegram memory context (working memory, user models, soul state)
- Launch the full cognitive pipeline on unhandled messages via
/telegram-respond
Prerequisites
TELEGRAM_BOT_TOKENin~/.config/env/secrets.envpip install --break-system-packages requests(send/status/callback scripts)pip install --break-system-packages python-telegram-bot(only for the listener)- Python 3.10+ for the listener. Python 3.9 has an asyncio event loop bug
that breaks CallbackQueryHandler in python-telegram-bot v22. On macOS the listener must use /opt/homebrew/bin/python3 (Homebrew Python 3.14), not /usr/bin/python3 (system 3.9). The skill's telegram_send.py, telegram_status.py, and telegram_callback.py use REST instead and work on any Python 3.9+.
- A bot created via @BotFather. Tom's instance runs
@claudicle_bot (ID: 8646522411); yours will differ.
- Your Telegram user ID for DMs, or a group/channel chat ID. Find your own
user ID by DM'ing @userinfobot.
Quick Start
Replace <CHAT_ID> with your Telegram chat or user ID.
# Send a message
python3 ~/.claude/skills/telegram/scripts/telegram_send.py <CHAT_ID> "Hello from Claudicle!"
# Markdown formatting
python3 ~/.claude/skills/telegram/scripts/telegram_send.py <CHAT_ID> "*bold* _italic_" --parse-mode Markdown
# Inline keyboard (approval gate)
python3 ~/.claude/skills/telegram/scripts/telegram_send.py <CHAT_ID> "Approve dispatch?" \
--buttons '[["Approve|approve_day42","Reject|reject_day42"]]'
# Read incoming messages
python3 ~/.claude/skills/telegram/scripts/telegram_check.py
# Read pending button taps
python3 ~/.claude/skills/telegram/scripts/telegram_callback.py --prefix approve:
# Check bot + listener health
python3 ~/.claude/skills/telegram/scripts/telegram_status.py
# Run the full cognitive pipeline on unhandled messages
/telegram-respond
Available Scripts
telegram_send.py — Send Messages
REST-based sender. Supports parse_mode, reply threading, stdin, silent mode, and inline keyboards. Long messages auto-split on newline boundaries; the keyboard attaches to the final chunk so the tap target lands on the most recent message.
python3 telegram_send.py CHAT_ID "text" [OPTIONS]
| Flag | Description | |------|-------------| | --reply-to MSG_ID | Reply to a specific message (thread in groups) | | --parse-mode MODE | Markdown, MarkdownV2, or HTML | | --buttons JSON | Inline keyboard as JSON (see format below) | | --stdin | Read message text from stdin | | --silent | Send without notification sound |
Button format: a list of rows, each row a list of "Label|callback_data" strings. To emit a URL button instead of a callback button, use "Label|url:https://...".
# Two buttons on one row
--buttons '[["Approve|approve_day42","Reject|reject_day42"]]'
# Two rows, one button each (stacked)
--buttons '[["Option A|opt_a"],["Option B|opt_b"]]'
# URL button mixed with callback
--buttons '[["View|url:https://example.com","Dismiss|dismiss"]]'
On success, prints one message_id per line to stdout so callers can capture them for later editing or referencing.
Parse mode fallback: if Telegram rejects the formatting (e.g., unescaped special chars in Markdown), the script retries once as plain text and prints a warning to stderr rather than failing the send.
telegram_check.py — Read Inbox
Reads ~/.claudicle/daemon/inbox.jsonl, filtered to telegram:* channels. Same shape as the SMS and Slack check scripts.
python3 telegram_check.py # Unhandled messages
python3 telegram_check.py --all # Include handled
python3 telegram_check.py --limit 5 # Last N
python3 telegram_check.py --mark-read TS # Mark one handled
python3 telegram_check.py --mark-all-read # Mark all telegram entries handled
telegram_callback.py — Callback Query Reader
Reads entry_type: "telegram_callback" entries from the same inbox. These are written by the listener's CallbackQueryHandler whenever a user taps an inline keyboard button. Supports prefix filtering (useful for namespacing approval flows), marking handled, and answering the callback to clear the spinner.
# All pending button taps
python3 telegram_callback.py
# Only a specific namespace
python3 telegram_callback.py --prefix "approve:"
# Clear the Telegram spinner on a specific tap
python3 telegram_callback.py --answer QUERY_ID "Received — processing."
# Mark handled so it stops showing up
python3 telegram_callback.py --mark-read 1775828391.0
Telegram expects answerCallbackQuery to be called within about 30 seconds of the tap, or the query expires and the spinner sticks. Call --answer promptly after detecting the tap, even if the actual work happens later.
telegram_status.py — Bot and Listener Health
Checks three things in one run: the bot is reachable via getMe, the listener process is alive (PID file + kill -0), and the inbox is being written to. Exits 0 only when everything is healthy.
python3 telegram_status.py # Human-readable
python3 telegram_status.py --json # For scripts
Exit codes: 0 = all good, 1 = bot OK but listener down, 2 = bot unreachable (token missing or network error).
telegram_memory.py — Memory Context CLI
Thin wrapper over ~/.claudicle/adapters/shared/claudicle_memory.py. Gives Claude access to working memory, user models, and soul state for Telegram channels. Used by the /telegram-respond pipeline.
python3 telegram_memory.py load-context <CHAT_ID>
python3 telegram_memory.py user-model tg:<USER_ID>
python3 telegram_memory.py soul-state
Listener Management
The listener lives in the existing Claudicle adapter, not in this skill:
python3 ~/.claudicle/adapters/telegram/telegram_listen.py --bg # Start background
python3 ~/.claudicle/adapters/telegram/telegram_listen.py --status # Check
python3 ~/.claudicle/adapters/telegram/telegram_listen.py --stop # Stop
Optional: Running the Listener Persistently
On macOS the listener can be kept alive by launchd. The Claudicle distribution ships a template plist and startup script; point them at the listener in ~/.claudicle/adapters/telegram/telegram_listen.py. On Linux use systemd-user or your preferred process supervisor.
Two constraints matter regardless of platform:
- The Python interpreter must