✨ Features
- 🚀 Async-First — Built on
httpx.AsyncClientfor high-performance async operations, fully compatible with officialzulip.Clientinterface - 📝 Type-Safe — Complete type hints and automatic validation with Pydantic v2 models
- 🎯 Command System — Powerful built-in command parser with type checking, argument validation, and auto-generated help
- 💾 Flexible Storage — Choose between lightweight JSON storage or full SQLAlchemy ORM with Alembic migrations
- 🌐 Internationalization — Built-in i18n support with JSON-based translation files
- 🔧 YAML Configuration — Single source of truth for bot settings in
bot.yaml - 🖥️ Interactive Console — Beautiful Rich-based TUI for managing multiple bots with live logs and command history
- 📦 Production-Ready — Long-polling event loop, automatic reconnection, and error recovery built-in
📦 Installation
From version v0.2.0 and later, the SDK is published to PyPI via an automated GitHub Actions workflow whenever a GitHub release is created.
Option 1: Install from PyPI (recommended for users)
# Using uv (recommended)
uv pip install async-zulip-bot-sdk
# Or using pip directly
pip install async-zulip-bot-sdk
Option 2: Install from source (for development)
git clone https://github.com/Open-LLM-VTuber/async-zulip-bot-sdk.git
cd async-zulip-bot-sdk
# Using uv (recommended)
uv venv
uv pip install -e .
# Or using venv + pip
python -m venv venv
venv\Scripts\activate # Windows
source venv/bin/activate # macOS/Linux
pip install -e .
🚀 Quick Start
⚠️ Breaking change (since v1.0.0): bot configuration now lives in each bot's
bot.yaml. Class-level attributes (e.g.,command_prefixes,enable_storage,enable_orm) are ignored. Set prefixes/mention/help/storage/ORM options in the bot's YAML instead of subclass attributes.
1. Configure Zulip Credentials
Download your zuliprc file:
You can create or regenerate your API Key in Settings - Personal - Account & privacy, enter your password, and select Download zuliprc. Place each bot's file under its own folder, e.g. bots/echo_bot/zuliprc.
2. Configure bots.yaml
Create a bots.yaml file at the root of project, you can refer to bots.yaml.example for details.
Define which bots to launch and where to find them:
bots:
- name: echo_bot
module: bots.echo_bot
class_name: BOT_CLASS
enabled: true
# Optional override; defaults to bots/<name>/zuliprc
# zuliprc: bots/echo_bot/zuliprc
config: {} # optional per-bot config passed to factory (second arg)
3. Configure per-bot settings (bot.yaml)
Create bots/echo_bot/bot.yaml to set prefixes/mentions/help/storage/ORM:
command_prefixes:
- "!"
enable_mention_commands: true
auto_help_command: true
enable_storage: true
# storage_path: bot_data/echo_bot.db
enable_orm: false
# orm_db_path: bot_data/echo_bot.sqlite
language: en
4. Create Your First Bot
import asyncio
from bot_sdk import (
BaseBot,
BotRunner,
Message,
CommandSpec,
CommandArgument,
setup_logging,
)
class MyBot(BaseBot):
def __init__(self, client):
# BaseBot will receive a bot-specific logger when used via BotRunner / console
super().__init__(client)
# Register commands (prefixes come from bot.yaml)
self.command_parser.register_spec(
CommandSpec(
name="echo",
description="Echo back the provided text",
args=[CommandArgument("text", str, required=True, multiple=True)],
handler=self.handle_echo,
)
)
async def on_start(self):
"""Called when bot starts"""
# Prefer structured logging over print
self.logger.info("Bot started! user_id={}", self._user_id)
async def handle_echo(self, invocation, message, bot):
"""Handle echo command"""
text = " ".join(invocation.args.get("text", []))
await self.send_reply(message, f"Echo: {text}")
async def on_message(self, message: Message):
"""Handle non-command messages"""
self.logger.debug("Incoming message: {}", message.content[:50])
await self.send_reply(message, "Try !help to see available commands!")
BOT_CLASS = MyBot
Remember to save this code in a __init__.py file under the directory your configured in bots.yaml.
In this example, you would save it as bots/echo_bot/__init__.py.
5. Run Your Bots
You can use this SDK in your own project directory (not necessarily this repo). A typical layout looks like:
my-zulip-bots/
bots.yaml
bots/
echo_bot/
__init__.py
bot.yaml
zuliprc
Interactive Console
The SDK comes with a built-in interactive console for managing bots, featuring a TUI (Text User Interface) powered by rich.
Run the console (recommended):
# Activate your virtual environment first
# .venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS/Linux
async-zulip-bot # runs in the current project directory
This command looks for bots.yaml and the bots/ package in the current working directory, so run it from your own project root.
Features:
- Rich TUI: Beautiful, split-screen layout for logs, status, and input.
- Command History: Use
Up/Downarrows to navigate previous commands. - Log Scrolling: Use
PageUp/PageDownto scroll through logs. - Bot Management: Run, stop, and reload bots dynamically.
- Tab Completion: Press
Tabto auto-complete commands and bot names.
After entering the interactive console, use the run command to start your bot:
bot-console> run echo_bot
Remember to use tab completion for faster typing!
For more commands, type help in the console.
📚 Core Concepts
AsyncClient
Fully async Zulip API client mirroring the official zulip.Client interface:
from bot_sdk import AsyncClient
async with AsyncClient(config_file="zuliprc") as client:
# Get user profile
profile = await client.get_profile()
# Send messages
await client.send_message({
"type": "stream",
"to": "general",
"topic": "Hello",
"content": "Hello, world!"
})
# Get subscriptions
subs = await client.get_subscriptions()
Command System
Type-safe command definitions with automatic validation:
from bot_sdk import CommandSpec, CommandArgument
# Define commands with arguments
self.command_parser.register_spec(
CommandSpec(
name="greet",
description="Greet a user",
args=[
CommandArgument("name", str, required=True),
CommandArgument("times", int, required=False),
],
handler=self.handle_greet,
)
)
async def handle_greet(self, invocation, message, bot):
name = invocation.args["name"]
times = invocation.args.get("times", 1)
greeting = f"Hello, {name}! " * times
await self.send_reply(message, greeting)
Auto-generated help:
Use !help or !? to automatically show all registered commands and arguments.
Lifecycle Hooks
class MyBot(BaseBot):
async def on_start(self):
"""Called when bot starts"""
pass
async def on_stop(self):
"""Called when bot stops"""
pass
async def on_message(self, message: Message):
"""Called for non-command messages"""
pass
🔧 Advanced Usage
Custom Command Prefixes and Mention Detection
Configuration for prefixes and mention commands is now read from bot.yaml (per-bot YAML config). Class-level attributes are ignored.
command_prefixes:
- "!"
- "/" # Not recommended as '/' is zulip official command prefix
enable_mention_commands: true # Enable @bot to trigger commands
auto_help_command: true # Auto-register built-in help command
Typed Message Models
from bot_sdk import Message, StreamMessageRequest
async def on_message(self, message: Message):
# Full type hints
sender = message.sender_full_name
content = message.content
# Send typed messages
await self.client.send_message(
StreamMessageRequest(
to=message.stream_id,
topic="Reply",
content="Typed reply!"
)
)
📚 Documentation
Comprehensive API documentation is available:
- Online Docs (hosted): https://docs.llmvtuber.com/async-zulip-bot-sdk/
- Source Docs (in this repo): see the
docs/directory (build withmkdocs serve)
Documentation includes:
- 📖 Quick Start Guide
- 🔧 API Reference (AsyncClient, BaseBot, BotRunner)
- 💬 Command System
- 📊 Data Models
- ⚙️ Configuration Management
- 📝 Logging
🤝 Contributing
Contributions are welcome! Feel free to submit Pull Requests.
Contributing Documentation: We welcome documentation contributions in both Chinese and English.
🙏 Credits & Notices
- Portions of bot_sdk/async_zulip.py are adapted from the Zulip upstream client at https://github.com/zulip/python-zulip-api/blob/main/zulip/zulip/__init__.py.
- The upstream project is licensed under Apache-2.0; the original license notice is preserved in the source, and the full text is included as Apache2.0.LICENSE.
- Huge thanks to the Zulip team for their great work and open-source contributions.
📄 License
MIT License - see LICENSE file for details
Made with ❤️ for the Open-LLM-VTuber Zulip team
Metadata
Release files for async-zulip-bot-sdk 1.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| async_zulip_bot_sdk-1.3.0.tar.gz | 51.8 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| async_zulip_bot_sdk-1.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 111.2 kB
Release files / async_zulip_bot_sdk-1.3.0.tar.gz
| Download URL | async_zulip_bot_sdk-1.3.0.tar.gz |
|---|---|
| Size | 51.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
973850d03e63e1f911dab233b0f5bd83a6291f70acb3c9ce5de791a93779c0b9
|
|
BLAKE2b-256 checksum How to use checksums |
efc47a488053e8f4de5e49a16165f176413d1ba29d8dc497572d2cebf368b24f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 10, 2026.
Transparency logRelease files / async_zulip_bot_sdk-1.3.0-py3-none-any.whl
| Download URL | async_zulip_bot_sdk-1.3.0-py3-none-any.whl |
|---|---|
| Size | 59.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
23fe5cede26347c087dfcf1f8e95bd053fe05511509a0bb218c04f013348df5e
|
|
BLAKE2b-256 checksum How to use checksums |
881a1d8283050775a00451257bb97f51b9515b2d7997090e07c88302b918e398
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 10, 2026.
Transparency logSecurity for async-zulip-bot-sdk 1.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for async_zulip_bot_sdk-1.3.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 10, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.