npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@shoru/kitten

v0.0.4

Published

A powerful Node.js framework to simplify making WhatsApp Bots

Readme

Kitten Framework

A powerful Node.js framework to simplify making WhatsApp Bots. built on top of the Baileys library. 😺

State npm License


Table of Contents

  1. Introduction
  2. Getting Started
  3. Configuration
  4. Plugin System
  5. Message Formatting
  6. Session Management
  7. Utilities
  8. API Reference

Introduction

Kitten simplifies WhatsApp bot development by providing:

  • Persistent Sessions — LMDB-powered storage with automatic recovery
  • Smart Reconnection — Exponential backoff with configurable retry limits
  • Plugin System — File-based plugins with Hot Module Replacement
  • Multi-Session — Automatic restoration of all saved sessions
  • Flexible Auth — QR code or pairing code authentication

Getting Started

Installation

npm install @shoru/kitten

Basic Usage

import { getClient } from '@shoru/kitten';

const { sock, session, id } = await getClient();

sock.ev.on('messages.upsert', async ({ messages }) => {
  const msg = messages[0];
  if (msg.message?.conversation === 'ping') {
    await sock.sendMessage(msg.key.remoteJid, { text: 'pong!' });
  }
});

Note: getClient() automatically restores all previously saved sessions in the background.

With Options

import { getClient } from '@shoru/kitten';

const { sock, session, id } = await getClient({
  id: 0,                    // Session ID (auto-generated if omitted)
  maxRetries: 30,           // Reconnection attempts
  silent: false,            // Suppress output
  socketConfig: {},         // Baileys socket overrides
  
  // Callbacks
  onConnect: ({ client }) => {
    console.log(`Connected with session ${client.id}`);
  },
  onReconnect: ({ client, attempts }) => {
    console.log(`Reconnected after ${attempts} attempts`);
  },
  onDisconnect: ({ message, statusCode, recoverable }) => {
    console.log(`Disconnected: ${message}`);
  },
  onStateChange: ({ oldState, newState }) => {
    console.log(`State: ${oldState} → ${newState}`);
  }
});

Custom Authentication

import qrcode from 'qrcode-terminal';

const { sock, id } = await getClient({
  onPairing: async ({ qr, requestPairingCode }) => {
    // Option 1: Display QR code
    console.log('Scan QR:');
    qrcode.generate(qr, { small: true });
    
    // Option 2: Use pairing code
    const code = await requestPairingCode('1234567890');
    console.log('Enter this code in WhatsApp:', code);
  }
});

Return Value

const { sock, session, id } = await getClient();

sock                      // Baileys WASocket instance
id                        // Numeric session identifier
session.id                // Same as id
await session.delete()    // Remove session from database
await session.clear()     // Clear keys, keep credentials

Configuration

Kitten uses cosmiconfig. Create one of:

  • kittenwa.config.js
  • kittenwa.config.mjs
  • .kittenwarc.json
  • package.json"kittenwa": {}

Default Values

export default {
  socket: {
    browser: Browsers.ubuntu('Chrome'),
    markOnlineOnConnect: false,
    syncFullHistory: false,
    generateHighQualityLinkPreview: true,
    logger: pino({ level: 'silent' })
  },

  db: {
    path: './db',
    compression: true,
    mapSize: 2 * 1024 * 1024 * 1024  // 2GB
  },

  plugins: {
    dir: 'plugins',
    prefixes: ['.', '\\', '!'],
    defaultEvent: 'messages.upsert',
    hmr: {
      enable: false,
      debounce: 200,
      debug: false
    }
  },

  timeZone: 'Africa/Casablanca'
}

Custom Configuration

// kittenwa.config.js
export default {
  plugins: {
    dir: 'src/plugins',
    prefixes: ['/', '!'],
    hmr: { enable: true }
  },
  timeZone: 'America/New_York'
};

Plugin System

File-based plugins with automatic loading and optional Hot Module Replacement.

Directory Structure

plugins/
├── greetings/
│   ├── hello.js
│   └── goodbye.js
├── admin/
│   └── ban.js
└── utils.js

Basic Plugin

// plugins/ping.js
export function ping(sock, ctx, event) {
  if (ctx.body === '!ping') {
    sock.sendMessage(ctx.roomId, { text: 'pong!' });
  }
}

Plugin with Options

// plugins/commands.js
export const help = async (sock, ctx) => {
  await sock.sendMessage(ctx.roomId, { 
    text: 'Available: !help, !ping, !info' 
  });
}

help.match: ['help', 'h', '?'];
help.prefix: ['!', '/'];
help.events: ['messages.upsert'];
help.enabled: true;

Options Reference

| Option | Type | Default | Description | |--------|------|---------|-------------| | match | (string \| RegExp)[] | [] | Command triggers | | prefix | string[] \| false | Config value | Required prefixes | | events | string[] | ['messages.upsert'] | Events to listen | | enabled | boolean | true | Toggle plugin |

Regex Matching

export const urlDetector = (sock, ctx) => {
  const match = ctx._match.match;  // RegExp exec result
  console.log('URL found:', match[0]);
}

urlDetector.match = [/https?:\/\/\S+/gi];

Match Context

When a command matches, ctx._match is added:

// User sends: "!help me"
ctx._match = {
  match: 'help',    // Matched string or RegExp result
  prefix: '!'       // Used prefix (null for regex)
}

Supported Events

messages.upsert          messages.update         messages.delete
messages.reaction        message-receipt.update  messaging-history.set
chats.upsert             chats.update            chats.delete
contacts.upsert          contacts.update         groups.update
group-participants.update                        presence.update
connection.update        creds.update            call
blocklist.set            blocklist.update

Hot Module Replacement

Enable for development:

// kittenwa.config.js
export default {
  plugins: {
    hmr: {
      enable: true,
      debounce: 200,
      debug: true
    }
  }
};

Console output:

[HMR] Added: greetings/hello.js (2)
[HMR] Reloaded: commands.js (3)
[HMR] Unloaded: old-plugin.js (1)

Naming Rules

| Pattern | Status | |---------|--------| | command.js | ✅ Loaded | | command.ts | ✅ Loaded | | _helper.js | ❌ Ignored | | types.d.ts | ❌ Ignored |


Message Formatting

Normalize raw Baileys messages into a consistent structure.

Usage

import { formatter } from '@shoru/kitten';

sock.ev.on('messages.upsert', ({ messages }) => {
  const msg = formatter(sock, messages[0]);
  
  console.log(msg.name);      // Sender name
  console.log(msg.body);      // Text content
  console.log(msg.isGroup);   // Boolean
});

Message Structure

{
  // Identity
  type: 'conversation',
  id: 'ABC123',
  name: 'John',
  jid: '[email protected]',
  fromMe: false,
  
  // Chat
  roomId: '[email protected]',
  isGroup: false,
  broadcast: false,
  
  // Content
  body: 'Hello!',
  mentions: [],
  mimetype: 'image/jpeg',
  fileName: 'photo.jpg',
  fileLength: 12345,
  hash: 'abc123...',
  isViewOnce: false,
  thumbnail: Buffer,
  
  // Forwarding
  isForwarded: false,
  forwardingScore: 0,
  
  // Timing
  timestamp: 1699999999,
  timeString: ['November 14, 2023', '12:26:39'],
  
  // Device
  device: 'android',
  isLid: false,
  
  // Raw data
  key: { ... },
  raw: { ... },
  contextInfo: { ... },
  
  // Methods
  async load() → Buffer,
  senderIs(jid) → boolean,
  async pn() → string,
  
  // Quoted message (when replying)
  quoted: { /* same structure */ }
}

Common Patterns

// Download media
if (msg.mimetype?.startsWith('image/')) {
  const buffer = await msg.load();
  fs.writeFileSync('image.jpg', buffer);
}

// Handle replies
if (msg.quoted) {
  console.log('Replying to:', msg.quoted.body);
}

// Check sender
if (msg.senderIs(adminJid)) {
  // Execute admin command
}

// Get phone number
const phone = await msg.pn();

Session Management

Sessions persist automatically in LMDB and are restored on startup.

Automatic Restoration

When you call getClient(), all previously saved sessions are automatically restored in the background. No manual iteration required.

import { getClient } from '@shoru/kitten';

// This connects the current session AND restores all others
const { sock } = await getClient({ id: 0 });

Session Utilities

import { listSessions, sessionExists } from '@shoru/kitten';

// List all saved session IDs
const ids = listSessions();  // [0, 1, 2]

// Check if a session exists
if (sessionExists(1)) {
  console.log('Session 1 exists');
}

Session Operations

const { session } = await getClient();

session.id                // Numeric identifier
await session.clear()     // Clear keys, keep credentials
await session.delete()    // Remove entire session

Utilities

Logger

import { logger } from '@shoru/kitten';

logger.info('Information');
logger.debug({ data }, 'Context');
logger.warn('Warning');
logger.error(err, 'Error');
logger.prompt('Direct output');

Database

import { LMDBManager } from '@shoru/kitten';

const { db } = LMDBManager;

const value = db.get('key');
await db.put('key', 'value');
await db.remove('key');

LMDBManager.isOpen      // Boolean
await LMDBManager.close();

Configuration

import { getConfig } from '@shoru/kitten';

const config = await getConfig();
console.log(config.plugins.dir);

Serialization

import { serialize, deserialize } from '@shoru/kitten';

const json = serialize({ buffer: Buffer.from('hello') });
const data = deserialize(json);

Time Formatting

import { getTimeString } from '@shoru/kitten';

const [date, time] = getTimeString(1699999999);
// ['November 14, 2023', '12:26:39']

Type Helpers

import { isString, toNumber, toBase64 } from '@shoru/kitten';

isString('hello')              // true
toNumber(BigInt(123))          // 123
toBase64(Buffer.from('hi'))    // 'aGk='

Phone Number

import { getPN } from '@shoru/kitten';

const phone = await getPN(sock, jid);  // '1234567890'

Spinner

import { spinner, pauseSpinner } from '@shoru/kitten';

spinner.start('Loading...');
spinner.stop();

// Pause during prompts
const result = await pauseSpinner(async () => {
  return await askUser();
});

API Reference

Primary

import { getClient } from '@shoru/kitten';

await getClient(options?) → { sock, session, id }

Sessions

import { listSessions, sessionExists } from '@shoru/kitten';

listSessions() → number[]
sessionExists(id) → boolean

Formatting

import { formatter } from '@shoru/kitten';

formatter(sock, rawMessage, eventName?) → FormattedMessage

Infrastructure

import { 
  getConfig,
  logger,
  LMDBManager,
  spinner,
  pauseSpinner
} from '@shoru/kitten';

Utilities

import {
  serialize,
  deserialize,
  getTimeString,
  isString,
  toNumber,
  toBase64,
  getPN
} from '@shoru/kitten';

Complete Example

import { getClient, logger } from '@shoru/kitten';

const { sock, id } = await getClient({
  id: 0,
  maxRetries: 15,
  onConnect: ({ client }) => {
    logger.info(`Session ${client.id} connected`);
  },
  onDisconnect: ({ message, recoverable }) => {
    logger.warn(`Disconnected: ${message} (recoverable: ${recoverable})`);
  }
});

logger.info(`Running as session ${id}`);

Made with ❤️ for Whatsapp Community by Aymane Shoru