A transport-agnostic bot engine: declarative flows to get going in 30 seconds, an async conversation runtime when you need full control, hooks/plugins/DI and a scheduler. You inject the transport (WhatsApp through @botmesh/baileys; Telegram/Discord use the same IProvider contract).
v2 is a transport-agnostic rewrite. If you come from v1 (
BaileysClass), read the migration guide.
npm i @botmesh/core@alpha @botmesh/baileys@alpha
Requires Node >= 22 (ESM-only). The core carries no transport dependency — the transport package you pick brings its own.
import { Bot, flow } from '@botmesh/core'
import { BaileysProvider } from '@botmesh/baileys'
const bot = new Bot({
provider: new BaileysProvider({
session: 'my-bot',
qr: { terminal: true }, // print the QR in the terminal
}),
})
bot.when('hola', (ctx) => ctx.reply('👋 Hello!'))
bot.use(
flow('signup')
.say('📝 Let us sign you up')
.ask('Your name?', 'name')
.say((_ctx, c) => `✅ Hi ${c.state.name}`)
)
// Advanced: async conversation with full control
bot.conversation('support', async (c, ctx) => {
await ctx.reply('Describe your problem')
const problem = await c.waitText({ timeout: '2m' })
await ctx.reply(`🎫 Logged: ${problem}`)
})
await bot.start() // scan the QR
A full example (commands, media, hooks, DI, scheduler) lives in packages/baileys-provider/examples/bot.ts.
No camera? Set pairingPhoneNumber: '+57300XXXXXXX' on BaileysProvider (full international format) and listen for bot.on('qr', ...) — the pairing code arrives there too.
Persistent flows:
flow(...).persistent()only survives a restart with a persistent store. The defaultMemoryStorelives in RAM; to keep checkpoints use a disk-backed store (e.g.@botmesh/sqlite-store).
IProvider normalizes input (InboundEvent) and output (OutboundContent); each transport resolves its own identity internally.flow().say().ask().choose() compiles to a real bot.conversation./reset) > active conversation > matchers (normalized equals by default, or contains/startsWith/endsWith) > catch-all.c.ask, c.waitText, c.waitMedia, c.waitLocation, c.choose, with validation, retries and per-wait timeouts.type: 'buttons' message with ctx.acknowledge() (no-op on WhatsApp); on transports with interactionsRequireAck (Discord) the core acks on its own.ConfigValidationError with a clear message.config.groupMode: 'all' | 'mentions' | 'commands'.ctx.timestamp is epoch milliseconds everywhere, and a text longer than the channel allows (capabilities.maxTextLength / maxTextBytes) is split into several messages at paragraph, sentence or word boundaries, never inside an emoji or the markup (splitLongText: false opts out). Provider authors check the contract with assertInboundContract and assertCapabilityContract from @botmesh/core/testing.bot.stop({ drain: true }) waits for in-flight sends.createTestBot() from @botmesh/core/testing, with a v2 MockProvider and assertions over the outbox.before/after/onError, bot.plug()/unplug(), bot.provide().bot.schedule() (cron), bot.every(), bot.delay().Note: the WhatsApp management sub-APIs (
bot.groups/profile/privacy/chats/presence) moved to the transport and will be re-exposed as provider namespaces soon.
| Import from | For |
|---|---|
@botmesh/core |
Applications: Bot, the flow DSL, message types, options and errors. |
@botmesh/core/testing |
Tests: createTestBot, MockProvider, chaos kit, golden transcripts, MemoryCoordinator. |
@botmesh/core/spi |
Authors of stores, durability backends, schedule stores, coordinators, tracer integrations and transports (KvDurability, MemoryKv, MemoryScheduleStore, messageKey, toInboundLocation, record types). Covered by semver. |
@botmesh/core/dev |
The botmesh dev terminal simulator. |
@botmesh/core/internal |
Engine classes and helpers. No semver guarantee. |
Coming from an earlier alpha? See Migrating to the stable API.
The default MemoryStore is ideal for development. For production use the @botmesh/sqlite-store package:
import { Bot } from '@botmesh/core'
import { BaileysProvider } from '@botmesh/baileys'
import { SQLiteStore } from '@botmesh/sqlite-store'
const bot = new Bot({
provider: new BaileysProvider({ session: 'my-bot' }),
store: new SQLiteStore({ file: './data/bot.db' }),
})
const bot = new Bot({
provider,
groupMode: 'mentions', // only answer when @mentioned
// groupMode: 'commands', // only answer commands
// groupMode: 'all', // answer everything (default)
})
pnpm docs:api (output in docs/api/)Inspired by ideas and code from bot-whatsapp by codigoencasa.
MIT © Andres Aya