bb-friend
v0.1.2
Published
Human-like TypeScript chat agent module. Plug in your OpenRouter key, database, and chat app.
Downloads
838
Maintainers
Readme
bb-friend
Drop a human-like AI chat agent into your Node app.
You keep your chat UI and database. bb-friend handles the agent: replies, typing delays, mood, secrets, optional photo shares, bot-view history, and usage cost.
Your user → your chat API / UI → bb-friend → OpenRouter (GPT)
↑ │
└──── events ──────┘ (typing, message, image)You provide: OpenRouter API key, identity (name, age, location, …), database, chat UI.
Package owns: personality, chatting goal, emotion style, timing (not configurable).
Install
npm install bb-friend better-sqlite3better-sqlite3 is only needed for the SQLite helper below. Use any DB by implementing a small storage adapter.
3-minute setup
import { createSqliteBbFriend } from "bb-friend";
const friend = createSqliteBbFriend({
database: "./chat.db", // or ":memory:"
openRouterApiKey: process.env.OPENROUTER_API_KEY!,
identity: {
name: "Alex",
age: 24,
location: "Seoul",
timezone: "Asia/Seoul",
bio: "Night walks.",
secrets: [{ key: "ex", value: "Not ready to talk about it." }],
},
});
// Wire events to your chat UI (one call)
friend.bind({
onTyping: ({ conversationId }) => showTyping(conversationId),
onMessage: ({ conversationId, text, asset }) => pushBotBubble(conversationId, text, asset),
onImage: ({ conversationId, asset }) => pushImage(conversationId, asset),
onError: ({ error }) => console.error(error),
});
// When a user sends a message in your app:
await friend.handleUserMessage({
conversationId: "chat_123",
peer: { key: "user_1", name: "Alice" },
text: "hey",
});That is the whole happy path:
- Create friend (SQLite file + schema created for you)
bindUI callbackshandleUserMessagewhen the user chats
How it fits your app
| Your job | bb-friend’s job |
| ---------------------------- | -------------------------------- |
| Render chat bubbles | Generate human-like replies |
| Auth / users / routing | Typing delays, first ping |
| Store whatever else you need | Persist chat + usage via storage |
| Operator dashboard UI | getBotView / usage totals |
Typical flow:
- User sends text in your UI
- Your backend calls
handleUserMessage - You get
typing→ thenmessage(and maybeimage) viabind/on - You push those into your UI (WebSocket, SSE, or in-process)
Operator flow:
const { me, inbox } = await friend.getBotView();
// me = the bot identity; inbox = chats with other people
const thread = await friend.getBotViewThread("chat_123");
// messages as { from: "me" | "them", content, ... }Wire to your chat UI
Events
| Event | When | What to do in UI |
| --------- | ------------------------- | --------------------------------------------- |
| typing | Bot is “typing” | Show typing indicator |
| delay | Read/type wait | Optional (timing already applied) |
| message | Bot text ready | Append bot bubble (asset if photo attached) |
| image | Photo shared | Show image (same as message.asset) |
| error | OpenRouter / agent failed | Show error state |
Use friend.bind({ ... }) or individual friend.on("message", ...).
Minimal HTTP + SSE recipe
// Inside your POST /chat/:id/messages handler (Express / Fastify / etc.)
res.setHeader("Content-Type", "text/event-stream");
const unbind = friend.bind({
onTyping: (p) => res.write(`event: typing\ndata: ${JSON.stringify(p)}\n\n`),
onMessage: (p) => res.write(`event: message\ndata: ${JSON.stringify(p)}\n\n`),
onImage: (p) => res.write(`event: image\ndata: ${JSON.stringify(p)}\n\n`),
onError: (p) =>
res.write(`event: error\ndata: ${JSON.stringify({ message: p.error.message })}\n\n`),
});
try {
await friend.handleUserMessage({
conversationId: id,
peer: { key: userId, name: userName },
text: body.text,
});
res.write(`event: done\ndata: {}\n\n`);
} finally {
unbind();
res.end();
}Same idea works with WebSockets: in each handler, socket.emit(...).
Proactive greeting
const result = await friend.ping({
conversationId: "chat_123",
peer: { key: "user_1", name: "Alice" },
});
// { sent: false, reason: "asleep" } outside awake hours in identity.timezoneBot-view & usage
await friend.getBotView();
await friend.getBotViewThread(conversationId);
await friend.getUsageSummary({ from: "2026-01-01", to: "2026-12-31" });
await friend.getUsageByConversation();Use these in an admin / ops screen (“I am the bot”, inbox of users, spend).
Identity (configurable) vs personality (fixed)
You can set: name, age, location, timezone, bio, secrets, photo assets.
You cannot set: chatting goal, personality, emotion style, timing rules.
Extra fields like personality / goal / emotion are rejected.
friend.updateIdentity({ location: "Busan" });
friend.registerAsset({
id: "selfie-1",
kind: "selfie",
urlOrPath: "/cdn/selfie-1.jpg",
caption: "lazy sunday",
});Pass assets: { resolveUrl: (id) => \https://cdn.example.com/${id}` }` in create options so shared photos get real URLs in events.
Custom database (not SQLite)
Implement BbFriendStorage (or reuse createSqlStorage with your own SQL executor):
import { createBbFriend, createMemoryStorage, getSchemaSql } from "bb-friend";
// tests
const friend = createBbFriend({
openRouter: { apiKey: process.env.OPENROUTER_API_KEY! },
identity: {/* ... */},
storage: createMemoryStorage(),
});
// production: your Postgres/Prisma adapter implementing BbFriendStorageSQLite table helper: getSchemaSql() creates bb_conversations, bb_messages, bb_usage_events.
Advanced path (manual SQLite):
import Database from "better-sqlite3";
import { createBbFriend, createSqlStorage, getSchemaSql } from "bb-friend";
const db = new Database("./chat.db");
db.exec(getSchemaSql());
const friend = createBbFriend({
openRouter: { apiKey: "..." },
identity: {/* ... */},
storage: createSqlStorage(db),
});Prefer createSqliteBbFriend unless you need a custom adapter.
API cheat sheet
| API | Purpose |
| ---------------------------------------------------------------- | -------------------------- |
| createSqliteBbFriend({ database, openRouterApiKey, identity }) | Fastest SQLite setup |
| createBbFriend({ openRouter, identity, storage }) | Full control |
| friend.bind(handlers) | Wire UI events in one call |
| friend.handleUserMessage({ conversationId, peer, text }) | User → bot reply |
| friend.ping({ conversationId, peer? }) | First greeting / check-in |
| friend.updateIdentity / registerAsset | Identity & photos |
| friend.getBotView / getBotViewThread | Operator inbox |
| friend.getUsageSummary / getUsageByConversation | Cost / tokens |
| friend.on / off | Individual events |
TypeScript: ESM + CJS + .d.ts included. No @types/bb-friend needed.
More examples
Interactive host app (Fastify + Vite): github.com/CByBB/bb-friend
License: MIT
