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

@kyyinfinite/forge

v1.0.0

Published

Forge — A modern WhatsApp Framework built on Baileys

Downloads

16

Readme

@kyyinfinite/forge

Forge — A modern WhatsApp framework built on Baileys.

Forge wraps the Baileys engine with a clean, chainable API for building interactive WhatsApp bots. It does not replace Baileys. It does not reimplement any protocol. It gives you a better interface over the same engine.

Install

npm install @kyyinfinite/forge

Requires Node.js >= 20 and a working @whiskeysockets/baileys installation.

Features

  • Clean chainable builder API for buttons, carousels, and AI rich responses
  • Full WhatsApp native flow protocol support (interactive messages, selections, CTAs)
  • AI Rich Response with markdown, code highlighting, tables, images, video, reels, and more
  • Carousel messages with per-card media headers
  • ButtonV2 (legacy buttons message format)
  • Media toolkit: resize, fetch, resolve, upload, mp4 duration/preview
  • Manual TypeScript declarations for every public API
  • Zero lock-in — engine methods remain available alongside every alias
  • ES Module, tree-shakeable, Node.js 20+

Quick Start

  • If you use Baileys Costume, use an alias when installing the dependencies, for example like the code below
{
  "name": "my-project",
  "version": "1.0.0",
  "description": "",
  "type": "module",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "dev": "node --watch index.js"
  },
  "dependencies": {
     "@whiskeysockets/baileys": "npm:@kyyinfinite/baileys"
      }
  "author": "",
  "license": "MIT"
}
import makeWASocket from '@whiskeysockets/baileys';
import { Bot } from '@kyyinfinite/forge';

const sock = makeWASocket({ /* your config */ });
const bot = new Bot(sock);

sock.ev.on('messages.upsert', async ({ messages }) => {
  const msg = messages[0];
  if (!msg.message) return;

  const jid = msg.key.remoteJid;

  await bot.text(jid, 'Hello from Forge!');
});

Basic Usage

Bot is your entry point. Pass it an active Baileys socket once and use it everywhere.

const bot = new Bot(sock);

// Plain messages
await bot.text(jid, 'Hello');
await bot.image(jid, 'https://example.com/photo.jpg', { caption: 'A photo' });
await bot.video(jid, './clip.mp4', { caption: 'Watch this' });
await bot.audio(jid, './voice.ogg', { ptt: true });
await bot.document(jid, './report.pdf', { fileName: 'report.pdf' });
await bot.sticker(jid, './sticker.webp');

// Utility
await bot.reply(jid, 'Got it!', quotedMessage);
await bot.react(jid, quotedMessage.key, '👍');
await bot.delete(jid, quotedMessage.key);
await bot.edit(jid, sentMessage.key, 'Updated text');
await bot.forward(jid, quotedMessage);       // shows "Forwarded"
await bot.copy(jid, quotedMessage);          // no "Forwarded" label

// Location and contact
await bot.location(jid, 37.7749, -122.4194, { name: 'San Francisco' });
await bot.contact(jid, vcardString, { name: 'John Doe' });

// Any message can be sent as a reply using the quoted option
await bot.image(jid, './photo.jpg', { caption: 'Here you go', quoted: msg });

Button

Interactive messages with native flow. Chain methods, call .send().

await bot.button()
  .title('Choose a plan')
  .body('Select the plan that works for you.')
  .footer('Powered by Forge')
  .image('https://example.com/banner.jpg')
  .reply('Starter', 'plan_starter')
  .reply('Pro', 'plan_pro')
  .url('View pricing', 'https://example.com/pricing', 'FULL')
  .send(jid);

Reply buttons

await bot.button()
  .body('What would you like to do?')
  .reply('Track order', 'track')
  .reply('Contact support', 'support')
  .reply('Cancel order', 'cancel')
  .send(jid);

CTA buttons

await bot.button()
  .body('Ready to proceed?')
  .url('Open website', 'https://example.com', 'FULL')
  .copy('Copy code', 'PROMO2025')
  .call('Call us', '+15550001234')
  .send(jid);

Selection list

await bot.button()
  .body('Choose a category')
  .footer('Scroll to see all options')
  .selection('Browse categories')
    .section('Electronics')
      .row('', 'Phones', 'Latest smartphones', 'cat_phones')
      .row('', 'Laptops', 'Work and gaming', 'cat_laptops')
    .section('Clothing')
      .row('', 'Men', 'Men\'s collection', 'cat_men')
      .row('', 'Women', 'Women\'s collection', 'cat_women')
  .send(jid);

