A production-grade WhatsApp bot built on Baileys 7.x — typed, observable, scalable, and friendly to extend.
⚠️ Please read DISCLAIMER.md before using. Running an unofficial WhatsApp client may violate WhatsApp's Terms of Service and can result in your number being banned. Use a number you can afford to lose.
- 🔌 Baileys 7.x with the recommended retry, group-metadata, and signal-key caches wired in
- 🧩 Folder-based command registry with hot-reload, alias resolution, and auto-skip of commands missing API keys
- 📦 Pluggable store backend — SQLite (default), Postgres, MongoDB, or Redis
- 🔑 Pluggable auth backend — multi-file (default), Redis, or SQLite
- 🆔 Login via QR or pairing code
- 📊 Built-in web dashboard at
:3001/dashboard(React-based, with live stats + 11 tabs) - 🚦 Per-chat queue for back-pressure
- ❤️ Health & metrics at
GET /healthzandGET /metrics(Prometheus) - 📝 Structured logging via Pino
- 🛡️ Built-in middleware for rate-limiting and concurrency control
- 🔄 Supervisor + worker model with exponential-backoff restart
- 🌐 Dual prefix —
.for users,$for admins - 🧪 Zod-validated config with legacy support
- 🤖 4 LLM providers out of the box: OpenAI, Google Gemini, Anthropic Claude, and local Ollama
- 🛠️ 12 intel-focused tools — VirusTotal, AlienVault OTX, GitHub releases/advisories, Wikipedia, SSRF-guarded
fetch_url, plus 5 read-only WhatsApp store queries - 🎭 Personas —
threat-intel(default, security-focused) /general/custom - 🧠 20-turn rolling memory per chat, persisted across restarts
- ✋ Per-chat opt-in — no surprise replies; users explicitly enable with
.ai on - 💰 Hard daily USD cost cap with per-provider pricing table; rate limits 30/user/hour + 100/chat/day
- 📡 Outbound-only mirror of WhatsApp log channels (
syslogs,botLogs,errLogs, …) to Telegram chats/channels - 🔌 Zero new dependencies — raw HTTPS to
api.telegram.orgover the existing circuit-breaker - ⚡ 2-second batching for info-level logs;
error/fatalflushes immediately - 🎯 Per-channel routing — different WhatsApp log channels can go to different Telegram destinations
- 💾 Persistent AI rate-limit counters (v1.3.0) survive bot restarts
- 🚀 CI/CD auto-release —
git push origin v1.x.xtriggers GitHub Release + Docker (GHCR + Docker Hub, multi-arch) + npm publish (with provenance) + Pages deploy in parallel - 📚 VitePress docs site auto-deployed to cosm1cbug.github.io/echofox
- 📊 22 Grafana panels out of the box (defaults + AI + Telegram + Signal Protocol Health)
- 🚨 2 built-in alert rules: AI cost > 80% of cap, Telegram failure rate > 20% — mirrored to both WhatsApp
errLogsAND Telegram - 🩹 Signal protocol self-healing (v1.4.2) — auto-recovers from
Bad MAC/No session founddecryption errors without operator intervention; demotes the noisy ERROR logs to DEBUG
| Node.js | ≥ 20 (tested on 20 + 22 in CI) |
| OS | Linux / macOS / Windows 11 |
| A real phone number you can scan a QR / pairing code from |
EchoFox can be installed three ways — pick whichever fits your workflow.
mkdir my-echofox && cd my-echofox
npm init -y
npm install echofox
# Copy the example config from the installed package:
cp node_modules/echofox/src/config.example.js src/config.js # Linux/macOS
# Or on Windows PowerShell:
# Copy-Item node_modules\echofox\src\config.example.js src\config.jsdocker pull ghcr.io/cosm1cbug/echofox:latest
# or pin a specific version:
docker pull ghcr.io/cosm1cbug/echofox:1.11.3Then run with your config mounted in (see Docker section below).
git clone https://github.com/Cosm1cBug/EchoFox.git
cd EchoFox
npm installcp src/config.example.js src/config.js
# Edit src/config.js with your detailsAt minimum set:
admins[]— your own JID (e.g.1234567890@s.whatsapp.net)bot.timezone— your IANA timezone
Everything else has sensible defaults. See Configuration reference below.
npm startOn first run you'll see a QR code in the terminal. Open WhatsApp on your phone → Settings → Linked Devices → Link a Device → scan it.
Alternatively, pairing code:
// src/config.js
login: { type: 'PAIRING', phoneNumber: '1234567890' }Then npm start prints an 8-character code; enter it on your phone in the same Linked Devices flow.
The bot ships a React-based web dashboard at :3001/dashboard with 12 tabs covering everything the bot tracks.
The tabs:
| Tab | Shows |
|---|---|
| Overview | uptime, throughput, current load |
| Groups | groups the bot is in + per-group activity |
| Contacts | known contacts with extended status |
| Presence | recently-active users with state icons |
| Newsletters | WhatsApp channels the bot follows |
| Subscriptions | RSS / AlienVault / VirusTotal subscriptions |
| Labels | WA Business labels |
| Blocklist | blocked JIDs |
| Metrics | full Prometheus metric snapshot |
| Diagnostics | self-test report (config, store, auth, network) |
| Alerts | active built-in alert rules |
| AI | config, cost-cap progress bar, per-day usage table, opted-in chats |
cd dashboard
npm install
npm run dev # Vite dev server, hot reloadcd dashboard
npm run build # outputs to dashboard/dist/Or let the bot serve the bundled version automatically when dashboard.enabled = true.
Run .menu in WhatsApp to see the live list — 30+ commands across general, download, convert, group, admin, misc, main, tools, user.
Highlights:
| Category | Commands |
|---|---|
| AI (v1.2.0+) | .ai status / on / off / clear / persona / provider / model |
| Intel | .virustotal, .alienvault, .thehackersnews, .rss, .github, .vtwatch |
| Download | .song, .video, .mediafire, … |
| Convert | .sticker, .toimg, .tts |
| Group | .add, .kick, .promote, .demote, .link, .approve, … |
Admin ($) |
$healthcheck, $serverinfo, $ai-admin stats, … |
See docs/commands.md for the full auto-generated catalogue.
Set in src/config.js:
ai: {
enabled: true,
defaultProvider: 'openai', // openai | gemini | anthropic | local
model: 'gpt-4o-mini',
costCapPerDayUsd: 5,
providers: {
openai: { apiKey: process.env.OPENAI_API_KEY },
},
},Then in any chat:
.ai on
hey echofox, what's the latest on log4j
The bot routes to the selected provider, chains tools (e.g. github_advisories + latest_hackernews + wiki_lookup), cites sources, and stays under your daily cap. Full guide: docs/guide/ai.md or the docs site.
Mirror WhatsApp log channels to Telegram with per-channel routing — outbound-only, no Telegram polling.
telegram: {
enabled: true,
botToken: process.env.TELEGRAM_BOT_TOKEN,
routing: {
syslogs: '@echofox_sys', // public channel handle
errLogs: '-1001234567890', // private group numeric id
// empty string disables that channel's mirror
},
parseMode: 'HTML',
batchMs: 2000, // errors flush instantly
},Full guide: docs/guide/telegram.md or the docs site.
┌─────────────┐
│bootstrap.js │ ← supervisor (PM2-like; restarts on crash)
│:3000/healthz│
│:3000/metrics│
└──────┬──────┘
│ fork()
┌────────▼──────┐
│ worker.js │ ← single Baileys socket
│:3001/dashboard│ ← (when enabled)
│:3001/metrics │ ← (store-backed counters)
└──────┬────────┘
┌──────────────┼──────────────────┐
│ │ │
┌────▼────┐ ┌─────▼─────┐ ┌────────▼────────┐
│ events/ │ │ commands/ │ │ services/ │
│ (28) │ │ (32) │ │ ai, telegram, │
│ │ │ │ │ alertEngine, │
│ │ │ │ │ signalHealth, │
│ │ │ │ │ metrics, … │
└─────────┘ └───────────┘ └─────────────────┘
│
┌──────▼──────┐
│ store/ │ SQLite | Postgres | Mongo | Redis
│ migrations │
└─────────────┘
Edit src/config.js. Every field has a sensible default; you can leave most empty.
| Path | Type | Default | Description |
|---|---|---|---|
bot.name |
string | "EchoFox" |
Bot display name |
bot.prefix |
string|RegExp | "." |
User-command prefix |
bot.adminPrefix |
string|RegExp | "$" |
Admin-command prefix |
bot.sessionName |
string | "@session" |
Folder name for WA auth files |
bot.timezone |
string | "Asia/Kolkata" |
IANA timezone for logs / scheduling |
bot.public |
boolean | true |
false = admin-only mode |
features.readMessages |
boolean | true |
Mark incoming msgs as read |
features.readStatus |
boolean | true |
Mark statuses as read |
features.antiCall |
boolean | false |
Auto-reject incoming calls |
features.syncHistory |
boolean | true |
Pull full history on first login |
login.type |
enum | "QR" |
"QR" or "PAIRING" |
login.phoneNumber |
string | "" |
Required if type="PAIRING" (digits only) |
auth.method |
enum | "MULTIFILE" |
"MULTIFILE" / "REDIS" / "SQLITE" |
storeDB.type |
enum | "SQLITE" |
"SQLITE" / "POSTGRES" / "MONGODB" / "REDIS" |
dashboard.enabled |
boolean | false |
Built-in web UI |
dashboard.port |
number | 3001 |
|
dashboard.password |
string | "change-me-please" |
| Path | Type | Default | Description |
|---|---|---|---|
ai.enabled |
boolean | false |
Master switch |
ai.defaultProvider |
enum | 'openai' |
openai / gemini / anthropic / local |
ai.model |
string | 'gpt-4o-mini' |
Provider-specific model name |
ai.maxTokens |
number | 800 |
Per-response token cap |
ai.costCapPerDayUsd |
number | 5 |
Hard daily cap — bot refuses to reply past this |
ai.persona |
enum | 'threat-intel' |
threat-intel / general / custom |
ai.memoryTurns |
number | 20 |
Rolling memory window (10 user + 10 assistant) |
ai.optInDefault |
enum | 'off' |
'on' to auto-enable in every chat |
ai.rateLimitPerUserPerHour |
number | 30 |
|
ai.rateLimitPerChatPerDay |
number | 100 |
|
ai.enableToolCalling |
boolean | true |
|
ai.toolWhitelist[] |
array | 12 tools | Which intel tools the model can call |
ai.providers.openai.apiKey |
string | '' |
|
ai.providers.gemini.apiKey |
string | '' |
|
ai.providers.anthropic.apiKey |
string | '' |
|
ai.providers.local.baseUrl |
string | 'http://localhost:11434' |
Ollama endpoint |
| Path | Type | Default | Description |
|---|---|---|---|
telegram.enabled |
boolean | false |
|
telegram.botToken |
string | '' |
from @BotFather |
telegram.routing.{syslogs,botLogs,userLogs,groupUpdates,callLogs,errLogs,movGroup} |
string | '' |
Telegram chat id or @channel per WA log channel |
telegram.parseMode |
enum | 'HTML' |
HTML / MarkdownV2 / plain |
telegram.batchMs |
number | 2000 |
Errors flush instantly regardless |
telegram.maxChunkChars |
number | 3800 |
Telegram cap is 4096 |
| Path | Type | Default | Description |
|---|---|---|---|
alerts.enabled |
boolean | true |
|
alerts.windowMinutes |
number | 60 |
rolling window |
alerts.minInvocations |
number | 10 |
need at least N runs to alert |
alerts.failureRateThreshold |
number | 0.30 |
per-command failure rate trigger |
alerts.rules.aiCostPct |
object | {threshold: 0.80, cooldownMinutes: 60} |
Fire when daily AI cost reaches this fraction of cap |
alerts.rules.telegramFailureRate |
object | {threshold: 0.20, minSends: 10, cooldownMinutes: 30} |
Fire when Telegram send-failure rate is high |
Every field can also be set via an environment variable:
ECHOFOX_<SECTION>_<CAMELCASEKEY> — e.g. ECHOFOX_APIS_OMDB_APIKEY=xyz, ECHOFOX_STOREDB_TYPE=POSTGRES.
Full configuration guide: docs/config.md.
See CONTRIBUTING.md. TL;DR: drop a .js file in src/commands/<category>/:
module.exports = {
name: 'hello',
alias: ['hi'],
desc: 'Says hello',
category: 'general',
cooldown: 3,
async start(sock, m, { ctx, args, text, config }) {
await ctx.reply(`Hello ${ctx.pushName || 'friend'}!`);
},
};Hot reload picks it up immediately. The contract test (npm test) verifies every command's shape and detects name/alias collisions across the whole tree.
curl http://localhost:3000/healthz # supervisor health
curl http://localhost:3000/metrics # supervisor + Node.js defaults
curl http://localhost:3001/metrics # store-backed countersPrometheus metrics exposed (28 counters + 9 gauges across 2 endpoints):
| Endpoint | Metric | Type | Description |
|---|---|---|---|
:3000 |
echofox_worker_up |
gauge | 1 if the worker is alive |
:3000 |
echofox_worker_restarts_total |
counter | Cumulative supervisor restarts |
:3001 |
echofox_messages_received_total |
counter | Inbound messages |
:3001 |
echofox_commands_total |
counter | Command invocations |
:3001 |
echofox_ai_chat_requests_total |
counter | AI chat requests |
:3001 |
echofox_ai_tokens_prompt_total |
counter | Prompt tokens consumed |
:3001 |
echofox_ai_tokens_completion_total |
counter | Completion tokens consumed |
:3001 |
echofox_ai_cost_usd_today |
gauge | Today's AI spend |
:3001 |
echofox_telegram_forwards_total |
counter | Telegram log forwards |
:3001 |
echofox_telegram_send_failures_total |
counter | Telegram send failures |
:3001 |
echofox_signal_decryption_failures_total |
counter | Baileys decryption errors |
:3001 |
echofox_signal_session_recoveries_total |
counter | Auto-triggered session resets |
…plus 18 more. Full list at src/store/schema/stats.js.
Grafana dashboard JSON: docker/grafana/dashboards/echofox-overview.json — 22 panels organised in 3 sections (core process metrics, AI v1.2+, Telegram v1.3+, Signal Protocol Health v1.4.2+). Auto-provisioned by the Compose observability profile.
docker run -d --name echofox \
-p 3000:3000 -p 3001:3001 \
-v echofox-data:/app/src/store/runtime \
-v echofox-session:/app/src/@session \
-e ECHOFOX_BOT_TIMEZONE=Asia/Kolkata \
cosm1cbug/echofox:latestTag scheme:
:1.4.2,:1.3.0, etc. — precise (immutable):1.4,:1.3,:1.2— major.minor (auto-updates with patches):latest— always newest stable:sha-abc1234— per-commit immutable tag
Available on both GHCR and Docker Hub.
# Bot only:
docker compose up -d
# With Prometheus + Grafana:
docker compose --profile observability up -d
# → Grafana at http://localhost:3001 (admin / changeme)- docs/deploy/docker.md — single container, env vars, volumes, updates
- docs/deploy/docker-compose.md — Compose + observability profile
- docs/deploy/podman.md — rootless alternative
- docs/deploy/multi-arch.md — building your own multi-arch images
- docs/deploy/troubleshooting.md — when things go wrong
- docs/deploy/ci-cd.md — the 7 GitHub Actions workflows + release flow
Tag-driven, fully automated:
# 1. Bump version
npm version patch # or minor / major
# 2. Update CHANGELOG.md + write RELEASE_NOTES_v<v>.md
git add . && git commit -m "chore(release): v$(node -p require\('./package.json'\).version)"
git push origin main
# 3. Tag — fires 4 workflows in parallel
git tag v$(node -p "require('./package.json').version")
git push origin --tagsWithin ~5 minutes:
- ✅ GitHub Release with
RELEASE_NOTES_v<v>.mdbody + source tarball - ✅ Multi-arch Docker images on GHCR + Docker Hub
- ✅ npm publish with provenance (SLSA attestation)
- ✅ Docs site deploy to GitHub Pages
Required secrets (all optional): NPM_TOKEN, DOCKERHUB_USERNAME, DOCKERHUB_TOKEN. See docs/deploy/ci-cd.md.
Pull requests welcome! See CONTRIBUTING.md.
CI gates every PR with:
- ESLint + Prettier
- 147 automated tests (Node 20 + 22 matrix)
- AGPL header check (every
.jsfile must have one) - Dashboard TypeScript typecheck
- TruffleHog secret scan
By participating, you agree to behave kindly and constructively.
Found a vulnerability? Please report it privately — see SECURITY.md.
Hardening summary:
- 🔒 Per-chat AI opt-in — no surprise replies
- 🔒 AI fetch_url SSRF guard — refuses RFC 1918 / link-local / loopback
- 🔒 Telegram bridge is strictly outbound — no inbound command surface
- 🔒 API keys never exposed via dashboard or
/api/ai/config - 🔒 Dashboard
/api/*routes Basic-auth gated - 🔒 npm published with
--provenancefor SLSA-style supply-chain attestation - 🔒 TruffleHog scans every push for leaked secrets
- 🔒 Weekly Docker image rebuild for base-image security patches
If EchoFox is useful to you, please ⭐ star the repo — it's the cheapest way to say "this is cool, keep going."
Licensed under the GNU Affero General Public License v3.0 or later.
In short: if you run a modified version of EchoFox as a service, you must offer the source of your modifications to the users who interact with it over the network. Third-party attributions in NOTICE.
- Baileys by @PurpShell and contributors — none of this would exist without their reverse-engineering
EchoFox is not affiliated with WhatsApp or Meta. WhatsApp™ is a trademark of WhatsApp LLC.