skills/telegram/SKILL.md
Telegram bot setup, one-way send/notify, and two-way listener that pipes incoming messages to a Claude Code session. Subcommands - setup (walks BotFather and captures bot token + chat_id), send (one shot message), notify (end-of-run ping with the same firing rule as /ro:pushover), listen (long-poll loop that runs `claude -p` per incoming message in a configured working dir and replies with the output). Use when Ronan asks to "set up a telegram bot", "telegram me when done", "ping me on telegram", "start the telegram listener", "let me query claude from my phone", or any /ro:telegram subcommand. Reads TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID, TELEGRAM_LISTEN_CWD from ~/.claude/.env.
npx skillsauth add RonanCodes/ronan-skills telegramInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Talk to a personal Telegram bot from anywhere. Send pings out, receive messages in, dispatch incoming messages to claude -p headless and reply with the output.
Sister skill to /ro:pushover. Pushover is one-way push only. Telegram is bidirectional and lets you query the agent while you are away from the laptop.
/ro:telegram setup # one-time bot creation, captures token + chat_id
/ro:telegram send <msg> # raw send, no envelope
/ro:telegram send-photo <path> # send an image (auto-falls back to sendDocument on dimension limit)
/ro:telegram notify <msg> [...] # end-of-run ping with title (mirrors pushover)
/ro:telegram listen # foreground long-poll loop
/ro:telegram listen --daemon # install launchd plist, run on login (mac)
/ro:telegram listen --stop # tear down the daemon
All four wrap shell scripts in scripts/. The skill itself is mostly orchestration plus the firing rule below.
notifySame trigger list as /ro:pushover. Fire BOTH in sequence at end of an AFK run so Ronan gets the redundant signal across both channels while we are still figuring out which one he prefers.
Fire when ANY of these are true for the current session:
AFK, go AFK, afk run, I'm AFK.night shift, kick off night shift, run night shift./ro:matt-pocock-coding-workflow was invoked in AFK / night-shift mode./ro:ralph, /ro:planner-worker, /ro:swarm, or /agentic-e2e-flow was invoked against a real backlog (not --plan-only, not --mode single)./loop was invoked with NO interval (self-paced).ping me, notify me when done, let me know when finished.Do NOT fire on ordinary interactive turns. Notifications only have signal if they are rare.
Skill walks you through it. The flow:
@BotFather./newbot, pick a display name (e.g. Ronan Claude Bot) and a unique username ending in bot (e.g. ronan_claude_bot).TELEGRAM_BOT_TOKEN to ~/.claude/.env and starts polling /getUpdates.chat_id, writes TELEGRAM_CHAT_ID, sends a confirmation back via sendMessage. Done.Run it:
bash skills/telegram/scripts/setup.sh
To re-run setup (e.g. new bot), delete the existing keys from ~/.claude/.env first.
bash skills/telegram/scripts/send.sh "hello from claude"
bash skills/telegram/scripts/notify.sh "night shift done - 7 stories merged" \
--title "Night shift"
notify.sh is a thin wrapper that prefixes the title in bold (*Title*\n\nmessage) and uses Markdown parse mode. send.sh is plain text.
bash skills/telegram/scripts/send-photo.sh ~/Pictures/foo.jpg --caption "screenshot"
bash skills/telegram/scripts/send-photo.sh https://example.com/img.png
bash skills/telegram/scripts/send-photo.sh ~/big.jpg --as-file # force sendDocument
Telegram's sendPhoto compresses and caps at ~10000px combined width+height. The script auto-detects PHOTO_INVALID_DIMENSIONS / oversize errors and retries via sendDocument, which sends the file uncompressed with no dimension cap (50 MB limit). Use --as-file to skip the photo attempt entirely.
Message envelope rules - keep it scannable, three things in order:
7 stories merged, failed on story #4).ready for review, needs your call on auth approach).Examples that work:
night shift done - 7 stories merged, 0 failed, ready for reviewralph loop paused after story 4 - needs your call on the auth approachloop finished - 12 PRs drained, queue emptyForeground long-poll loop. For every incoming message from TELEGRAM_CHAT_ID:
claude -p "<message>" in TELEGRAM_LISTEN_CWD (env var, default $HOME/Dev).# foreground (testing / one session)
bash skills/telegram/scripts/listen.sh
# point at a specific working dir for this run
bash skills/telegram/scripts/listen.sh --cwd ~/Dev/ai-projects/llm-wiki
# daemon mode (macOS launchd, persists across reboot while laptop is awake)
bash skills/telegram/scripts/listen.sh --daemon
# stop the daemon
bash skills/telegram/scripts/listen.sh --stop
Two layers:
chat_id other than TELEGRAM_CHAT_ID. Anyone who knows the bot username can DM it; without this check every random message becomes a Claude Code invocation in your working tree. Do not disable.claude -p because launchd has no TTY to prompt for tool-call approvals. Without it, every Read / Bash / Edit hangs or errors and the bot is effectively text-completion-only. With it, the bot can do anything Claude Code can do as you, in TELEGRAM_LISTEN_CWD. The chat_id allowlist is what stops random Telegram users from exploiting that.If you ever lose your phone or Telegram account is compromised, bash skills/telegram/scripts/listen.sh --stop to take the daemon down, then revoke the bot in BotFather (/revoke).
If you need multi-user later, change TELEGRAM_CHAT_ID to a comma list and update the filter in listen.sh. To make the bot read-only, remove --dangerously-skip-permissions from the dispatch line in listen.sh.
Telegram caps sendMessage at 4096 chars. listen.sh splits long replies into multiple messages, suffixed (part N/M).
The dispatch shape:
cd "$TELEGRAM_LISTEN_CWD"
claude -p --session-id "$SESSION_UUID" "$INCOMING_MESSAGE"
Conversational continuity. The listener keeps a "current session UUID" in ~/.claude/telegram-session.json and passes it via --session-id on every call. Same UUID twice means claude resumes the prior conversation, so a back-and-forth on Telegram shares one thread instead of starting cold every message.
Session rotation rules:
/new (or /reset) in Telegram to force a fresh session UUID. Bot confirms with new session started.TELEGRAM_SESSION_IDLE_HOURS (default 6), the next message starts a fresh UUID. Prevents unbounded context growth on neglected threads.Other commands the listener understands:
/new or /reset - rotate to a fresh session./cwd - reply with the current TELEGRAM_LISTEN_CWD.!<shell command> - run raw shell instead of dispatching to claude. e.g. !git status. Output goes back as a reply.State file shape:
{
"session_id": "5a8e3b7c-...",
"started_at": "2026-05-14T10:00:00Z",
"last_message_at": "2026-05-14T10:42:11Z",
"message_count": 7
}
v1 of this skill ships with launchd-daemon support, so the listener auto-starts when you log in and runs as long as your laptop is awake.
v2 (separate work) ports the listener to the factory Pi substrate so the bot is reachable when the laptop is closed. That ties into the existing factory design (Pi + CF Sandbox + GH issues intake). When that lands, this skill points its --daemon flag at the Pi instead of installing launchd locally.
Until v2: laptop must be awake for the listener to respond. The launchd plist sets KeepAlive so it auto-restarts on crash.
~/.claude/.env)# --- telegram ---
TELEGRAM_BOT_TOKEN=123456:ABC-defGhIjk...
TELEGRAM_CHAT_ID=1234567890
TELEGRAM_LISTEN_CWD=/Users/ronan/Dev
setup.sh writes the first two. TELEGRAM_LISTEN_CWD is optional; defaults to $HOME/Dev if absent.
TELEGRAM_BOT_TOKEN not set - run /ro:telegram setup.TELEGRAM_CHAT_ID not set - same.Unauthorized from Telegram API - token is wrong or bot was deleted in BotFather.Forbidden: bot was blocked by the user - you blocked the bot in Telegram. Unblock and resend.claude: command not found in listener output - Claude Code CLI is not on PATH for the launchd context. Fix in the plist EnvironmentVariables block.The trigger rule for notify lives in ~/CLAUDE.md under "Phone Notifications", loaded into every session. That global rule tells Claude when to fire; this SKILL.md describes how. Same pattern as /ro:pushover.
development
Close the loop on a Linear ticket when its work ships - move the status and post a deploy comment with the PR link, what shipped, and a try-it link, mentioning the collaborator. Used as the tail of /ro:linear-nightshift for every merged mirror, or manually after an ad-hoc build. Triggers on "linear update", "update the linear ticket", "mark NUT-x done", "tell eoin it shipped", "/ro:linear-update".
devops
Run a night-shift against a collaborator's Linear board. Pulls the team's Grilled tickets (/ro:linear-grill moves a ticket to Grilled once its questions are answered), VERIFIES the questions were actually answered (unanswered → bounce the ticket to the "Question for <name>" state), mirrors verified tickets to ephemeral GitHub issues with ready-for-agent, then runs the standard /ro:night-shift machinery on GitHub. Tail-calls /ro:linear-update for everything that merged + deployed. Triggers on "linear nightshift", "nightshift linear", "drain the linear board", "run the shift off linear", "/ro:linear-nightshift".
development
Grill a collaborator's Linear tickets and move every processed ticket to where it belongs. Resolves the board from the repo's .ro-linear.json, reads the collaborator's Backlog / Ready-for-agent issues, then per ticket either posts 3-5 decision-extracting questions (state moves to "Question for <name>") or confirms it build-ready (state moves to "Grilled", the gate /ro:linear-nightshift consumes); shipped-and-confirmed tickets close as Done. The async-collaborator counterpart of /ro:day-shift for people who never touch GitHub. Triggers on "grill linear", "grill eoin's tickets", "linear grill", "add questions to the linear tickets", "/ro:linear-grill".
development
--- name: about-page description: Add a standard About page to any web app, what it is, the tech stack, and an FAQ, wired into a footer link with a sticky footer. Built with Spartan + Tailwind (the canonical component layer) and falls back to semantic HTML so it ships reliably. Use whenever building, polishing, or shipping an app, every app should have one. Triggers on "add an about page", "about page", "footer about link", or as a standard step in app build/polish. category: frontend argument-h