Posted on
Artificial Intelligence

Artificial Intelligence Community Management

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

Bash-first playbook for Artificial Intelligence Community Management

AI communities move fast. New users flood in, questions pile up, and your maintainers get stretched thin. The result? Slower responses, repeated questions, and missed signals from your most active contributors.

The good news: with a few lightweight Bash scripts, standard Linux tools, and an optional local LLM, you can automate the boring parts of community ops—without abandoning the command line.

This guide shows you how to:

  • Collect messages from your channels

  • Auto-draft helpful replies with an LLM

  • Publish daily digests

  • Generate lightweight activity metrics

  • Flag potentially risky posts for moderation

All with Bash, curl, jq, and sqlite—plus cron for scheduling.

Why manage AI communities this way?

  • Scale without losing the human touch: Let scripts draft answers and summaries; humans approve, edit, or step in on edge cases.

  • CLI-native, auditable workflows: Everything is files and logs. Version-control your automation behavior.

  • Tool-agnostic APIs: Discord, Matrix, Discourse, GitHub—if there’s an HTTP API, Bash and curl can reach it.

  • Privacy by design: Use a local LLM (e.g., via ollama) so sensitive community content never leaves your server.

Prerequisites: Install core tools

Use your distro’s package manager to install a minimal toolkit. Choose the commands for your system.

  • Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y curl jq sqlite3 git cron s-nail
sudo systemctl enable --now cron
  • Fedora/RHEL/CentOS (dnf):
sudo dnf install -y curl jq sqlite git cronie mailx
sudo systemctl enable --now crond
  • openSUSE (zypper):
sudo zypper refresh
sudo zypper install -y curl jq sqlite3 git cronie s-nail
sudo systemctl enable --now cron

Notes:

  • sqlite package name is sqlite3 on Debian/openSUSE and sqlite on Fedora.

  • mailx may be provided by s-nail. If email notifications are not needed, you can skip it.

  • cron service name is cron on Debian/openSUSE and crond on Fedora.

Optional: Local LLM via ollama (recommended for privacy/performance):

curl -fsSL https://ollama.com/install.sh | sh
# After installing, pull a small instruct model:
ollama pull llama3:8b-instruct

Project setup

mkdir -p ai-comm-mgr/{bin,data,logs,summaries}
cd ai-comm-mgr

# Minimal state store
sqlite3 data/state.db 'CREATE TABLE IF NOT EXISTS checkpoints (source TEXT PRIMARY KEY, last_id TEXT);'
sqlite3 data/state.db 'CREATE TABLE IF NOT EXISTS replied (message_id TEXT PRIMARY KEY);'

# Environment file (edit with your tokens/IDs)
cat > .env << 'EOF'
# Discord example (use a bot with channel read/write scope)
DISCORD_BOT_TOKEN=your_discord_bot_token_here
DISCORD_CHANNEL_ID=your_channel_id_here
BOT_USER_ID=your_bot_user_id_here

# Optional: Slack/Matrix/other webhook for moderator alerts
MOD_WEBHOOK_URL=https://hooks.slack.com/services/XXX/YYY/ZZZ

# Local LLM endpoint (ollama default)
OLLAMA_HOST=http://127.0.0.1:11434
LLM_MODEL=llama3:8b-instruct
EOF

# Convenient automatic env loading per shell session
echo 'set -a; [ -f .env ] && . ./.env; set +a' >> .bashrc
. ./.env

Tip: Keep .env out of version control. Add it to .gitignore.

Step 1: Collect channel messages (Discord example)

This example pulls new messages from a Discord channel using a Bot token and stores them as JSON Lines. The same pattern works for Matrix, Slack, or Discourse—swap the API endpoint and authentication.

File: bin/collect_discord.sh

#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
set -a; [ -f .env ] && . ./.env; set +a

: "${DISCORD_BOT_TOKEN:?Missing DISCORD_BOT_TOKEN}"
: "${DISCORD_CHANNEL_ID:?Missing DISCORD_CHANNEL_ID}"

STATE_DB="data/state.db"
LAST_ID="$(sqlite3 "$STATE_DB" "SELECT last_id FROM checkpoints WHERE source='discord';" || true)"
URL="https://discord.com/api/v10/channels/${DISCORD_CHANNEL_ID}/messages?limit=100"
[ -n "$LAST_ID" ] && URL="${URL}&after=${LAST_ID}"

