On this page

Wire ships with a Telegram bot so you can run commands from anywhere. Ask for an audit during a customer meeting. Trigger refine while on a train. Rebuild the site from your couch. The bot wraps Wire's CLI behind a chat interface that reads your messages, runs the matching Wire command, and sends the output back as a Telegram reply.

The source lives in bot/telegram_bot.py in the newsroom repository. This page covers setup for self-hosted customers. If you use the Wire managed service, we run the bot for you, so skip to How it works and contact us to onboard.

Two different bot/ things

Wire has two unrelated features whose names are similar. This page is about one of them. The customer call that led to this page (#411) was filed after someone confused them.

bot/ directory in the newsroom repo. The Telegram bot source code: telegram_bot.py, the systemd service units, and the clients.yml site registry. This is what this page documents. You run it on a VPS.

extra.wire.bot_dir: true in wire.yml. A separate, unrelated feature that generates a /bot/ URL on your rendered site for AI agents to read your styleguide and topic list. It has nothing to do with Telegram. See the configuration reference for that option.

Do not enable extra.wire.bot_dir to run the Telegram bot. You do not need it.

How it works

Telegram message
  -> telegram_bot.py (long polling, systemd service)
    -> Quick command? (/status, /sites, /help) -> direct response
    -> Everything else -> claude --print with system prompt
      -> Claude reads clients.yml, runs Wire CLI
      -> "Wire wired!" + response sent back to chat

The bot reads bot/clients.yml on every Claude invocation, so adding a new site is a YAML edit plus a git pull on the VPS. No restart needed for client changes.

Commands that work from chat:

  • /status shows systemd health, disk space, and the last build result
  • /sites lists configured Wire sites
  • /help prints the quick-command list
  • Plain English messages go through Claude, which picks the right CLI command and runs it

A message like "audit the products topic on wise-relations" becomes cd /opt/wire/sites/wise-relations.com && python -m wire.chief audit products under the hood. Claude reads the reply, summarizes the interesting bits, and sends them back.

Prerequisites

  • A VPS. We use Hetzner CPX22 running Ubuntu 22.04. Any Linux server with systemd works.
  • Python 3.13 and a clone of the newsroom repo installed via pip install -e ".[dev]"
  • ANTHROPIC_API_KEY in your environment, or a logged-in Claude CLI (the bot spawns Claude as a subprocess)
  • A Telegram account so you can talk to @BotFather
  • Root or sudo access to install the systemd unit

If you do not yet run Wire locally, do that first. See Getting Started for the basic install and first build.

Setup walkthrough

Create the bot with BotFather

Open @BotFather in Telegram. Send /newbot, pick a name, pick a username ending in bot. BotFather replies with a token that looks like 123456:ABCDEF.... Copy it.

Then send /mybots, pick your new bot, open Bot Settings, then Group Privacy, and turn privacy off. Without this step, bots inside Telegram groups only see messages that start with a slash command. The bot will appear dead for everything else.

If you toggle privacy after the bot is already in a group, remove the bot from the group and re-add it. Telegram caches the old setting until a fresh join.

Find your chat ID

Create a private Telegram group just for this bot. Add the bot and make it an admin. Send any message in the group. Then from any shell:

curl "https://api.telegram.org/bot<TOKEN>/getUpdates" | python3 -m json.tool

Find the "chat": {"id": -1003...} field in the output. That negative number is your chat ID. Copy it.

If the group becomes a supergroup later (happens automatically when the group turns public or hits certain size thresholds), the chat ID changes completely. Old messages keep working but new ones silently fail. Re-run getUpdates and update your .env.

Write the .env file

On the VPS, create /opt/wire/.env:

TELEGRAM_BOT_TOKEN=123456:ABCDEF...
TELEGRAM_CHAT_ID=-1003830229038
BOT_ALLOWED_CHATS=-1003830229038
ANTHROPIC_API_KEY=sk-ant-...

BOT_ALLOWED_CHATS is a comma-separated whitelist of chat IDs the bot accepts commands from. Any message from a chat not on the list is silently ignored. This is the primary security boundary. If a stranger finds your bot username and messages it, the bot does nothing.

Lock the file down: chmod 600 /opt/wire/.env.

Register your sites

Edit bot/clients.yml in the newsroom repo:

clients:
  - name: mysite.com
    path: /opt/wire/sites/mysite.com
    platform: wire
    deploy: github-pages
    url: https://mysite.com

Commit, push, git pull on the VPS. The bot reads this file on every Claude call, so no restart is needed.

Install the systemd service

sudo cp /opt/wire/newsroom/bot/wire-operator.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable wire-operator
sudo systemctl start wire-operator
sudo systemctl status wire-operator

Verify the bot replies by sending /status in Telegram. You should see server health and the configured site count within a few seconds. If nothing comes back, check sudo journalctl -u wire-operator -n 50 --no-pager.

Running multiple bots on one VPS

The wire-bot@.service template lets one VPS host one bot per customer. Each instance reads its own /opt/wire/bots/<name>/.env and /opt/wire/bots/<name>/clients.yml, so customer A never sees customer B's sites or chat messages. The full runbook including SSH deploy keys for external customer repos lives in bot/README.md on GitHub.

Security model

The bot has a single boundary: BOT_ALLOWED_CHATS. Messages from any chat not on that list are dropped without a reply. There is no user-level auth inside an allowed chat, so if you add the bot to a group, everyone in the group can trigger Wire commands.

Run the bot from a private group that only you and trusted team members are in. Never add the bot to a public channel.

The bot also enforces Wire's own fail-loud gates. A chat message that asks Wire to do something unsafe, like running deduplicate without fresh GSC data, still gets refused at the command layer. The bot is not a second safety net, it is a second interface to the same safety net.

Where to go next