Send as card (for Carousel)

const card = await bot.button()
  .title('Product name')
  .body('Description here')
  .image('https://example.com/product.jpg')
  .reply('Buy now', 'buy_1')
  .toCard();

ButtonV2

Legacy button format using buttonsMessage.

await bot.buttonV2()
  .body('Pick one')
  .footer('Only one choice allowed')
  .thumbnail('https://example.com/thumb.jpg')
  .button('Option A', 'opt_a')
  .button('Option B', 'opt_b')
  .button('Option C', 'opt_c')
  .send(jid);

Carousel

A horizontally scrollable set of cards. Each card is built with bot.button().toCard().

const card1 = await bot.button()
  .title('Nike Air Max')
  .body('Classic streetwear comfort')
  .footer('$129')
  .image('https://example.com/shoe1.jpg')
  .reply('Add to cart', 'cart_1')
  .url('View details', 'https://example.com/shoe1', 'FULL')
  .toCard();

const card2 = await bot.button()
  .title('Adidas Ultraboost')
  .body('Performance running shoe')
  .footer('$189')
  .image('https://example.com/shoe2.jpg')
  .reply('Add to cart', 'cart_2')
  .url('View details', 'https://example.com/shoe2', 'FULL')
  .toCard();

await bot.carousel()
  .body('Our top picks for you')
  .card(card1)
  .card(card2)
  .send(jid);

You can also pass an array of cards at once:

await bot.carousel()
  .card([card1, card2, card3])
  .send(jid);

AI Rich

The most powerful builder. Composes rich AI-style responses with mixed content primitives. All primitives are chainable. Lazy promises are resolved automatically at send time.

await bot.ai()
  .title('Forge AI')
  .text('Here is a summary of your request.')
  .code('javascript', `function greet(name) {\n  return 'Hello, ' + name;\n}`)
  .suggest(['Tell me more', 'Start over', 'Help'])
  .send(jid);

Text with inline entities

await bot.ai()
  .text('Visit [OpenAI](https://openai.com) for more details.')
  .text('This result cites [](https://wikipedia.org/wiki/AI).')
  .send(jid);

Code block

await bot.ai()
  .text('Here is a sorting function:')
  .code('python', `def bubble_sort(arr):\n    n = len(arr)\n    for i in range(n):\n        for j in range(0, n-i-1):\n            if arr[j] > arr[j+1]:\n                arr[j], arr[j+1] = arr[j+1], arr[j]\n    return arr`)
  .send(jid);

Table

await bot.ai()
  .text('Comparison table:')
  .table([
    ['Feature',   'Starter', 'Pro',       'Enterprise'],
    ['Messages',  '1,000',   '10,000',    'Unlimited'],
    ['AI Rich',   '✗',       '✓',         '✓'],
    ['Carousel',  '✗',       '✓',         '✓'],
    ['Support',   'Email',   'Priority',  'Dedicated'],
  ])
  .send(jid);

Images

await bot.ai()
  .text('Generated artwork:')
  .image('https://example.com/art.jpg')
  .send(jid);

// Multiple images
await bot.ai()
  .image(['https://example.com/1.jpg', 'https://example.com/2.jpg'])
  .send(jid);

Video

// Simple URL
await bot.ai()
  .video('https://example.com/clip.mp4')
  .send(jid);

// Auto-extract duration and thumbnail from the buffer
await bot.ai()
  .video('https://example.com/clip.mp4', { autoFill: true })
  .send(jid);

// Manual metadata
await bot.ai()
  .video({ url: 'https://example.com/clip.mp4', duration: 30, mime_type: 'video/mp4' })
  .send(jid);

Search sources

await bot.ai()
  .text('Based on these sources:')
  .source([
    ['https://google.com/favicon.ico', 'https://google.com/article', 'Google — AI Overview'],
    ['https://wiki.org/favicon.ico',   'https://wikipedia.org/wiki/AI', 'Wikipedia — Artificial intelligence'],
  ])
  .send(jid);

Suggestions

// Single
await bot.ai()
  .suggest('Tell me more')
  .send(jid);

// Multiple — horizontal scroll
await bot.ai()
  .suggest(['Summarize', 'Translate', 'Explain simply', 'Give examples'], { scroll: true })
  .send(jid);

// Multiple — action row (no scroll)
await bot.ai()
  .suggest(['Yes', 'No', 'Maybe'], { scroll: false })
  .send(jid);

Products

