"Knock knock." "Who's there?" "Interrupting Cow." "Interrupting Co —" "MOO."
A one-shot CLI that pings you on Telegram from any script, long-running
task, or Claude Code skill. Stdlib-only
at runtime — no requests, no python-telegram-bot, no transitive
dependency surprises in your .venv.
Mirrored on both GitHub and Codeberg. Issues filed on either are welcome; commits are pushed to both.
The "Claude finished a long-running task — should we ping the operator?" problem comes up constantly:
- A RunPod training run finishes at 3am while you're asleep
- A pod's balance crosses a threshold and you need to decide what to do
- A scheduled deploy hits an unrecoverable error
- A long backup completes (or fails) overnight
A claude -p "..." ping is fine when you're still at the keyboard, but it's invisible if you've stepped away. Telegram works because your phone is always within arm's reach.
InterruptingCow is the smallest possible bridge from "shell command exit code" → "vibration in your pocket." One CLI, two entry points (interruptingcow and moo), zero runtime dependencies.
Prereqs (one-time, ~3 minutes):
- Open Telegram. Search for @BotFather, tap Start, and send
/newbot. Follow the prompts; you'll get a bot token that looks like123456789:ABC-DEF1234ghIkl-zyx57W2v1u123ew11. - Find your chat ID: send your new bot any message (e.g.
/start), then in a browser visithttps://api.telegram.org/bot<YOUR_TOKEN>/getUpdatesand readresult[0].message.chat.id— it's an integer.
Install:
pip install interruptingcowOr from source:
git clone https://github.com/CryptoJones/InterruptingCow.git
cd InterruptingCow
pip install -e .Configure — pick one:
# (a) env vars
export TELEGRAM_BOT_TOKEN='123456789:ABC...'
export TELEGRAM_CHAT_ID='999999999'
# (b) config file
mkdir -p ~/.config/interruptingcow
cat > ~/.config/interruptingcow/config.json <<'EOF'
{ "bot_token": "123456789:ABC...", "chat_id": "999999999" }
EOF
chmod 600 ~/.config/interruptingcow/config.json
# (c) CLI flags (least convenient; usable for tests / one-offs)
moo --bot-token '...' --chat-id '...' "hi"Use it:
moo "training run finished"
moo --severity warning "balance below \$10"
moo --severity critical "POD ON FIRE — terminating now"
moo --silent "weekly cron complete" # delivers without notification sound
long-running-command && moo "build done" || moo --severity critical "build FAILED"
some-script | moo - # body from stdininterruptingcow [text] [--severity LEVEL] [--silent]
[--bot-token TOKEN] [--chat-id ID]
[--config PATH]
| Flag | Default | Description |
|---|---|---|
text |
— | Message body. Use - to read body from stdin. Omit + pipe stdin = same thing. |
--severity |
info |
info → ✅, warning → ⚠️, critical → 🚨. Prefix prepended to the message. |
--silent |
off | Deliver without playing the phone's notification sound. |
--bot-token |
env / config | Override the bot token. |
--chat-id |
env / config | Override the chat id. |
--config |
~/.config/interruptingcow/config.json |
Override config file path. |
--version |
— | Print version + exit. |
--help |
— | The full reference. |
Exit codes: 0 ok, 2 bad usage / missing credentials, 3 Telegram API error.
#!/usr/bin/env bash
set -euo pipefail
trap 'moo --severity critical "$(basename "$0") FAILED at line $LINENO"' ERR
# ... your script ...
moo "$(basename "$0") complete"There's a companion skill that wraps this CLI for triggering by phrases like "ping me on telegram when X finishes." See claude-skill-InterruptingCow.
- Telegram caps a single message at 4096 characters; longer bodies are truncated with
…(truncated)appended. - The Bot API rate limit is ~30 messages/second per bot, ~20/minute per chat for groups. One CLI invocation = one message; you'd have to hammer this in a tight loop to hit anything.
- This is one-way (Claude → human). No incoming-message handling.
Apache 2.0. See LICENSE.
Proudly Made in Nebraska. Go Big Red! 🌽 https://xkcd.com/2347/