TMP="data/.discord_pull.json"
curl -sS -H "Authorization: Bot ${DISCORD_BOT_TOKEN}" "$URL" > "$TMP"

# API returns newest-first; normalize to oldest-first and append to log
jq -c 'reverse[]' "$TMP" >> data/messages.jsonl || true

# Update last checkpoint
NEW_LAST_ID="$(jq -r '.[0].id // empty' "$TMP")"
if [ -n "$NEW_LAST_ID" ]; then
  sqlite3 "$STATE_DB" "INSERT INTO checkpoints(source,last_id) VALUES('discord','$NEW_LAST_ID')
    ON CONFLICT(source) DO UPDATE SET last_id=excluded.last_id;"
fi

rm -f "$TMP"
echo "$(date -Is) Collected messages. Last ID: ${NEW_LAST_ID:-unchanged}" >> logs/collect.log

Usage:

bash bin/collect_discord.sh

Safety notes:

  • Respect platform rate limits. If you need to backfill large histories, introduce sleep and paging logic.

  • Check bot permissions: it must read messages in the specified channel.

Step 2: Auto-draft answers to FAQs with a local LLM

Use an LLM to propose concise replies to typical user questions, then post them as threaded replies. Keep a human-in-the-loop by routing drafts to a “staging” channel or requiring maintainer emoji approval—start simple and iterate.

File: bin/auto_reply.sh

#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
set -a; [ -f .env ] && . ./.env; set +a

: "${DISCORD_BOT_TOKEN:?Missing DISCORD_BOT_TOKEN}"
: "${DISCORD_CHANNEL_ID:?Missing DISCORD_CHANNEL_ID}"
: "${BOT_USER_ID:?Missing BOT_USER_ID}"
: "${OLLAMA_HOST:?Missing OLLAMA_HOST}"
: "${LLM_MODEL:=llama3:8b-instruct}"

STATE_DB="data/state.db"
MSG_FILE="data/messages.jsonl"
[ -f "$MSG_FILE" ] || { echo "No messages yet. Run collect first."; exit 0; }