await bot.ai()
  .text('Here are some recommendations:')
  .product([
    { image: 'https://example.com/p1.jpg', title: 'Widget A', price: '$19.99' },
    { image: 'https://example.com/p2.jpg', title: 'Widget B', price: '$34.99' },
  ])
  .send(jid);

Build options

// Forwarded message appearance
await bot.ai()
  .text('Forwarded answer')
  .send(jid, { forwarded: true });

// With notification transparency metadata
await bot.ai()
  .text('System notification')
  .send(jid, { notification: true });

// Quote another message
await bot.ai()
  .text('In reply to your question:')
  .send(jid, { quoted: originalMessage });

Toolkit

Static utilities used internally. Also available for your own media pipelines.

import { Toolkit } from '@kyyinfinite/forge';

// Resize an image buffer
const resized = await Toolkit.resize(buffer, 300, 300);

// Download any URL as a Buffer
const buf = await Toolkit.fetchBuffer('https://example.com/photo.jpg');

// Download silently (returns empty Buffer on failure instead of throwing)
const buf = await Toolkit.fetchBuffer(url, {}, { silent: true });

// Upload a Buffer to the WhatsApp CDN, get back a WA CDN URL
const cdnUrl = await Toolkit.toUrl(sock, imageBuffer, 'image');

// Resolve any media input to a CDN URL
const url = await Toolkit.resolveMedia(sock, 'https://example.com/img.jpg', 'image', {
  result: 'url',
});

// Resolve and resize
const url = await Toolkit.resolveMedia(sock, buffer, 'image', {
  resize: true, width: 640, height: 480, result: 'url',
});

// Get MP4 duration in seconds
const duration = Toolkit.getMp4Duration(mp4Buffer);

// Extract a frame from an MP4
const frame = await Toolkit.getMp4Preview(mp4Buffer, { result: 'buffer' });

// Parse inline entities from text
const { text, inline_entities } = Toolkit.extractIE(
  'Check out [this link](https://example.com)'
);

Migration

From raw Baileys

// Before
await sock.sendMessage(jid, {
  viewOnceMessage: {
    message: {
      buttonsMessage: {
        contentText: 'Pick one',
        buttons: [
          { buttonId: 'opt_a', buttonText: { displayText: 'Option A' }, type: 1 },
        ],
      },
    },
  },
});

// After
await bot.buttonV2()
  .body('Pick one')
  .button('Option A', 'opt_a')
  .send(jid);

From the engine directly

Every engine method is still available. You only need to update the method names if you want the cleaner API.

// Engine API (still works)
const btn = new Button(sock);
btn.setTitle('Hello').addReply('Yes', 'yes');

// Wrapper API (same result)
const btn = bot.button();
btn.title('Hello').reply('Yes', 'yes');

FAQ

Does this change any WhatsApp protocol behavior?
No. Every builder delegates 100% of execution to the engine. The generated WAProto payloads are identical.

Can I mix engine methods and wrapper aliases in the same chain?
Yes. Because the wrapper extends the engine class, both sets of methods exist on the same instance.

await bot.button()
  .title('Mixed')         // wrapper alias
  .addReply('Ok', 'ok')   // engine method
  .send(jid);

Does bot.ai() use sendMessage?
No. AIRich.send() calls relayMessage() directly with the raw botForwardedMessage content. This is intentional and required by the protocol.

How do I access the raw message output without sending?
Use .build() instead of .send().

const msg = await bot.button().title('Hello').reply('Ok', 'ok').build(jid);
// msg is a WAMessage — inspect or relay manually

Can I use Toolkit independently from Bot?
Yes. Import it directly:

import { Toolkit } from '@kyyinfinite/forge';

Best Practice

Always await .send(). It resolves all lazy media promises and performs uploads before sending.

// Correct
await bot.ai().video('https://example.com/clip.mp4', { autoFill: true }).send(jid);

// Wrong — fire-and-forget drops errors and may cause partial sends
bot.ai().video('https://example.com/clip.mp4').send(jid);

Build carousel cards before constructing the carousel, since toCard() performs media uploads.

// Correct — upload cards first, then build the carousel
const cards = await Promise.all([
  bot.button().image(url1).reply('Buy', 'b1').toCard(),
  bot.button().image(url2).reply('Buy', 'b2').toCard(),
]);
await bot.carousel().card(cards).send(jid);

Reuse the Bot instance. It is stateless — you can share one instance across your entire application.

// Create once
export const bot = new Bot(sock);

// Use anywhere
import { bot } from './bot.js';
await bot.text(jid, 'Hello');