An Intelligrit Labs Project
A comprehensive command-line interface and Go library for the Zulip API. Provides complete scriptable access to messages, streams, users, and all Zulip features with full API coverage.
- ✅ Complete API coverage - Every Zulip endpoint supported
- ✅ Scriptable - JSON output by default, perfect for piping to
jqor other tools - ✅ Real-time events - Listen for messages and events as they happen
- ✅ Type-safe Go library - Use in your own Go applications
- ✅ No config files - Simple environment variable authentication
- ✅ Stream management - Create, update, subscribe, manage topics
- ✅ Message operations - Send, fetch, update, delete, reactions
- ✅ User management - List, create, update, presence tracking
- ✅ File uploads - Upload and manage attachments
go install github.com/intelligrit/zulip-cli/cmd/zulip-cli@latestOr build from source:
git clone https://github.com/intelligrit/zulip-cli.git
cd zulip-cli
go build -o zulip-cli ./cmd/zulip-cliSet up your Zulip credentials as environment variables:
export ZULIP_URL=https://your-org.zulipchat.com
export ZULIP_EMAIL=bot@example.com
export ZULIP_API_KEY=your_api_key_hereYou can get your API key from your Zulip account settings.
For convenience, add to your shell profile:
# ~/.bashrc or ~/.zshrc
export ZULIP_URL=https://your-org.zulipchat.com
export ZULIP_EMAIL=bot@example.com
export ZULIP_API_KEY=your_api_key_here# Send a message to a stream
zulip-cli send-message --stream general --topic "Hello" --content "Hi everyone!"
# Send a direct message
zulip-cli send-message --to user@example.com --content "Private message"
# List all streams
zulip-cli list-streams
# Get recent messages
zulip-cli get-messages --stream general --num-before 10
# Listen for new messages
zulip-cli listen --messages-only# Send a stream message
zulip-cli send-message -s general -t "Announcements" -c "Important update!"
# Send a direct message to multiple users
zulip-cli send-message --to alice@example.com,bob@example.com -c "Meeting at 3pm"
# Get messages from a stream/topic
zulip-cli get-messages --stream engineering --topic "deployments" --num-before 20
# Update a message
zulip-cli update-message 12345 --content "Updated text"
# Change message topic
zulip-cli update-message 12345 --topic "New Topic"
# Delete a message
zulip-cli delete-message 12345
# Add emoji reaction
zulip-cli add-reaction 12345 thumbs_up
# Upload a file
zulip-cli upload-file document.pdf
# Mark all messages as read
zulip-cli mark-all-as-read
# Get message edit history
zulip-cli get-message-history 12345# List all streams
zulip-cli list-streams
# Get stream ID by name
zulip-cli get-stream "general"
# Create a stream
zulip-cli create-stream engineering --description "Engineering discussions"
# Update stream settings
zulip-cli update-stream 42 --description "New description"
# Delete a stream
zulip-cli delete-stream 42
# Subscribe to streams
zulip-cli subscribe engineering design product
# Unsubscribe from streams
zulip-cli unsubscribe random
# List your subscriptions
zulip-cli list-subscriptions
# Mute a topic
zulip-cli mute-topic --stream general --topic "off-topic"
# Move topic to another stream
zulip-cli move-topic --stream-id 42 --new-stream-id 43 --topic "old-name"# List all users
zulip-cli list-users
# Get user details
zulip-cli get-user 123
# Get your own profile
zulip-cli get-profile
# Create a user
zulip-cli create-user user@example.com "Full Name"
# Update user
zulip-cli update-user 123 --full-name "New Name"
# Get user presence
zulip-cli get-user-presence 123
# Update your presence
zulip-cli update-presence active# Get server settings
zulip-cli server-settings
# List custom emoji
zulip-cli list-emoji
# Upload custom emoji
zulip-cli upload-emoji smiley emoji.png
# Manage alert words
zulip-cli list-alert-words
zulip-cli add-alert-words "urgent" "asap" "critical"
# Manage user groups
zulip-cli list-user-groups
zulip-cli create-user-group "Engineering" --description "Engineering team"All commands output JSON by default, making zulip-cli perfect for scripting.
# Pretty print all streams
zulip-cli list-streams | jq
# Pretty print with color
zulip-cli list-users | jq -C# Get just stream names
zulip-cli list-streams | jq -r '.streams[].name'
# Get email addresses of all users
zulip-cli list-users | jq -r '.members[].email'
# Extract message content
zulip-cli get-messages --stream general --num-before 10 | jq -r '.messages[].content'
# Count total messages
zulip-cli get-messages --stream general --num-before 100 | jq '.messages | length'# Filter streams by name pattern
zulip-cli list-streams | jq '.streams[] | select(.name | contains("eng"))'
# Get messages from specific sender
zulip-cli get-messages --stream general --num-before 50 | \
jq '.messages[] | select(.sender_full_name == "Alice")'
# Find messages with reactions
zulip-cli get-messages --stream general --num-before 100 | \
jq '.messages[] | select(.reactions | length > 0)'
# List admin users
zulip-cli list-users | jq '.members[] | select(.is_admin == true) | .full_name'
# Count users by type
zulip-cli list-users | jq 'group_by(.is_bot) | map({bot: .[0].is_bot, count: length})'
# Get topics with recent activity
zulip-cli list-stream-topics 42 | jq '.topics | sort_by(.max_id) | reverse | .[0:5]'# Format messages as CSV
zulip-cli get-messages --stream general --num-before 10 | \
jq -r '.messages[] | [.id, .sender_full_name, .subject, .content] | @csv'
# Create a table of streams
zulip-cli list-streams | \
jq -r '.streams[] | "\(.stream_id)\t\(.name)\t\(.description)"' | column -t
# Export to YAML
zulip-cli get-profile | yq -PSend daily digest:
#!/bin/bash
STREAM="general"
TOPIC="Daily Digest"
DATE=$(date +%Y-%m-%d)
# Get today's messages
MESSAGES=$(zulip-cli get-messages --stream "$STREAM" --num-before 100 | \
jq -r ".messages[] | select(.timestamp > $(date -d 'today' +%s)) | \
\"- [\(.sender_full_name)]: \(.content)\""
)
# Send digest
zulip-cli send-message \
--stream "$STREAM" \
--topic "$TOPIC" \
--content "**Digest for $DATE**\n\n$MESSAGES"Monitor for mentions:
#!/bin/bash
MY_NAME="Alice"
zulip-cli listen --messages-only | jq --unbuffered -r \
"select(.sender_full_name != \"$MY_NAME\" and (.content | contains(\"@$MY_NAME\"))) | \
\"[MENTION] \(.sender_full_name) in #\(.display_recipient)/\(.subject): \(.content)\""Bulk subscribe users:
#!/bin/bash
STREAM="announcements"
# Get all active user emails
USER_EMAILS=$(zulip-cli list-users | \
jq -r '.members[] | select(.is_active == true and .is_bot == false) | .email')
# Subscribe them all
for email in $USER_EMAILS; do
echo "Subscribing $email to $STREAM"
zulip-cli subscribe "$STREAM" --principals "$email"
doneGenerate message statistics:
#!/bin/bash
STREAM="general"
MESSAGES=$(zulip-cli get-messages --stream "$STREAM" --num-before 500)
echo "Message Statistics for #$STREAM"
echo "================================"
echo -n "Total messages: "
echo "$MESSAGES" | jq '.messages | length'
echo -n "Unique senders: "
echo "$MESSAGES" | jq '[.messages[].sender_full_name] | unique | length'
echo "Top 5 senders:"
echo "$MESSAGES" | jq -r '[.messages | group_by(.sender_full_name) | .[] |
{sender: .[0].sender_full_name, count: length}] |
sort_by(.count) | reverse | .[0:5] | .[] |
" \(.sender): \(.count) messages"'Watch for keywords:
#!/bin/bash
KEYWORDS=("urgent" "critical" "help")
zulip-cli listen --messages-only | jq --unbuffered -r \
"select(.content | ascii_downcase | test(\"$(IFS='|'; echo "${KEYWORDS[*]}")\")) | \
\"[ALERT] \(.sender_full_name): \(.content)\"" | \
while read -r alert; do
echo "$alert"
osascript -e "display notification \"$alert\" with title \"Zulip Alert\""
donego get github.com/intelligrit/zulip-clipackage main
import (
"fmt"
"log"
"github.com/intelligrit/zulip-cli/client"
"github.com/intelligrit/zulip-cli/types"
)
func main() {
// Create client (uses environment variables)
c, err := client.NewClient()
if err != nil {
log.Fatal(err)
}
// Send a message
resp, err := c.SendMessage(client.SendMessageRequest{
Type: "stream",
To: "general",
Topic: "Hello",
Content: "Hi from Go!",
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Message sent! ID: %d\n", resp.ID)
// Get messages
messages, err := c.GetMessages(client.GetMessagesRequest{
Anchor: "newest",
NumBefore: 10,
NumAfter: 0,
Narrow: []types.Narrow{
{Operator: "stream", Operand: "general"},
},
})
if err != nil {
log.Fatal(err)
}
for _, msg := range messages.Messages {
fmt.Printf("[%s] %s: %s\n", msg.Subject, msg.SenderFullName, msg.Content)
}
}config := client.Config{
URL: "https://your-org.zulipchat.com",
Email: "bot@example.com",
APIKey: "your_api_key",
Verbose: true, // Enable debug output
Insecure: false, // Set true to skip TLS verification (not recommended)
}
c, err := client.NewClientWithConfig(config)// Listen for all events
err := c.CallOnEachEvent(func(event map[string]interface{}) {
fmt.Printf("Event: %v\n", event)
}, []string{"message", "reaction"}, nil)
// Listen for messages only
err := c.CallOnEachMessage(func(msg types.Message) {
fmt.Printf("Message from %s: %s\n", msg.SenderFullName, msg.Content)
})zulip-cli/
├── client/ # Go client library
│ ├── client.go # Core client & auth
│ ├── messages.go # Message operations
│ ├── streams.go # Stream operations
│ ├── users.go # User operations
│ ├── groups.go # User group operations
│ ├── realm.go # Realm/emoji/filters
│ ├── events.go # Event streaming
│ └── storage.go # Bot storage
├── types/ # Type definitions
│ └── types.go # All API types
├── cmd/zulip-cli/ # CLI application
│ ├── main.go
│ └── commands/ # CLI commands
│ ├── root.go
│ ├── messages.go
│ ├── streams.go
│ ├── users.go
│ ├── groups.go
│ └── misc.go
├── LICENSE
├── README.md
└── go.mod
- Go 1.23 or higher
- Access to a Zulip server for testing
go build -o zulip-cli ./cmd/zulip-cli# Set up test environment
export ZULIP_URL=https://your-test-org.zulipchat.com
export ZULIP_EMAIL=test-bot@example.com
export ZULIP_API_KEY=your_test_api_key
# Test basic commands
./zulip-cli server-settings
./zulip-cli get-profile
./zulip-cli list-streams
# Run Go tests
go test ./...The library provides complete coverage of the Zulip API including:
- Messages - Send, fetch, update, delete, reactions, flags, rendering
- Streams - Create, update, delete, subscribe, topics, email addresses
- Users - List, create, update, deactivate, presence, alert words
- User Groups - Create, update, delete, manage members
- Emoji - List, upload, delete custom emoji
- Realm - Linkifiers, profile fields, server settings
- Events - Real-time event streaming and message listening
- Files - Upload and manage attachments
- YAML output format support
- Table output format for better CLI readability
- Configuration file support (optional)
- Shell completion scripts
- Webhooks/outgoing webhooks support
- Message drafts management
- Typing indicators
- Read receipts
- OPML export for subscriptions
zulip-cli is developed by Intelligrit Labs, the R&D arm of Intelligrit LLC. We build tools for ourselves and release them for everyone. Intelligrit delivers AI-driven IT modernization for federal agencies.
MIT License - see LICENSE file for details
Contributions are welcome! This project follows standard Go conventions.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes following Go best practices
- Write tests for new functionality
- Ensure all tests pass (
go test ./...) - Run
go fmtandgo vet - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Follow standard Go formatting (
gofmt,go vet) - Write clear, descriptive commit messages
- Add comments for exported functions and types
- Keep functions focused and modular
For issues, questions, or contributions, please open an issue on GitHub.
- Built with Cobra CLI framework
- Complete reimplementation of the python-zulip-api library in Go
- Thanks to the Zulip team for their excellent API documentation
