discli is a standard Unix CLI. It reads tokens from environment variables, outputs structured JSON, and exits with meaningful codes. This makes it a natural fit for shell scripts, cron jobs, and CI/CD pipelines.
Bash One-Liners
Quick operations you can run directly from your terminal or paste into a script.
discli message send "#general" "Hello from the terminal!"discli --json channel list --server "My Server" | jq -r '.[].name'discli --json member list "My Server" --limit 1000 | jq 'length'discli --json message search "#general" "" --author "alice#1234" --after "$(date -u +%Y-%m-%d)" | jq 'length'discli --json message history "#general" --days 7 | jq -r '.[] | [.timestamp, .author, .content] | @csv' > history.csvLATEST=$(discli --json message list "#general" --limit 1 | jq -r '.[0].id')discli reaction add "#general" "$LATEST" "👍"Channel Logging Script
Record all messages from a channel to a file, with automatic rotation.
#!/usr/bin/env bashset -euo pipefail
CHANNEL="${1:?Usage: $0 <channel> [log_dir]}"LOG_DIR="${2:-./logs}"mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/$(date -u +%Y-%m-%d).jsonl"
echo "Logging $CHANNEL to $LOG_FILE (Ctrl+C to stop)" >&2
discli --json listen --channel "$CHANNEL" --events messages >> "$LOG_FILE"Usage:
# Log #support to ./logs/./channel-logger.sh "#support"
# Log to a custom directory./channel-logger.sh "#support" /var/log/discord
# Run in backgroundnohup ./channel-logger.sh "#support" /var/log/discord &The log file rotates daily by date. Each line is a complete JSON object, so you can process it with jq, import it into a database, or pipe it to a log aggregator.
Analyzing Logged Data
# Messages per author todaycat logs/$(date -u +%Y-%m-%d).jsonl | jq -r '.author' | sort | uniq -c | sort -rn
# Find messages containing "error"cat logs/*.jsonl | jq -r 'select(.content | test("error"; "i")) | "\(.timestamp) \(.author): \(.content)"'
# Count messages per hourcat logs/$(date -u +%Y-%m-%d).jsonl | jq -r '.timestamp[:13]' | sort | uniq -cReaction-Based Polls
Create a simple poll by posting a message and adding reaction options.
#!/usr/bin/env bashset -euo pipefail
CHANNEL="${1:?Usage: $0 <channel> <question> <emoji1> <emoji2> [emoji3...]}"QUESTION="${2:?}"shift 2EMOJIS=("$@")
if [ ${#EMOJIS[@]} -lt 2 ]; then echo "Need at least 2 emoji options" >&2 exit 1fi
# Build the poll messageMESSAGE="📊 **Poll:** $QUESTION\n\n"for emoji in "${EMOJIS[@]}"; do MESSAGE+="$emoji — React to vote\n"done
# Send the message and capture the IDMSG_ID=$(discli --json message send "$CHANNEL" "$(echo -e "$MESSAGE")" | jq -r '.id')
if [ -z "$MSG_ID" ] || [ "$MSG_ID" = "null" ]; then echo "Failed to send poll message" >&2 exit 1fi
# Add reaction optionsfor emoji in "${EMOJIS[@]}"; do discli reaction add "$CHANNEL" "$MSG_ID" "$emoji" sleep 0.5 # Avoid rate limitsdone
echo "Poll created! Message ID: $MSG_ID"Usage:
./reaction-poll.sh "#general" "Best programming language?" "🐍" "🦀" "🟨"Cron Job Examples
Schedule Discord operations to run on a timer.
Daily Channel Summary
#!/usr/bin/env bashset -euo pipefail
CHANNEL="#daily-summary"SOURCE="#general"
# Get yesterday's message countYESTERDAY=$(date -u -d "yesterday" +%Y-%m-%d 2>/dev/null || date -u -v-1d +%Y-%m-%d)COUNT=$(discli --json message history "$SOURCE" --days 1 | jq 'length')
# Get top authorsTOP_AUTHORS=$(discli --json message history "$SOURCE" --days 1 \ | jq -r '[group_by(.author) | .[] | {author: .[0].author, count: length}] | sort_by(-.count) | .[:5] | .[] | " \(.author): \(.count) messages"')
discli message send "$CHANNEL" "📊 **Daily Summary for #general** ($YESTERDAY)
Messages: $COUNTTop contributors:$TOP_AUTHORS"Crontab entry (run at 9 AM UTC daily):
0 9 * * * /path/to/daily-summary.shScheduled Announcements
# Every Monday at 9 AM: team standup reminder0 9 * * 1 discli message send "#engineering" "🔔 Time for standup! What did you work on last week?"
# First of every month: billing reminder0 10 1 * * discli message send "#billing" "📅 Monthly billing cycle starts today. Check your dashboards."
# Every hour during business hours: status check0 9-17 * * 1-5 discli --json server list | jq -r '.[].name' | while read server; do echo "$(date): $server online"; done >> /var/log/discord-status.logCron jobs run with a minimal environment. Make sure discli is in the PATH or use the full path. Set DISCORD_BOT_TOKEN in the crontab or use ~/.discli/config.json for token resolution.
Cron Environment Setup
# Option 1: Set token in crontabDISCORD_BOT_TOKEN=your-token-here0 9 * * * /path/to/daily-summary.sh
# Option 2: Source a token file0 9 * * * . /path/to/.env && /path/to/daily-summary.sh
# Option 3: Use config.json (no env needed)# Ensure ~/.discli/config.json has {"token": "your-token"}0 9 * * * /usr/local/bin/discli message send "#general" "Good morning!"GitHub Actions: Deploy Notifications
Send Discord notifications from your CI/CD pipeline.
name: Deploy and Notify
on: push: branches: [main]
jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Deploy run: | # Your deployment steps here echo "Deploying..."
- name: Install discli run: pip install discord-cli-agent
- name: Notify Discord env: DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }} run: | COMMIT_MSG=$(git log -1 --pretty=%s) COMMIT_AUTHOR=$(git log -1 --pretty=%an) COMMIT_SHA=$(git log -1 --pretty=%h)
discli message send "#deploys" "🚀 **Deployed to production**
Commit: \`$COMMIT_SHA\` — $COMMIT_MSG Author: $COMMIT_AUTHOR Branch: ${{ github.ref_name }} Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"Deployment Status with Embeds
- name: Notify Discord (with embed) env: DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }} run: | discli message send "#deploys" "Deployment complete" \ --embed-title "v$(cat VERSION) deployed" \ --embed-desc "All checks passed. [View run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"Notify on Failure
- name: Notify failure if: failure() env: DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }} run: | discli message send "#deploys" "❌ **Deployment failed**
Branch: ${{ github.ref_name }} Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} Please investigate."JSON + jq Patterns
Message Analytics
# Messages per day over the last weekdiscli --json message history "#general" --days 7 \ | jq 'group_by(.timestamp[:10]) | map({date: .[0].timestamp[:10], count: length})'
# Average message lengthdiscli --json message list "#general" --limit 100 \ | jq '[.[].content | length] | add / length'
# Most used words (top 20)discli --json message list "#general" --limit 500 \ | jq -r '.[].content' \ | tr '[:upper:]' '[:lower:]' | tr -cs '[:alpha:]' '\n' \ | sort | uniq -c | sort -rn | head -20Server Health Check
#!/usr/bin/env bashset -euo pipefail
STATUS_CHANNEL="#bot-status"
# Check if bot can connect and list serversSERVERS=$(discli --json server list 2>&1) || { echo "CRITICAL: Cannot connect to Discord" >&2 exit 1}
SERVER_COUNT=$(echo "$SERVERS" | jq 'length')TOTAL_MEMBERS=$(echo "$SERVERS" | jq '[.[].member_count] | add')
discli message send "$STATUS_CHANNEL" "✅ **Health Check Passed**Servers: $SERVER_COUNTTotal members: $TOTAL_MEMBERSTimestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)"Bulk Operations
# Send a message to all text channels in a serverdiscli --json channel list --server "My Server" \ | jq -r '.[] | select(.type == "text") | .id' \ | while read channel_id; do discli message send "$channel_id" "Important announcement: Server maintenance tonight at 2 AM UTC" sleep 1 # Rate limit friendly done
# Export all channels' recent historydiscli --json channel list --server "My Server" \ | jq -r '.[] | select(.type == "text") | "\(.id) \(.name)"' \ | while read channel_id channel_name; do echo "Exporting #$channel_name..." >&2 discli --json message list "$channel_id" --limit 100 > "export-${channel_name}.json" sleep 2 doneError Handling in Scripts
Exit Codes
discli exits with 0 on success and non-zero on failure. Use standard bash error handling:
#!/usr/bin/env bashset -euo pipefail # Exit on error, undefined vars, pipe failures
# Method 1: set -e (script exits on first error)discli message send "#general" "Hello"
# Method 2: Explicit error checkingif ! discli message send "#general" "Hello" 2>/dev/null; then echo "Failed to send message" >&2 # Fallback or alertfi
# Method 3: Capture output and checkOUTPUT=$(discli --json message send "#general" "Hello" 2>&1) || { echo "Send failed: $OUTPUT" >&2 exit 1}MSG_ID=$(echo "$OUTPUT" | jq -r '.id')echo "Sent message $MSG_ID"Retry Logic
#!/usr/bin/env bash
send_with_retry() { local channel="$1" local message="$2" local max_attempts="${3:-3}" local attempt=1
while [ $attempt -le $max_attempts ]; do if discli message send "$channel" "$message" 2>/dev/null; then return 0 fi echo "Attempt $attempt/$max_attempts failed. Retrying in ${attempt}s..." >&2 sleep "$attempt" attempt=$((attempt + 1)) done
echo "Failed after $max_attempts attempts" >&2 return 1}
# Usagesend_with_retry "#deploys" "Deployment complete" 3Token Validation
# Quick check that the token worksif ! discli server list >/dev/null 2>&1; then echo "ERROR: Discord token is invalid or bot is not in any servers" >&2 echo "Set DISCORD_BOT_TOKEN or configure via: discli config set token YOUR_TOKEN" >&2 exit 1fiCombining with Other Tools
Pipe webhook data to Discord
# Forward a GitHub webhook payload to Discordcurl -s "https://api.github.com/repos/owner/repo/releases/latest" \ | jq -r '"New release: \(.tag_name) — \(.html_url)"' \ | xargs -I{} discli message send "#releases" "{}"Monitor a log file
# Watch a log file and forward errors to Discordtail -F /var/log/app/error.log | while read line; do discli message send "#alerts" "🚨 Error: $line" sleep 1 # Rate limitdoneDatabase query results to Discord
# Send query results as a formatted messageRESULT=$(psql -t -c "SELECT count(*) FROM users WHERE created_at > now() - interval '1 day'")discli message send "#metrics" "📊 New users today: **$RESULT**"Security in Scripts
Never hardcode tokens in scripts. Use environment variables, secret managers, or ~/.discli/config.json.
# Bad: token in scriptdiscli --token "mfa.abc123..." message send "#general" "Hello"
# Good: environment variableexport DISCORD_BOT_TOKEN="$VAULT_TOKEN"discli message send "#general" "Hello"
# Good: config file (set once)discli config set token "$VAULT_TOKEN"discli message send "#general" "Hello"For CI/CD, always use encrypted secrets:
env: DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }}For automated scripts, use the minimum required permission profile:
# CI notification only needs to send messagesdiscli --profile chat message send "#deploys" "Deployed"Next Steps
- CLI Usage — Full command reference with examples
- Security & Permissions — Lock down your automation
- Building Agents — Go beyond scripts with persistent agents