# Filter: questions not from the bot, recently seen, not already replied
# Heuristic: contains '?' and not previously processed
cutoff="$(date -u -d '12 hours ago' +%s)"
jq -c --arg bot "$BOT_USER_ID" --argjson cutoff "$cutoff" '
  select(.author.id != $bot and (.author.bot // false) != true) |
  select(.content | test("\\?")) |
  select((.timestamp | fromdateiso8601) >= $cutoff)
' "$MSG_FILE" | while IFS= read -r line; do
  msg_id="$(jq -r '.id' <<<"$line")"
  author="$(jq -r '.author.username' <<<"$line")"
  content="$(jq -r '.content' <<<"$line")"

  # skip if already replied
  if sqlite3 "$STATE_DB" "SELECT 1 FROM replied WHERE message_id='$msg_id';" | grep -q 1; then
    continue
  fi

  # Build prompt with lightweight, policy-aware instructions
  prompt=$(cat <<EOF
You are a helpful community assistant for an open-source AI project.

- Be brief (<=150 words), friendly, and practical.

- If you aren't certain, say so and link to docs or ask a clarifying question.

- Encourage code snippets or minimal reproductions when relevant.

- Never reveal secrets or pretend to have access to private data.

Question from $author:
$content

If a short code example helps, prefer bash commands or links to official docs.
EOF
)

  answer="$(curl -sS -X POST "${OLLAMA_HOST}/api/generate" \
    -H 'Content-Type: application/json' \
    -d "$(jq -n --arg model "$LLM_MODEL" --arg prompt "$prompt" '{model:$model, prompt:$prompt, stream:false}')" \
    | jq -r '.response')"

  # Post the reply as a thread message_reference
  payload="$(jq -n --arg c "$answer" --arg ref "$msg_id" \
    '{content:$c, message_reference:{message_id:$ref}}')"

  http_code="$(curl -sS -o /tmp/discord_reply.out -w "%{http_code}" \
    -H "Authorization: Bot ${DISCORD_BOT_TOKEN}" \
    -H "Content-Type: application/json" \
    -X POST "https://discord.com/api/v10/channels/${DISCORD_CHANNEL_ID}/messages" \
    -d "$payload")"

  if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
    sqlite3 "$STATE_DB" "INSERT OR IGNORE INTO replied (message_id) VALUES ('$msg_id');"
    echo "$(date -Is) Replied to $msg_id" >> logs/auto_reply.log
    sleep 2  # be nice to rate limits
  else
    echo "$(date -Is) Failed to reply $msg_id: HTTP $http_code $(cat /tmp/discord_reply.out)" >> logs/auto_reply.log
  fi
done

Usage:

bash bin/collect_discord.sh
bash bin/auto_reply.sh

Tips:

  • Start by posting drafts into a private channel for review.

  • Maintain a FAQ.md and include it in the prompt (e.g., append its contents) to improve accuracy.

  • If you prefer a hosted API, swap the ollama call for your provider’s curl endpoint.

Step 3: Ship a daily discussion digest

Create a short, scannable summary of what happened yesterday and commit it to a repo or publish it to your forum/newsletter.

File: bin/daily_digest.sh

#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
set -a; [ -f .env ] && . ./.env; set +a

: "${OLLAMA_HOST:?Missing OLLAMA_HOST}"
: "${LLM_MODEL:=llama3:8b-instruct}"

MSG_FILE="data/messages.jsonl"
[ -f "$MSG_FILE" ] || exit 0

ydate="$(date -u -d 'yesterday' +%F)"
start_ts="$(date -u -d "$ydate 00:00:00Z" +%s)"
end_ts="$(date -u -d "$ydate 23:59:59Z" +%s)"

context="$(jq -r --argjson s "$start_ts" --argjson e "$end_ts" '
  select((.timestamp | fromdateiso8601) >= $s and (.timestamp | fromdateiso8601) <= $e) |
  "[\(.author.username)] \(.content)"
' "$MSG_FILE")"

[ -z "$context" ] && exit 0

prompt=$(cat <<EOF
Create a concise daily digest for an AI developer community.

- Focus on new releases, bugfixes, FAQs, design decisions, action items.

- Use bullets. Group by theme. Max ~250 words.

- Include contributor handles when relevant.

Messages:
$context
EOF
)

summary="$(curl -sS -X POST "${OLLAMA_HOST}/api/generate" \
  -H 'Content-Type: application/json' \
  -d "$(jq -n --arg model "$LLM_MODEL" --arg prompt "$prompt" '{model:$model, prompt:$prompt, stream:false}')" \
  | jq -r '.response')"

out="summaries/${ydate}.md"
mkdir -p summaries
printf "# Daily Digest (%s)\n\n%s\n" "$ydate" "$summary" > "$out"
echo "$(date -Is) Wrote $out" >> logs/digest.log

Schedule it with cron:

crontab -l 2>/dev/null; echo "15 0 * * * /bin/bash $PWD/bin/daily_digest.sh >/dev/null 2>&1" | crontab -

Optionally, git-push the summaries to a docs repo:

git init
git remote add origin git@github.com:yourorg/ai-community-digests.git
git add summaries
git commit -m "Add daily digest $(date -I)"
git push -u origin main

Step 4: Lightweight metrics that matter

Without heavy dashboards, you can still get signal:

File: bin/metrics.sh

#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."

MSG_FILE="data/messages.jsonl"
[ -f "$MSG_FILE" ] || exit 0

since_days="${1:-7}"
since_ts="$(date -u -d "$since_days days ago" +%s)"

echo "Top contributors (last $since_days days):"
jq -r --argjson s "$since_ts" '
  select((.timestamp | fromdateiso8601) >= $s) | .author.username
' "$MSG_FILE" | sort | uniq -c | sort -nr | head -n 10

echo
echo "Daily message counts:"
jq -r --argjson s "$since_ts" '
  select((.timestamp | fromdateiso8601) >= $s) |
  (.timestamp | fromdateiso8601 | strflocaltime("%Y-%m-%d"))
' "$MSG_FILE" | sort | uniq -c | awk '{print $2, $1}'

echo
echo "Common tags/keywords (#, backticked tokens, or code-ish words):"
jq -r --argjson s "$since_ts" '
  select((.timestamp | fromdateiso8601) >= $s) | .content
' "$MSG_FILE" \
| tr ' ' '\n' \
| grep -E '(^#\w+|`[^`]+`|[A-Za-z0-9_\-]+\.(py|sh|ipynb)|API|GPU|CUDA|token|fine[- ]?tune|quant|inference)' -i \
| sed 's/[`.,:;(){}"]//g' \
| tr '[:upper:]' '[:lower:]' \
| sort | uniq -c | sort -nr | head -n 20

Usage:

bash bin/metrics.sh 14

Extend this script to compute response times by pairing question messages with first replies (via message_reference) if you rely heavily on threaded replies.

Step 5: Proactive moderation alerts

Flag messages that might contain secrets, PII, or risky topics, then notify moderators via email or webhook.

File: bin/moderate.sh

#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
set -a; [ -f .env ] && . ./.env; set +a

MSG_FILE="data/messages.jsonl"
[ -f "$MSG_FILE" ] || exit 0

since_ts="$(date -u -d '2 hours ago' +%s)"

patterns=(
  '(?i)api[_ -]?key'
  '(?i)secret'
  '(?i)password'
  '(?i)token(?!ization)'
  '(?i)private[- ]?key'
  '(?i)credit[- ]?card|ccv|cvv'
  '(?i)ssn|social[- ]security'
  '(?i)personally identifiable|pii'
)

jq -c --argjson s "$since_ts" 'select((.timestamp | fromdateiso8601) >= $s)' "$MSG_FILE" \
| while IFS= read -r line; do
  content="$(jq -r '.content' <<<"$line")"
  author="$(jq -r '.author.username' <<<"$line")"
  mid="$(jq -r '.id' <<<"$line")"

  for rx in "${patterns[@]}"; do
    if grep -Pq "$rx" <<<"$content"; then
      msg="Potentially sensitive content from $author (id:$mid): $content"
      echo "$(date -Is) $msg" >> logs/moderation.log

      if [ -n "${MOD_WEBHOOK_URL:-}" ]; then
        payload="$(jq -n --arg text "$msg" '{text:$text}')"
        curl -sS -H "Content-Type: application/json" -X POST "$MOD_WEBHOOK_URL" -d "$payload" >/dev/null
      elif command -v mail >/dev/null 2>&1; then
        printf "%s\n" "$msg" | mail -s "[AI Community] Moderation flag" you@example.com || true
      fi
      break
    fi
  done
done

Schedule the collector and moderation checker:

( crontab -l 2>/dev/null; echo "*/5 * * * * /bin/bash $PWD/bin/collect_discord.sh >/dev/null 2>&1"; \
  echo "*/10 * * * * /bin/bash $PWD/bin/moderate.sh >/dev/null 2>&1" ) | crontab -

Note: Email delivery depends on a local MTA; if you don’t have one, prefer a webhook to Slack/Matrix.

Real-world example (composite)

An open-source inference engine saw its Discord support channel balloon from ~100 to ~800 daily messages after a major release. Maintainers adopted:

  • Message collection + daily digests: new contributors could catch up quickly, reducing repeated questions.

  • Auto-draft replies for FAQs: maintainers reviewed and one-click approved the bot’s suggestions for “CUDA errors,” “model quantization,” and “token limits.” Roughly 45% of questions received a draft answer; maintainers edited ~30% of drafts before posting.

  • Moderation alerts: a few accidental API key pastes were flagged within minutes.

Outcomes over 6 weeks:

  • Median time-to-first-reply dropped from 5h to 1h 50m.

  • Repeated question rate (heuristic: near-duplicate content) fell ~25%.

  • Maintainer burnout indicators (off-hours pages, backlog) significantly improved.

Operational tips

  • Iterate prompts: Keep them short, include your FAQ, code of conduct, and links to docs.

  • Log everything: logs/auto_reply.log and logs/digest.log are your audit trail.

  • Rate limits: Add sleeps and exponential backoff where needed.

  • Privacy: Prefer a local LLM for drafting, especially if messages might include user data.

  • Fail safe: If an API call fails, don’t loop aggressively; log and retry later.

Conclusion and next step

You don’t need a heavy SaaS to run a responsive AI community. Start with: 1) Install the prerequisites. 2) Wire up collect_discord.sh to pull messages. 3) Trial daily_digest.sh to brief your team every morning. 4) Pilot auto_reply.sh in a private review channel before going public.

Once you’re confident, enable the cron jobs and expand to other platforms (Matrix/Slack) by swapping endpoints in the collector.

If you want a ready-to-fork skeleton with these scripts, turn this layout into a repo, add your README, and start shipping better community ops—straight from Bash.