ReplyPilot is a TypeScript CLI for automating WhatsApp replies with LM Studio, Ollama, or any OpenAI-compatible chat completions endpoint.
It listens for new direct WhatsApp messages, fetches recent chat history, asks your configured model to reply in your tone, and sends the response back through WhatsApp Web.
Install globally (CLI use):
npm i -g gimirick-replypilotUninstall:
npm uninstall -g gimirick-replypilotInstall locally (programmatic use):
npm i gimirick-replypilotUninstall:
npm uninstall gimirick-replypilotnpm update -g gimirick-replypilotUpdates the globally installed version to the latest release.
npm cache clean --forceClears the npm cache if you encounter integrity or checksum errors during install.
ReplyPilot requires these runtime packages (auto-installed by npm install):
| Package | Purpose |
|---|---|
whatsapp-web.js |
WhatsApp Web client library |
openai |
OpenAI-compatible LLM provider client |
commander |
CLI command parsing |
@inquirer/prompts |
Interactive setup wizard |
conf |
Persistent config storage |
zod |
Config schema validation |
p-queue |
Message queue with concurrency control |
pino |
Structured logging |
qrcode-terminal |
QR code display in terminal |
Plus development tooling (TypeScript, Vitest, ESLint, Prettier, tsup, tsx) installed automatically as devDependencies.
External requirements:
- A local or remote OpenAI-compatible chat completions API (LM Studio, Ollama, OpenAI, etc.).
- ffmpeg on
PATH(required only when voice note processing is enabled).
Warning
Linux Users (Ubuntu/Debian, etc.): Do not use sudo npm i -g gimirick-replypilot. Installing with sudo downloads the internal browser to the root directory, causing the app to crash for normal users. Use a Node version manager (like NVM) to install without sudo, or use the NPM Local Install (npx) method below instead.
Install from npm:
npm i -g gimirick-replypilotreplypilot becomes a system-wide command.
# Setup wizard (creates a named config ββ> AI settings only)
replypilot setup
# Authenticate a WhatsApp account (scans QR code)
replypilot login
# Switch between authenticated WhatsApp accounts
replypilot account switch
# Start automation (connects active WhatsApp + active config)
replypilot start
# Display installed version
replypilot version
# Health check (Node version, config, provider reachability, ffmpeg)
replypilot doctor
# View current config (API key redacted)
replypilot config show
# Reset config (prompts confirmation)
replypilot config reset
# Switch to a different named config
replypilot config switch
# Remove all WhatsApp session data (prompts confirmation)
replypilot logout
# Clear WhatsApp web client cache (prompts confirmation)
replypilot cache
# Clear everything: npm cache, all configs, all WhatsApp accounts, and web cache
replypilot clearProgrammatic API (any .js / .mjs file):
import { startAutomation, loadConfig, type AppConfig } from 'gimirick-replypilot';
await startAutomation();
await startAutomation({ safety: { dryRun: true } });
const config: AppConfig = loadConfig();Tip
Recommended for Linux: Using npx safely bypasses global permission issues on Linux and guarantees the internal browser downloads to the correct user directory.
Install from npm:
npm i gimirick-replypilotAll features via npx:
npx replypilot setup
npx replypilot login
npx replypilot account switch
npx replypilot start
npx replypilot version
npx replypilot doctor
npx replypilot config show
npx replypilot config reset
npx replypilot config switch
npx replypilot logout
npx replypilot cache
npx replypilot clearProgrammatic API in your project:
import { startAutomation, loadConfig, runDoctor } from 'gimirick-replypilot';
import { ReplyAutomation, OpenAiCompatibleProvider } from 'gimirick-replypilot';
import { runSetupWizard, createConfigStore } from 'gimirick-replypilot';
import { MissingConfigError, ReplyPilotError } from 'gimirick-replypilot';
await startAutomation();git clone https://github.com/GimiRick/ReplyPilot.git
cd ReplyPilot
npm installnpm run build
node dist/cli.js setup
node dist/cli.js login
node dist/cli.js account switch
node dist/cli.js start
node dist/cli.js version
node dist/cli.js doctor
node dist/cli.js config show
node dist/cli.js config reset
node dist/cli.js config switch
node dist/cli.js logout
node dist/cli.js cache
node dist/cli.js clearOr via npm scripts:
npm start # node dist/cli.js startnpm run dev # tsx src/cli.ts start
tsx src/cli.ts setup
tsx src/cli.ts login
tsx src/cli.ts account switch
tsx src/cli.ts version
tsx src/cli.ts doctor
tsx src/cli.ts config show
tsx src/cli.ts config reset
tsx src/cli.ts config switch
tsx src/cli.ts logout
tsx src/cli.ts cache
tsx src/cli.ts clearnpm test # vitest run
npm run test:watch # vitest (watch)
npm run test:coverage # vitest run --coverage
npm run typecheck # tsc --noEmit
npm run lint # eslint .
npm run format # prettier --write .
npm run build # tsup (ESM + .d.ts)
npm run pack:dry-run # inspect npm tarballThe setup wizard also configures optional voice note handling, transcription via Whisper Cloud API, a local whisper.cpp server, or native audio passthrough to a multimodal LLM. See Voice Note Processing for details.
During setup you may optionally set a rate limit for LLM API calls (default: 36 calls/minute when enabled). This caps how often the LLM is invoked globally, preventing provider overload. Disabled by default.
You can also configure a wait time before sending messages (default: 10 seconds when enabled). Same-chat messages arriving within this window are batched into a single LLM call. Set to 0 for immediate processing. Disabled by default.
Optionally add fallback API keys during setup. If the primary key fails (rate limited, out of balance, server error, etc.), ReplyPilot automatically rotates to the next key and retries, keeping your replies flowing with no interruption.
- Open LM Studio, load a chat model, start the local OpenAI-compatible server.
- Run
replypilot setup, select LM Studio. - Defaults: Base URL
http://localhost:1234/v1, API keylm-studio.
-
Install and start Ollama. Pull a model:
ollama pull llama3.1
-
Run
replypilot setup, select Ollama. -
Choose between Ollama Cloud and Ollama Local:
- Ollama Cloud β defaults to
https://ollama.com/v1with modelgemma4:31b-cloud. - Ollama Local β defaults to
http://localhost:11434/v1with API keyollama.
- Ollama Cloud β defaults to
Works with ChatGPT, Gemini, or any OpenAI-compatible API. To switch providers, either re-run setup and enter a new config name, or use the switch command if you already have multiple configs saved:
replypilot setup # create a new config, enter a name like "chatgpt"
replypilot config switch # or switch between existing configsDuring setup enter the base URL, API key, and model name for your provider.
| Provider | Base URL | Example model |
|---|---|---|
| ChatGPT | https://api.openai.com/v1 |
gpt-5.5 |
| Gemini | https://generativelanguage.googleapis.com/v1beta/openai/ |
gemini-3.5-flash |
| Groq | https://api.groq.com/openai/v1 |
llama-4-scout-17b-16e-instruct |
During setup, after entering your primary API key, ReplyPilot asks if you'd like to add fallback API keys. These are backup keys for the same provider useful for rate limits, expired credits, or any other failure.
? Do you want to add a fallback API key? (y/N)
? Fallback API key (enter key)
? Do you want to add another fallback API key? (y/N)
If a request with the current key fails, ReplyPilot first retries the request with exponential backoff (1s, 2s, 4s β up to the configured max retries). Transient errors like HTTP 429 (rate limited), 503, 408, 504, and network errors (ECONNRESET, ETIMEDOUT) are retried automatically. If all retries fail, ReplyPilot rotates to the next fallback key and retries. If all keys fail, the error is reported. Fallback keys are optional, you can press enter (defaults to n) to skip.
You can create and manage multiple named configurations. Each config stores its own LLM provider settings, model, voice note preferences, and automation behavior independently of which WhatsApp account is active.
Configs and WhatsApp accounts are fully decoupled. You can use the same WhatsApp account with different AI configurations, or the same AI config with different WhatsApp accounts.
Run replypilot setup as many times as you want. Each time, you'll be asked for a config name:
? Configuration name (default)
Enter a name like work, personal, chatgpt, or press Enter for the default.
replypilot config switchShows a list of your saved configs. Select one to make it active. This only changes which AI settings are used, your WhatsApp account stays the same.
replypilot config showPrints the active config's name at the top along with the full config (API keys redacted).
replypilot config resetDisplays a selection of your saved configs. Pick one to delete, or choose Reset all configurations to delete everything. With only one config, you get a direct confirm prompt. If the active config is deleted, the next one becomes active automatically.
WhatsApp accounts are managed separately from configs. You log into each account once, give it a name, and then select which one to use.
replypilot loginYou'll be prompted for an account name (e.g., work-phone, personal). Names must be unique if you try to reuse a name, you'll be asked to enter a different one.
? WhatsApp account name: work-phone
A QR code appears in the terminal. Scan it from WhatsApp on your phone (Linked Devices β Link a device). Once scanned, the account is saved and set as active.
You can repeat this for multiple phone numbers. Each gets its own name and its own saved session, no need to re-scan QR codes later.
replypilot account switchShows all authenticated accounts. Select one to make it active. This only changes which WhatsApp account is used, your AI config stays the same.
replypilot logoutDisplays a selection of your authenticated accounts. Pick one to remove its session, or choose Logout all accounts to remove everything. With only one account, you get a direct confirm prompt. You'll need to run replypilot login again before you can use any account.
replypilot cacheClears the WhatsApp web client cache (.wwebjs_cache) from the current directory.
replypilot clearWipes everything: runs npm cache clean --force, deletes all saved configurations, removes all WhatsApp accounts, and clears the WhatsApp web client cache. You'll be prompted to confirm before anything is deleted.
- Create an AI config:
replypilot setupβ pick your provider, model, and style prompt. - Log into WhatsApp:
replypilot loginβ give it a name and scan the QR code. - Start:
replypilot startβ the tool connects the active WhatsApp account using the active config. - First-time calibration: The first time you run the tool, it needs about 10 minutes to calibrate. During this period, it downloads browser data, initializes the WhatsApp session, and syncs your chats. After 10 minutes it will start working normally. From the second time onward, it works instantly.
To use a different account or a different AI setup later, just use replypilot account switch or replypilot config switch β they're independent.
- Direct contact messages are processed.
- Messages sent by you are ignored.
- Group auto-replies are disabled by default.
- Broadcast list auto-replies are disabled by default.
- Archived chat auto-replies are disabled by default.
- WhatsApp status broadcasts are always ignored.
- Voice notes are ignored by default (
ignoremode). - Dry-run can be enabled during setup to log replies without sending them.
Archived chats are ignored by default. When a message arrives from an archived chat, the following pipeline runs to block the reply:
message.getChat() β chat.archived === true
β
toFilterableMessage() β filterable.archived = true
β
client.ts:123 getIgnoreReason() β 'archived' β skipMediaProcessing = true
β
toLightweightRuntimeMessage() β runtimeMessage.archived = true
β
messageHandler(runtimeMessage) β automation.handleIncomingMessage()
β
automation.ts:92 getIgnoreReason(message, config)
β
filters.ts:42 message.archived && !config.whatsapp.allowArchived
β true && true = true
β
returns 'archived' β message IGNORED β LLM does NOT reply
If you want the LLM to interact with archived chats, enable it during replypilot setup:
? Auto-reply in archived chats? (y/N)
Or add "allowArchived": true to your config's whatsapp section.
These settings are not exposed in the setup wizard, but can be added directly to your config file (replypilot config show to view the current config).
{
"whatsapp": {
"loginDelayMs": 500
}
}Controls how long (in milliseconds) the app waits after WhatsApp confirms it's ready before completing the login process. This delay ensures the session data is saved to disk.
- Default:
500(0.5 seconds) - Range:
0β30,000 - Increase (e.g.,
2000) if you keep getting QR code prompts on every login (slow I/O). - Decrease (e.g.,
0) to log in faster, at the risk of the session not persisting.
{
"automation": {
"shutdownTimeoutMs": 15000
}
}Controls how long (in milliseconds) the app waits for a graceful shutdown before force-exiting. When you press Ctrl+C, the app completes all pending messages, then stops WhatsApp and the AI provider. If this takes longer than the timeout, it force-closes.
- Default:
15000(15 seconds) - Range:
1,000β120,000 - Increase (e.g.,
30000) if you have slow connections that need more time to shut down. - Decrease (e.g.,
5000) to make the app exit faster when stuck.
| Feature | Global | Local (npx) | Git Clone (built) | Git Clone (tsx) |
|---|---|---|---|---|
setup |
β | β | β | β |
login |
β | β | β | β |
account switch |
β | β | β | β |
start |
β | β | β npm start |
β npm run dev |
version |
β | β | β | β |
doctor |
β | β | β | β |
config show / reset |
β | β | β | β |
config switch |
β | β | β | β |
logout |
β | β | β | β |
cache |
β | β | β | β |
clear |
β | β | β | β |
| Multi-WA accounts | β | β | β | β |
| Programmatic API | β import from pkg |
β import from pkg |
β import from ./dist |
β import from ./src |
| TypeScript types | β auto | β auto | β from dist/ |
β from src/ |
| Multi-config profiles | β | β | β | β |
| Run tests | β | β | β | β npm test |
| Hot-reload | β | β | β | β tsx --watch |
// Core automation
import { startAutomation, ReplyAutomation, processIncomingMessageBatch } from 'gimirick-replypilot';
import { type AutomationResult, type ReplyAutomationOptions } from 'gimirick-replypilot';
import { type RuntimeIncomingMessage } from 'gimirick-replypilot';
// LLM provider
import {
OpenAiCompatibleProvider,
type OpenAiCompatibleProviderOptions,
} from 'gimirick-replypilot';
import {
type LlmProvider,
type GenerateReplyInput,
type GenerateReplyResult,
} from 'gimirick-replypilot';
import { type ChatContextMessage, type PromptMessage } from 'gimirick-replypilot';
import {
buildReplyPrompt,
cleanGeneratedReply,
formatChatContext,
trimContextMessages,
} from 'gimirick-replypilot';
// Config
import {
loadConfig,
saveConfig,
deleteConfig,
tryLoadConfig,
hasConfig,
listConfigNames,
getActiveConfigName,
setActiveConfigName,
validateConfigName,
} from 'gimirick-replypilot';
import { createConfigStore, getConfigFilePath, getWhatsAppSessionDir } from 'gimirick-replypilot';
import {
removeWhatsAppSessionData,
removeWhatsAppSessionAccount,
clearActiveWhatsAppAccount,
type ReplyPilotConfigStore,
} from 'gimirick-replypilot';
import { runSetupWizard, promptForConfig, createConfigFromSetupAnswers } from 'gimirick-replypilot';
import { type PromptAdapter, type SetupAnswers } from 'gimirick-replypilot';
import { parseAppConfig, mergeAppConfig, redactConfig } from 'gimirick-replypilot';
import { type AppConfig, type PartialAppConfig, type LlmProviderName } from 'gimirick-replypilot';
import { CONFIG_VERSION, DEFAULT_APP_CONFIG, PROVIDER_DEFAULTS } from 'gimirick-replypilot';
import { appConfigSchema, providerSchema, logLevelSchema } from 'gimirick-replypilot';
// Doctor / health
import { runDoctor, formatDoctorReport, checkProviderReachability } from 'gimirick-replypilot';
import { isSupportedNodeVersion, type DoctorReport, type DoctorCheck } from 'gimirick-replypilot';
// WhatsApp
import { loginWhatsAppAccount } from 'gimirick-replypilot';
import { fetchChatContext, normalizeChatMessage, normalizeChatMessages } from 'gimirick-replypilot';
import { type WhatsAppRawChat, type WhatsAppRawMessage } from 'gimirick-replypilot';
import { DuplicateMessageGuard, getIgnoreReason, shouldProcessMessage } from 'gimirick-replypilot';
import { type FilterableWhatsAppMessage, type IgnoreReason } from 'gimirick-replypilot';
// WhatsApp account management
import {
getActiveWhatsAppAccount,
setActiveWhatsAppAccount,
listWhatsAppAccounts,
removeWhatsAppSessionAccount,
clearActiveWhatsAppAccount,
} from 'gimirick-replypilot';
// Audio (voice note transcription)
import { oggToMp3 } from 'gimirick-replypilot';
import { transcribeCloud, transcribeLocal } from 'gimirick-replypilot';
// Queue, Logger & Metrics
import { MessageQueue, type MessageQueueOptions } from 'gimirick-replypilot';
import { createLogger, type Logger } from 'gimirick-replypilot';
import { MetricsCollector, type MetricsSnapshot } from 'gimirick-replypilot';
import { HealthServer, type HealthInfo, type HealthServerOptions } from 'gimirick-replypilot';
// Errors
import { ReplyPilotError, MissingConfigError, ConfigValidationError } from 'gimirick-replypilot';
import { ProviderResponseError, ProviderTimeoutError } from 'gimirick-replypilot';
import { ConfigNotFoundError } from 'gimirick-replypilot';CLI (Commander) β Config (Zod + conf) β LLM Provider (OpenAI SDK) β WhatsApp Client (whatsapp-web.js)
β β β
Doctor (health check) Automation (debounce/batch) Filters (ignore logic)
Audio (ffmpeg + Whisper) Context (chat history)
Metrics (in-memory counters)
Queue (p-queue concurrency)
Health Server (/health, /metrics)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CLI (Commander) β
β setup login account switch start doctor config show β
β config reset config switch logout cache clear version β
ββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββ
β Config Layer (conf + Zod) β
β βββββββββββββββ ββββββββββββ βββββββββββββββ β
β β schema.ts β β store.ts β β setup.ts β β
β β (Zod parse) β β (Conf) β β (wizard) β β
β βββββββββββββββ ββββββββββββ βββββββββββββββ β
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββ
β Runtime Orchestration β
β ββββββββββββββββββ ββββββββββββββββ ββββββββββββββββββ β
β β ReplyAutomationβ β MessageQueue β β Logger (pino) β β
β β (message flow) β β (p-queue) β β β β
β ββββββββββββββββββ€ ββββββββββββββββ€ ββββββββββββββββββ€ β
β β Metrics β β HealthServer β β β β
β β Collector β β (/health, β β β β
β β (counters) β β /metrics) β β β β
β ββββββββββββββββββ ββββββββββββββββ ββββββββββββββββββ β
ββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββ΄ββββββββββββββββββββββββββββββββββ
βΌ βΌ
ββββββββββββββββ βββββββββββββββββββββββββββββββββββββ
β WhatsApp β β LLM Provider Layer β
β Layer β β ββββββββββββββββββββββββββββββ β
β ββββββββ β β β OpenAiCompatibleProvider β β
β βclientβ β β β ββββββββββββ βββββββββ β β
β β.ts βββββΌββββ β β β prompt.tsβ βopenai β β β
β ββββββββ€ β β β β β (build, β β-compa β β β
β βfilterβ β β β β β clean) β βtible. β β β
β βs.ts β β β β β ββββββββββββ βts β β β
β ββββββββ€ β β β β β(OPenAIβ β β
β βcontexβ β βΌ β β β SDK + β β β
β βt.ts β β ββββββββββββ β β β retry)β β β
β ββββββββ β β Audio β β ββββββββββββββββββ΄ββββββββ β β
β β β Layer β β LlmProvider (interface) β β
β β ββββββββββββ βββββββββββββββββββββββββββββββββββββ
β β ββconvert ββ
β β ββ.ts ββ
β β βββββββββββ€β
β β ββtranscriββ
β β ββber.ts ββ
β β ββββββββββββ
β β ββββββββββββ
ββββββββββββββββ
The WhatsApp layer transcribes voice notes (via the Audio layer) or passes raw audio to the LLM before the message enters the main pipeline.
Each incoming WhatsApp message flows through these stages:
WhatsApp Web ββ> Client.on('message')
β
βΌ
βββββββββββββββββββββ
β toRuntimeMessage β raw Message + Chat β RuntimeIncomingMessage
βββββββββ€ββββββββββββ
β
βββββββ΄ββββββ
β β
βΌ βΌ
ββββββββββββββββββ βββββββββββββββββββββ
β image/sticker β β voice note? β
β β download β β (message.type===' β
β (retry 3Γ) β β ptt') β
β β imageData β β (retry 3Γ) β
ββββββββββββββββββ βββββ€ββββ€ββββ€ββββ€ββββ
β β β β β
mode: ignore β β β β β
βββββββββββββββ β β β β
mode: whisper_cloud β β β
β oggToMp3() β β β β
β transcribeCloud()ββββ β β
mode: whisper_local β β
β oggToMp3() β β β
β transcribeLocal()ββββββββ β
mode: native_audioβ β
β oggToMp3() β β
β audioData βββββββββββββ
β
βΌ
body = voiceBody ?? (message text or media label)
audioData passed through if native_audio
β
βΌ
βββββββββββββββββββββ
β Filtering β
βββββββββ€ββββββββββββ
β
βββββββββ΄ββββββββ
β β
βΌ βΌ
getIgnoreReason() duplicateGuard
{self, empty, {seen IDs}
group, broadcast,
status_broadcast,
archived,
voice_note_ignored}
β β
βββββββββ¬ββββββββ
[ignored] β [pass]
βΌ
ββββββββββββββββ
β MessageQueue per-chat sequential, global-parallel
ββββββββ¬ββββββββββ
βΌ
ββββββββββββββββ
β fetchContext chat history via whatsapp-web.js
ββββββββ¬ββββββββββ
βΌ
ββββββββββββββββ
β buildReplyPrompt owner style + context + incoming
β (may include + optional image/audio content parts
β input_audio part β UserContentPart[])
ββββββββ¬ββββββββββ
βΌ
ββββββββββββββββ
β llmProvider.generateReply()
β - withTimeout / retryTransient
β * retries transient errors (429, 503, 408, 504, network failures)
β * exponential backoff: 1s, 2s, 4s...
β - key rotation on persistent failure
β - ProviderTimeoutError if no response
β - cleanGeneratedReply
ββββββββ¬ββββββββββ
βΌ
ββββββββββββββββ
β dryRun? ββyesββ> log only (status: 'dry-run')
β no
βΌ
chat.sendMessage(reply) ββ> WhatsApp Web
Four modes are available, configured during replypilot setup:
| Mode | Audio Flow | LLM Receives |
|---|---|---|
whisper_cloud |
OGGβMP3 β POST /v1/audio/transcriptions β transcription text |
Transcribed text as message body |
whisper_local |
OGGβMP3 β POST /inference (whisper.cpp) β transcription text |
Transcribed text as message body |
native_audio |
OGGβMP3 β attached as {type:"input_audio", input_audio:{data,format:"mp3"}} |
Raw audio content part (multimodal model required) |
ignore |
Skipped entirely | Message labeled [voice note] |
Whisper models available for cloud mode: whisper-1 (default), gpt-4o-mini-transcribe, gpt-4o-transcribe, or a custom model name.
Media download from WhatsApp Web is retried up to 3 times with a 1-second delay between attempts, handling transient Puppeteer CDP serialization errors that can occur when decryption keys are not yet available. This applies to both image/sticker downloads and voice note downloads. If all retries fail, processing continues without the media.
OGG-to-MP3 conversion is handled by src/audio/convert.ts via ffmpeg with a 120-second timeout. Conversion failures are caught and logged; the voice note is replaced with [voice note] text.
- Global limit: max 1 concurrent LLM request (
globalConcurrency: 1). - Rate limit: optionally configurable during
replypilot setupβ caps LLM calls per minute. When set, a rolling window (60s) prevents exceeding the configured ceiling. - Per-chat serial: messages in the same chat process one at a time (
perChatConcurrency: 1). - Message batching: same-chat messages arriving within
automation.debounceMs(default 10s, configurable viareplypilot setup) are coalesced into a single LLM call. Bodies are joined with newlines; messages are sorted by timestamp before combining. Set to 0 for immediate processing (no batching). - Chat queues are created lazily (one
PQueueperchatId). - Duplicate guard tracks up to 5,000 seen message IDs, pruning oldest entries when full.
replypilot start
β
βββ loadConfig() read active named config, Zod-validate
βββ getActiveWhatsAppAccount() read active WA account (falls back to config sessionName)
βββ new OpenAiCompatibleProvider() init OpenAI SDK client(s)
β βββ fallbackApiKeys[] rotate on request failure
βββ new ReplyAutomation() wire config, provider, queue, logger
βββ new WhatsAppClientAdapter( init whatsapp-web.js (LocalAuth)
β activeAccount) using active WA account as clientId
β βββ register QR handler
β βββ register ready handler
β βββ register disconnect handler
βββ whatsapp.onMessage(handler) register pipeline entry point
βββ if healthServerPort given:
β βββ new HealthServer({port, host: '127.0.0.1', metrics})
β βββ serve GET /health + /metrics
βββ whatsapp.start()
β βββ client.initialize() Puppeteer + QR scan
β βββ wait for ready event connection synced (10s timeout)
β βββ client.on('message') attach raw message listener
βββ [waiting for messages]
| Layer | File | Role |
|---|---|---|
| CLI | cli.ts |
Commander program, 12 commands, dependency injection for testability |
| Config | schema.ts |
Zod schema, AppConfig type, defaults, parseAppConfig validation |
| Config | store.ts |
Persistent JSON store via conf, multi-config profiles, multi-WA account tracking, session dir management |
| Config | setup.ts |
Interactive @inquirer/prompts wizard (named configs, 3 providers + voice note flow) |
| Runtime | automation.ts |
ReplyAutomation orchestrator (message batching), processIncomingMessageBatch, startAutomation |
| Runtime | queue.ts |
MessageQueue wrapping p-queue with chat-scoped sub-queues and optional global rate limiting (maxCallsPerMinute) |
| Runtime | logger.ts |
Pino logger with API key redaction |
| Runtime | errors.ts |
Typed error hierarchy (MissingConfigError, ProviderTimeoutError, etc.) |
| Runtime | metrics.ts |
MetricsCollector β in-memory counters for messages, LLM calls, latency, and processing time |
| Runtime | health-server.ts |
HealthServer β optional HTTP endpoint (:port/health, :port/metrics) using Node.js built-in http |
| LLM | provider.ts |
LlmProvider interface, ChatContextMessage / GenerateReplyInput types |
| LLM | openai-compatible.ts |
OpenAI SDK adapter, per-key client creation, key rotation on failure, transient-error retry with timeout race |
| LLM | prompt.ts |
Prompt construction (buildReplyPrompt), output cleanup (cleanGeneratedReply), UserContentPart (text/image/audio) |
client.ts |
WhatsAppClientAdapter, loginWhatsAppAccount (standalone auth flow), lifecycle events, raw message β RuntimeIncomingMessage (includes voice note processing) |
|
context.ts |
Chat history fetch (fetchChatContext), message normalization, media type labels |
|
filters.ts |
getIgnoreReason (self, empty, group, broadcast, archived, voice_note, status_broadcast), DuplicateMessageGuard with FIFO pruning |
|
| Audio | convert.ts |
OGG-to-MP3 conversion via ffmpeg subprocess with timeout |
| Audio | transcriber.ts |
Cloud (transcribeCloud) and local (transcribeLocal) Whisper transcription |
| Doctor | doctor.ts |
runDoctor health checks (Node, config, provider reachability, ffmpeg availability) |
Part of the GimiRick toolchain. Founded by Mohammad Faiz.
CC BY-NC-ND 4.0 β Attribution-NonCommercial-NoDerivatives 4.0 International. See the LICENSE file for the full legal text.
ReplyPilot uses whatsapp-web.js, an unofficial WhatsApp Web automation library. You are responsible for following WhatsApp rules and local laws. Avoid spam, bulk messaging, and impersonation without consent. This tool is intended for personal automation and controlled use.