@diegoaltoworks/chatter 0.28.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,428 @@
1
+ // src/channels/whatsapp/crypto.ts
2
+ import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
3
+ var IV_LENGTH = 12;
4
+ var TAG_LENGTH = 16;
5
+ function deriveKey(secret) {
6
+ return createHash("sha256").update(secret, "utf-8").digest();
7
+ }
8
+ function encrypt(plaintext, secret) {
9
+ const iv = randomBytes(IV_LENGTH);
10
+ const cipher = createCipheriv("aes-256-gcm", deriveKey(secret), iv);
11
+ const ciphertext = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
12
+ return Buffer.concat([iv, cipher.getAuthTag(), ciphertext]).toString("base64");
13
+ }
14
+ function decrypt(payload, secret) {
15
+ const raw = Buffer.from(payload, "base64");
16
+ const iv = raw.subarray(0, IV_LENGTH);
17
+ const tag = raw.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
18
+ const ciphertext = raw.subarray(IV_LENGTH + TAG_LENGTH);
19
+ const decipher = createDecipheriv("aes-256-gcm", deriveKey(secret), iv);
20
+ decipher.setAuthTag(tag);
21
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf-8");
22
+ }
23
+
24
+ // src/channels/whatsapp/authState.ts
25
+ var schemaReady = /* @__PURE__ */ new WeakMap();
26
+ function ensureAuthSchema(client) {
27
+ let ready = schemaReady.get(client);
28
+ if (!ready) {
29
+ ready = client.execute("CREATE TABLE IF NOT EXISTS wa_auth (id TEXT PRIMARY KEY, value TEXT NOT NULL)").catch((error) => {
30
+ schemaReady.delete(client);
31
+ throw error;
32
+ });
33
+ schemaReady.set(client, ready);
34
+ }
35
+ return ready;
36
+ }
37
+ async function useTursoAuthState(db, secret, sessionId, runtime) {
38
+ await ensureAuthSchema(db);
39
+ const rowId = (id) => sessionId === "default" ? id : `s:${sessionId}/${id}`;
40
+ async function read(id) {
41
+ const result = await db.execute({
42
+ sql: "SELECT value FROM wa_auth WHERE id = ?",
43
+ args: [rowId(id)]
44
+ });
45
+ const row = result.rows[0];
46
+ if (!row) return null;
47
+ return JSON.parse(decrypt(row.value, secret), runtime.bufferJSON.reviver);
48
+ }
49
+ async function write(id, value) {
50
+ const payload = encrypt(JSON.stringify(value, runtime.bufferJSON.replacer), secret);
51
+ await db.execute({
52
+ sql: "INSERT OR REPLACE INTO wa_auth (id, value) VALUES (?, ?)",
53
+ args: [rowId(id), payload]
54
+ });
55
+ }
56
+ async function remove(id) {
57
+ await db.execute({ sql: "DELETE FROM wa_auth WHERE id = ?", args: [rowId(id)] });
58
+ }
59
+ const creds = await read("creds") ?? runtime.initAuthCreds();
60
+ return {
61
+ state: {
62
+ creds,
63
+ keys: {
64
+ get: async (type, ids) => {
65
+ const data = {};
66
+ await Promise.all(
67
+ ids.map(async (id) => {
68
+ let value = await read(`${type}-${id}`);
69
+ if (type === "app-state-sync-key" && value) {
70
+ value = runtime.appStateSyncKeyFromObject(
71
+ value
72
+ );
73
+ }
74
+ if (value) data[id] = value;
75
+ })
76
+ );
77
+ return data;
78
+ },
79
+ set: async (data) => {
80
+ const jobs = [];
81
+ for (const category of Object.keys(data)) {
82
+ const entries = data[category];
83
+ if (!entries) continue;
84
+ for (const id of Object.keys(entries)) {
85
+ const value = entries[id];
86
+ jobs.push(value ? write(`${category}-${id}`, value) : remove(`${category}-${id}`));
87
+ }
88
+ }
89
+ await Promise.all(jobs);
90
+ }
91
+ }
92
+ },
93
+ saveCreds: () => write("creds", creds),
94
+ clear: async () => {
95
+ if (sessionId === "default") {
96
+ await db.execute("DELETE FROM wa_auth WHERE id NOT LIKE 's:%'");
97
+ } else {
98
+ await db.execute({
99
+ sql: "DELETE FROM wa_auth WHERE id LIKE ?",
100
+ args: [`s:${sessionId}/%`]
101
+ });
102
+ }
103
+ }
104
+ };
105
+ }
106
+
107
+ // src/channels/whatsapp/baileys.ts
108
+ function wrapMissingBaileysError(cause) {
109
+ return new Error(
110
+ "The WhatsApp channel ('@diegoaltoworks/chatter/whatsapp') requires the optional peer dependency '@whiskeysockets/baileys', which is not installed. Install it with `bun add @whiskeysockets/baileys` (or npm/pnpm/yarn) to use this channel.",
111
+ { cause }
112
+ );
113
+ }
114
+ var cached;
115
+ function loadBaileys() {
116
+ if (!cached) {
117
+ cached = import("@whiskeysockets/baileys").catch((error) => {
118
+ cached = void 0;
119
+ throw wrapMissingBaileysError(error);
120
+ });
121
+ }
122
+ return cached;
123
+ }
124
+ function silentLogger() {
125
+ const noop = () => void 0;
126
+ const logger = {
127
+ level: "silent",
128
+ trace: noop,
129
+ debug: noop,
130
+ info: noop,
131
+ warn: noop,
132
+ error: noop,
133
+ fatal: noop
134
+ };
135
+ logger.child = () => logger;
136
+ return logger;
137
+ }
138
+
139
+ // src/channels/whatsapp/lease.ts
140
+ var LEASE_STALE_MS = 9e4;
141
+ var LEASE_HEARTBEAT_MS = 3e4;
142
+ var LEASE_WAIT_MS = 3e4;
143
+ function canAcquireLease(existing, instanceId, now, staleMs = LEASE_STALE_MS) {
144
+ if (!existing) return true;
145
+ if (existing.instanceId === instanceId) return true;
146
+ return now - existing.heartbeatAt >= staleMs;
147
+ }
148
+ var schemaReady2 = /* @__PURE__ */ new WeakMap();
149
+ function ensureWaLeaseSchema(client) {
150
+ let ready = schemaReady2.get(client);
151
+ if (!ready) {
152
+ ready = client.execute(
153
+ `CREATE TABLE IF NOT EXISTS wa_lease (
154
+ session_id TEXT PRIMARY KEY,
155
+ instance_id TEXT NOT NULL,
156
+ heartbeat_at INTEGER NOT NULL
157
+ )`
158
+ ).catch((error) => {
159
+ schemaReady2.delete(client);
160
+ throw error;
161
+ });
162
+ schemaReady2.set(client, ready);
163
+ }
164
+ return ready;
165
+ }
166
+ function createTursoWaLeaseStore(client) {
167
+ return {
168
+ async tryAcquire(sessionId, instanceId, now, staleMs) {
169
+ await ensureWaLeaseSchema(client);
170
+ const result = await client.execute({
171
+ sql: `INSERT INTO wa_lease (session_id, instance_id, heartbeat_at) VALUES (?, ?, ?)
172
+ ON CONFLICT(session_id) DO UPDATE SET
173
+ instance_id = excluded.instance_id,
174
+ heartbeat_at = excluded.heartbeat_at
175
+ WHERE wa_lease.instance_id = excluded.instance_id
176
+ OR wa_lease.heartbeat_at <= excluded.heartbeat_at - ?
177
+ RETURNING instance_id`,
178
+ args: [sessionId, instanceId, now, staleMs]
179
+ });
180
+ const row = result.rows[0];
181
+ return Boolean(row) && row.instance_id === instanceId;
182
+ },
183
+ async release(sessionId, instanceId) {
184
+ await ensureWaLeaseSchema(client);
185
+ await client.execute({
186
+ sql: "DELETE FROM wa_lease WHERE session_id = ? AND instance_id = ?",
187
+ args: [sessionId, instanceId]
188
+ });
189
+ }
190
+ };
191
+ }
192
+
193
+ // src/channels/whatsapp/channel.ts
194
+ var RECONNECT_DELAY_MS = 5e3;
195
+ var RECONNECT_MAX_DELAY_MS = 10 * 60 * 1e3;
196
+ function reconnectDelayMs(failures) {
197
+ return Math.min(RECONNECT_DELAY_MS * 2 ** Math.min(failures, 20), RECONNECT_MAX_DELAY_MS);
198
+ }
199
+ async function shutdownWaSessions(sessions) {
200
+ await Promise.all(
201
+ [...sessions.values()].map(async (handle) => {
202
+ handle.stopHeartbeat();
203
+ handle.sock.end(void 0);
204
+ await handle.release().catch(() => void 0);
205
+ })
206
+ );
207
+ }
208
+ async function loseSession(sessionId, sessions) {
209
+ const handle = sessions.get(sessionId);
210
+ if (!handle) return;
211
+ sessions.delete(sessionId);
212
+ await shutdownWaSessions(/* @__PURE__ */ new Map([[sessionId, handle]]));
213
+ }
214
+ async function acquireSessionLease(sessionId, deps) {
215
+ const { leaseStore, instanceId, sessions, isStopped, connect } = deps;
216
+ const now = deps.now ?? Date.now;
217
+ const staleMs = deps.staleMs ?? LEASE_STALE_MS;
218
+ const heartbeatMs = deps.heartbeatMs ?? LEASE_HEARTBEAT_MS;
219
+ const waitMs = deps.waitMs ?? LEASE_WAIT_MS;
220
+ const schedule = deps.schedule ?? ((fn, ms) => void setTimeout(fn, ms));
221
+ if (isStopped()) return;
222
+ const retry = () => {
223
+ schedule(
224
+ () => void acquireSessionLease(sessionId, deps).catch(
225
+ (error) => console.error(`WhatsApp[${sessionId}]: lease re-check failed:`, error)
226
+ ),
227
+ waitMs
228
+ );
229
+ };
230
+ let acquired;
231
+ try {
232
+ acquired = await leaseStore.tryAcquire(sessionId, instanceId, now(), staleMs);
233
+ } catch (error) {
234
+ console.warn(`WhatsApp[${sessionId}]: lease acquire failed, retrying:`, error);
235
+ retry();
236
+ return;
237
+ }
238
+ if (!acquired) {
239
+ console.warn(`WhatsApp[${sessionId}]: lease held by another instance, re-checking later`);
240
+ retry();
241
+ return;
242
+ }
243
+ const heartbeat = setInterval(() => {
244
+ void leaseStore.tryAcquire(sessionId, instanceId, now(), staleMs).then((stillHeld) => {
245
+ if (stillHeld || isStopped()) return;
246
+ console.error(`WhatsApp[${sessionId}]: lost the lease to another instance \u2014 disconnecting`);
247
+ void loseSession(sessionId, sessions).then(retry);
248
+ }).catch((error) => console.warn(`WhatsApp[${sessionId}]: lease heartbeat failed:`, error));
249
+ }, heartbeatMs);
250
+ if (typeof heartbeat === "object" && "unref" in heartbeat) heartbeat.unref();
251
+ sessions.set(sessionId, {
252
+ sock: { end: () => void 0 },
253
+ stopHeartbeat: () => clearInterval(heartbeat),
254
+ release: () => leaseStore.release(sessionId, instanceId)
255
+ });
256
+ try {
257
+ await connect(sessionId);
258
+ } catch (error) {
259
+ console.error(`WhatsApp[${sessionId}]: connect failed right after acquiring the lease:`, error);
260
+ await loseSession(sessionId, sessions);
261
+ retry();
262
+ }
263
+ }
264
+ function senderNameFor(channelName, sessionId) {
265
+ return sessionId === "default" ? channelName : `${channelName}:${sessionId}`;
266
+ }
267
+ function createWhatsAppChannel(config) {
268
+ const channelName = "whatsapp";
269
+ const sessionIds = config.sessionIds && config.sessionIds.length > 0 ? config.sessionIds : ["default"];
270
+ const now = config.now ?? Date.now;
271
+ const schedule = config.schedule ?? ((fn, ms) => void setTimeout(fn, ms));
272
+ const staleMs = config.staleMs ?? LEASE_STALE_MS;
273
+ const heartbeatMs = config.heartbeatMs ?? LEASE_HEARTBEAT_MS;
274
+ const waitMs = config.waitMs ?? LEASE_WAIT_MS;
275
+ const loadBaileys2 = config.loadBaileys ?? loadBaileys;
276
+ let stopped = false;
277
+ const instanceId = crypto.randomUUID();
278
+ const sessions = /* @__PURE__ */ new Map();
279
+ const failureCounts = /* @__PURE__ */ new Map();
280
+ const registeredSenderNames = /* @__PURE__ */ new Map();
281
+ let senders;
282
+ return {
283
+ name: channelName,
284
+ async start(deps) {
285
+ const db = deps.db;
286
+ const leaseStore = createTursoWaLeaseStore(db);
287
+ senders = deps.senders;
288
+ await loadBaileys2();
289
+ async function connect(sessionId) {
290
+ if (stopped || !sessions.has(sessionId)) return;
291
+ const baileys = await loadBaileys2();
292
+ const runtime = {
293
+ bufferJSON: baileys.BufferJSON,
294
+ initAuthCreds: baileys.initAuthCreds,
295
+ appStateSyncKeyFromObject: (value) => baileys.proto.Message.AppStateSyncKeyData.fromObject(value)
296
+ };
297
+ const { state, saveCreds } = await useTursoAuthState(
298
+ db,
299
+ config.sessionSecret,
300
+ sessionId,
301
+ runtime
302
+ );
303
+ if (!state.creds.registered) {
304
+ console.warn(
305
+ `WhatsApp[${sessionId}]: no paired session found \u2014 pair it first (see docs/channels.md). Re-checking in 60s.`
306
+ );
307
+ schedule(() => void connect(sessionId).catch(() => void 0), 6e4);
308
+ return;
309
+ }
310
+ const { version } = await baileys.fetchLatestBaileysVersion();
311
+ const logger = silentLogger();
312
+ const sock = baileys.makeWASocket({
313
+ version,
314
+ auth: {
315
+ creds: state.creds,
316
+ // biome-ignore lint/suspicious/noExplicitAny: silentLogger is structurally pino-compatible; Baileys' logger type is not exported
317
+ keys: baileys.makeCacheableSignalKeyStore(state.keys, logger)
318
+ },
319
+ // biome-ignore lint/suspicious/noExplicitAny: silentLogger is structurally pino-compatible; Baileys' logger type is not exported
320
+ logger,
321
+ markOnlineOnConnect: false,
322
+ syncFullHistory: false
323
+ });
324
+ const leaseHandle = sessions.get(sessionId);
325
+ if (leaseHandle) leaseHandle.sock = sock;
326
+ sock.ev.on(
327
+ "creds.update",
328
+ () => void saveCreds().catch(
329
+ (error) => console.warn(`WhatsApp[${sessionId}]: creds save failed:`, error)
330
+ )
331
+ );
332
+ sock.ev.on("connection.update", (update) => {
333
+ const { connection, lastDisconnect } = update;
334
+ if (connection === "open") {
335
+ failureCounts.set(sessionId, 0);
336
+ console.log(`WhatsApp[${sessionId}]: connected as ${sock.user?.id ?? "unknown"}`);
337
+ const senderName = senderNameFor(channelName, sessionId);
338
+ const sender = {
339
+ sendText: async (chatId, text) => {
340
+ await sock.sendMessage(chatId, { text });
341
+ }
342
+ };
343
+ deps.senders.register(senderName, sender);
344
+ registeredSenderNames.set(sessionId, senderName);
345
+ }
346
+ if (connection === "close") {
347
+ const senderName = registeredSenderNames.get(sessionId);
348
+ if (senderName) {
349
+ deps.senders.unregister(senderName);
350
+ registeredSenderNames.delete(sessionId);
351
+ }
352
+ const statusCode = lastDisconnect?.error?.output?.statusCode;
353
+ if (statusCode === baileys.DisconnectReason.loggedOut) {
354
+ console.error(
355
+ `WhatsApp[${sessionId}]: session logged out \u2014 re-pair to reconnect (see docs/channels.md).`
356
+ );
357
+ void loseSession(sessionId, sessions);
358
+ return;
359
+ }
360
+ if (!stopped && sessions.has(sessionId)) {
361
+ const delay = reconnectDelayMs(failureCounts.get(sessionId) ?? 0);
362
+ failureCounts.set(sessionId, (failureCounts.get(sessionId) ?? 0) + 1);
363
+ console.warn(
364
+ `WhatsApp[${sessionId}]: connection closed (${statusCode ?? "?"}), reconnecting in ${Math.round(delay / 1e3)}s...`
365
+ );
366
+ schedule(
367
+ () => void connect(sessionId).catch(
368
+ (error) => console.error(`WhatsApp[${sessionId}]: reconnect failed:`, error)
369
+ ),
370
+ delay
371
+ );
372
+ }
373
+ }
374
+ });
375
+ if (config.onMessage) {
376
+ sock.ev.on("messages.upsert", async ({ messages, type }) => {
377
+ if (type !== "notify") return;
378
+ for (const message of messages) {
379
+ try {
380
+ await config.onMessage?.({ sessionId, sock, message });
381
+ } catch (error) {
382
+ console.warn(`WhatsApp[${sessionId}]: onMessage handler failed:`, error);
383
+ }
384
+ }
385
+ });
386
+ }
387
+ }
388
+ for (const sessionId of sessionIds) {
389
+ void acquireSessionLease(sessionId, {
390
+ leaseStore,
391
+ instanceId,
392
+ sessions,
393
+ isStopped: () => stopped,
394
+ connect,
395
+ now,
396
+ staleMs,
397
+ heartbeatMs,
398
+ waitMs,
399
+ schedule
400
+ }).catch((error) => console.error(`WhatsApp[${sessionId}]: failed to start:`, error));
401
+ }
402
+ },
403
+ async stop() {
404
+ stopped = true;
405
+ for (const senderName of registeredSenderNames.values()) {
406
+ senders?.unregister(senderName);
407
+ }
408
+ registeredSenderNames.clear();
409
+ await shutdownWaSessions(sessions);
410
+ }
411
+ };
412
+ }
413
+ export {
414
+ LEASE_HEARTBEAT_MS,
415
+ LEASE_STALE_MS,
416
+ LEASE_WAIT_MS,
417
+ acquireSessionLease,
418
+ canAcquireLease,
419
+ createTursoWaLeaseStore,
420
+ createWhatsAppChannel,
421
+ decrypt,
422
+ encrypt,
423
+ loadBaileys,
424
+ reconnectDelayMs,
425
+ senderNameFor,
426
+ shutdownWaSessions,
427
+ useTursoAuthState
428
+ };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Deploy-overlap protection: at most one running instance may hold a given
3
+ * WhatsApp session's Baileys connection at a time.
4
+ *
5
+ * Rolling deploys can briefly run the outgoing and incoming revision side by
6
+ * side, and Baileys/WhatsApp treats a second concurrent connection as a
7
+ * takeover — it logs the first one out. The lease lives in Turso (`wa_lease`)
8
+ * so it's visible to every instance. Acquiring/renewing is a single atomic
9
+ * upsert — see `createTursoWaLeaseStore` — so two instances racing the same
10
+ * row can never both believe they hold it.
11
+ */
12
+ import type { Client } from "@libsql/client";
13
+ /** Stale after this long without a heartbeat — a crashed holder's lease frees up. */
14
+ export declare const LEASE_STALE_MS = 90000;
15
+ /** How often a holder renews its lease while connected. */
16
+ export declare const LEASE_HEARTBEAT_MS = 30000;
17
+ /** How long a non-holder waits before re-checking whether the lease freed up. */
18
+ export declare const LEASE_WAIT_MS = 30000;
19
+ export interface WaLeaseRow {
20
+ instanceId: string;
21
+ heartbeatAt: number;
22
+ }
23
+ /**
24
+ * Pure: may `instanceId` take (or keep) the lease right now, given the
25
+ * current row (null = never held)? Mirrors the SQL WHERE clause in
26
+ * `createTursoWaLeaseStore` — keep the two in sync.
27
+ */
28
+ export declare function canAcquireLease(existing: WaLeaseRow | null, instanceId: string, now: number, staleMs?: number): boolean;
29
+ export interface WaLeaseStore {
30
+ /** Atomically acquires or renews the lease; resolves true iff `instanceId` now holds it. */
31
+ tryAcquire(sessionId: string, instanceId: string, now: number, staleMs: number): Promise<boolean>;
32
+ /** Releases the lease iff still held by `instanceId` (no-op otherwise — a late release must never undo a takeover). */
33
+ release(sessionId: string, instanceId: string): Promise<void>;
34
+ }
35
+ /**
36
+ * A Turso-backed `WaLeaseStore`. The WHERE clause on DO UPDATE encodes
37
+ * `canAcquireLease`: SQLite skips the update (and RETURNING yields no row)
38
+ * when it's false, so a losing racer sees zero rows back — no separate
39
+ * read-then-write, no race window.
40
+ */
41
+ export declare function createTursoWaLeaseStore(client: Client): WaLeaseStore;
42
+ //# sourceMappingURL=lease.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lease.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/lease.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAE7C,qFAAqF;AACrF,eAAO,MAAM,cAAc,QAAS,CAAC;AACrC,2DAA2D;AAC3D,eAAO,MAAM,kBAAkB,QAAS,CAAC;AACzC,iFAAiF;AACjF,eAAO,MAAM,aAAa,QAAS,CAAC;AAEpC,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,UAAU,GAAG,IAAI,EAC3B,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,MAAuB,GAC/B,OAAO,CAIT;AAED,MAAM,WAAW,YAAY;IAC3B,4FAA4F;IAC5F,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClG,uHAAuH;IACvH,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/D;AAuBD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAyBpE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=lease.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lease.test.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/lease.test.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@diegoaltoworks/chatter",
3
- "version": "0.28.0",
3
+ "version": "0.29.0",
4
4
  "description": "Embeddable AI chatbot framework with RAG, authentication, and customizable widgets",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
