Starter X Chat bot powered by Grok and Grok Imagine.
Out of the box it is a Grok pass-through: encrypted DMs in, Grok out. /imagine generates (or edits) an image with Grok Imagine and sends it back as an encrypted Chat attachment.
The X Chat crypto, inbox, activity stream, and media path are already working. Put your product in xchat_bot/handler.py.
inbound ciphertext → Chat XDK decrypt → handler.handle(Message) → Reply
Reply text/image → Chat XDK encrypt → POST /2/chat/conversations/{id}/messages
| File | What it is |
|---|---|
xchat_bot/handler.py |
Customize this. Message in, Reply out. |
xchat_bot/grok.py |
Grok chat completions + Imagine generate/edit |
xchat_bot/bot.py |
Delivery loop (stream, poll, decrypt, send, media) |
xchat_bot/chat.py |
Chat XDK wrapper |
xchat_bot/x_api.py |
X HTTP (/2/chat/*, activity, media) |
xchat_bot/store.py |
SQLite: seen events, cursors, history |
Python 3.10+.
git clone git@github.com:xdevplatform/xchat-agent-skeleton.git
cd xchat-agent-skeleton
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
cp .env.example .envFill in .env:
| Variable | What |
|---|---|
X_ACCESS_TOKEN |
OAuth 2.0 user token for the bot with dm.read dm.write tweet.read users.read media.write |
X_BEARER_TOKEN |
App-only Bearer (inbox discovery via GET /2/activity/stream) |
CHAT_BOT_USER_ID |
Numeric user id of the bot account |
CHAT_PRIVATE_KEYS_B64 |
Chat XDK export_keys blob (recommended) |
CHAT_SIGNING_KEY_VERSION |
Registered public_key_version |
XAI_API_KEY |
xAI key for Grok and Imagine |
Then:
python -m xchat_bot.mainKeep that process running. GET /2/chat/conversations only returns the primary inbox. DMs from people who don't follow the bot sit in Message requests. Set X_BEARER_TOKEN so the activity stream can discover those threads. Until then, put handles in CHAT_PEER_USER_IDS or ask people to follow the bot first.
python -m unittest discover -s testsAnyone who DMs the bot gets a Grok reply, with per-user history.
| User sends | Bot does |
|---|---|
| text | Grok chat, using stored history |
| photo (+ optional caption) | Grok vision |
/imagine a red fox in fog |
Grok Imagine → encrypted image |
photo + /imagine make it a sketch |
Imagine edit against the attached photo |
/reset |
clears that user's Grok history |
Change the voice with GROK_SYSTEM_PROMPT in .env, or edit DEFAULT_SYSTEM_PROMPT in config.py.
handler.py is the product layer. The rest of the process (keys, decrypt, send, media) stays as-is.
from xchat_bot.handler import Message, Reply
class MyBot:
def handle(self, message: Message) -> Reply:
if message.text.lower().startswith("/imagine"):
image = self.grok.imagine(message.text.split(" ", 1)[1])
return Reply("Here you go.", image)
answer = self.grok.chat([
{"role": "system", "content": "You are a travel concierge."},
{"role": "user", "content": message.text},
])
return Reply(answer)Wire it in main.py instead of GrokPassthrough. Reply.text is the DM body. Reply.image (optional bytes) is uploaded as encrypted Chat media.
Bots should use a Chat XDK key blob (CHAT_PRIVATE_KEYS_B64), not a Juicebox PIN. Never commit .env, never log plaintext or key material.