7
7
  "types": "./dist/index.d.ts",
8
8
  "bin": {
9
- "chatter": "./dist/bin/create-apikey.js"
9
+ "chatter": "./dist/bin/create-apikey.js",
10
+ "wa-pair": "./dist/bin/wa-pair.js"
10
11
  },
11
12
  "exports": {
12
13
  ".": {
@@ -38,6 +39,11 @@
38
39
  "types": "./dist/channels/index.d.ts",
39
40
  "import": "./dist/channels/index.mjs",
40
41
  "require": "./dist/channels/index.js"
42
+ },
43
+ "./whatsapp": {
44
+ "types": "./dist/channels/whatsapp/index.d.ts",
45
+ "import": "./dist/channels/whatsapp/index.mjs",
46
+ "require": "./dist/channels/whatsapp/index.js"
41
47
  }
42
48
  },
43
49
  "files": [
@@ -47,7 +53,7 @@
47
53
  ],
48
54
  "scripts": {
49
55
  "dev": "bun --watch src/index.ts",
50
- "build": "bun run build:clean && bun run build:types && bun run build:esm && bun run build:cjs && bun run build:mcp && bun run build:usage && bun run build:channels && bun run build:bin && bun run build:client && bun run build:copy-client",
56
+ "build": "bun run build:clean && bun run build:types && bun run build:esm && bun run build:cjs && bun run build:mcp && bun run build:usage && bun run build:channels && bun run build:whatsapp && bun run build:bin && bun run build:client && bun run build:copy-client",
51
57
  "build:clean": "rm -rf dist",
52
58
  "build:types": "tsc --emitDeclarationOnly --outDir dist",
53
59
  "build:esm": "esbuild src/index.ts --bundle --format=esm --outfile=dist/index.mjs --external:hono --external:openai --external:@libsql/client --external:jose --platform=node --target=node24",
@@ -55,7 +61,8 @@
55
61
  "build:mcp": "esbuild src/mcp-server.ts --bundle --format=esm --outfile=dist/mcp-server.mjs --external:@modelcontextprotocol/sdk --external:openai --external:@libsql/client --external:zod --platform=node --target=node24 && esbuild src/mcp-server.ts --bundle --format=cjs --outfile=dist/mcp-server.js --external:@modelcontextprotocol/sdk --external:openai --external:@libsql/client --external:zod --platform=node --target=node24",
56
62
  "build:usage": "esbuild src/usage/index.ts --bundle --format=esm --outfile=dist/usage/index.mjs --external:@libsql/client --platform=node --target=node24 && esbuild src/usage/index.ts --bundle --format=cjs --outfile=dist/usage/index.js --external:@libsql/client --platform=node --target=node24",
57
63
  "build:channels": "esbuild src/channels/index.ts --bundle --format=esm --outfile=dist/channels/index.mjs --external:hono --external:openai --external:@libsql/client --platform=node --target=node24 && esbuild src/channels/index.ts --bundle --format=cjs --outfile=dist/channels/index.js --external:hono --external:openai --external:@libsql/client --platform=node --target=node24",
58
- "build:bin": "esbuild bin/create-apikey.ts --bundle --format=cjs --outfile=dist/bin/create-apikey.js --external:jose --platform=node --target=node18 --banner:js='#!/usr/bin/env node'",
64
+ "build:whatsapp": "esbuild src/channels/whatsapp/index.ts --bundle --format=esm --outfile=dist/channels/whatsapp/index.mjs --external:@libsql/client --external:@whiskeysockets/baileys --platform=node --target=node24 && esbuild src/channels/whatsapp/index.ts --bundle --format=cjs --outfile=dist/channels/whatsapp/index.js --external:@libsql/client --external:@whiskeysockets/baileys --platform=node --target=node24",
65
+ "build:bin": "esbuild bin/create-apikey.ts --bundle --format=cjs --outfile=dist/bin/create-apikey.js --external:jose --platform=node --target=node18 --banner:js='#!/usr/bin/env node' && esbuild bin/wa-pair.ts --bundle --format=cjs --outfile=dist/bin/wa-pair.js --external:@libsql/client --external:@whiskeysockets/baileys --external:qrcode-terminal --platform=node --target=node18 --banner:js='#!/usr/bin/env node'",
59
66
  "build:client": "cd src/client && bun run build",
60
67
  "build:copy-client": "mkdir -p dist/static && cp src/client/dist/chatter.js src/client/dist/chatter.css dist/static/",
61
68
  "test": "bun test",
@@ -108,10 +115,20 @@
108
115
  "peerDependencies": {
109
116
  "@libsql/client": "^0.17.2",
110
117
  "@modelcontextprotocol/sdk": "^1.30.0",
118
+ "@whiskeysockets/baileys": "^7.0.0-rc14",
111
119
  "hono": "^4.12.14",
112
120
  "openai": "^7.3.0",
121
+ "qrcode-terminal": "^0.12.0",
113
122
  "zod": "^3.25.76 || ^4.4.3"
114
123
  },
124
+ "peerDependenciesMeta": {
125
+ "@whiskeysockets/baileys": {
126
+ "optional": true
127
+ },
128
+ "qrcode-terminal": {
129
+ "optional": true
130
+ }
131
+ },
115
132
  "dependencies": {
116
133
  "jose": "6.2.8"
117
134
  },
@@ -121,11 +138,13 @@
121
138
  "@modelcontextprotocol/sdk": "1.30.0",
122
139
  "@types/bun": "1.3.14",
123
140
  "@types/node": "26.2.0",
141
+ "@whiskeysockets/baileys": "7.0.0-rc14",
124
142
  "esbuild": "0.28.1",
125
143
  "hono": "4.13.1",
126
144
  "husky": "9.1.7",
127
145
  "lint-staged": "17.3.0",
128
146
  "openai": "7.4.0",
147
+ "qrcode-terminal": "0.12.0",
129
148
  "typescript": "7.0.2",
130
149
  "zod": "4.4.3"
131
150
  },