npm package: @hareeshgg/funnl
Description: AI-powered social media engagement SDK for Meta (Instagram + Threads) with Gemini and OpenAI support.
Latest version: 1.0.0

# Funnl SDK

**AI-powered social media engagement SDK for Meta (Instagram + Threads).**

Funnl extracts the core intelligence from a production social media agent into a clean, framework-agnostic library. Plug it into Express, Fastify, Koa, or any Node.js backend—the SDK handles AI-generated replies, webhook parsing, and Meta API delivery.

---

## Features

- 🤖 **AI-Powered Replies** — Gemini (comment rapport) + OpenAI (hybrid DM engine)
- 📸 **Instagram** — DM auto-replies, comment-to-comment engagement funnels
- 🧵 **Threads** — Comment replies, mention handling, automated posting (TEXT, IMAGE, VIDEO)
- 🔌 **Framework-Agnostic** — No Express dependency. Works with any HTTP framework.
- 🗄️ **Pluggable Storage** — In-memory default, or bring your own (Prisma, MongoDB, Redis, etc.)
- 🎯 **Lead Extraction** — Automatic contact info + intent extraction from conversations
- 🛡️ **Webhook Safety** — Built-in deduplication, self-reply filtering, and challenge verification
- ⚙️ **Config-Driven** — No `.env` reads. Pass credentials via config object.

---

## Installation

```bash
# From the project root (local development)
npm install ./funnl

# Or after publishing to npm
npm install funnl
```

### Peer Dependencies

Install the AI providers you plan to use:

```bash
# Required
npm install axios

# For Gemini AI (comment replies)
npm install @google/generative-ai

# For OpenAI (hybrid engine + lead extraction)
npm install openai
```

---

## Quick Start

```javascript
const express = require('express');
const Funnl = require('funnl');

const agent = new Funnl({
  meta: {
    accessToken: 'your-instagram-page-access-token',
    webhookVerifyToken: 'your-webhook-verify-token',
  },
  ai: {
    geminiApiKey: 'your-gemini-api-key',
  },
});

const app = express();
app.use(express.json());

// Webhook verification
app.get('/webhooks/instagram', (req, res) => {
  const result = agent.verifyWebhook(req.query);
  if (result.verified) return res.status(200).send(result.challenge);
  return res.sendStatus(403);
});

// Handle incoming webhooks
app.post('/webhooks/instagram', async (req, res) => {
  res.status(200).send('EVENT_RECEIVED');
  await agent.handleWebhook(req.body, 'instagram');
});

app.listen(3000, () => console.log('Running on :3000'));
```

---

## Configuration

```javascript
const agent = new Funnl({
  // ── Meta Platform Credentials (required) ──
  meta: {
    accessToken: '...',              // Instagram Page Access Token (required)
    threadsAccessToken: '...',       // Threads token (optional, falls back to accessToken)
    instagramAccountId: '...',       // IG Business Account ID (for self-reply filtering)
    facebookPageId: '...',           // Facebook Page ID (optional)
    botUsername: '...',              // Threads bot username (for self-reply filtering)
    webhookVerifyToken: '...',      // For Meta webhook challenge verification
  },

  // ── AI Provider Credentials (optional) ──
  ai: {
    geminiApiKey: '...',             // Google Gemini API key
    geminiModel: 'gemini-2.5-flash-lite',  // Gemini model (optional)
    openaiApiKey: '...',             // OpenAI API key (optional)
    openaiModel: 'gpt-4-turbo-preview',    // OpenAI model (optional)
  },

  // ── Storage (optional, defaults to in-memory) ──
  storage: new MyCustomAdapter(),

  // ── Logger (optional, defaults to console) ──
  logger: winstonLogger,

  // ── Options ──
  options: {
    humanize: true,                  // Enable typing delays (default: true)
    humanizeDelayMs: { min: 2000, max: 6000 },  // Typing delay range
    longDelay: { min: 1, max: 5 },   // Long delay in minutes (null = disabled)
    deduplicationTTL: 300000,        // 5 min dedup cache (default)
    onLeadQualified: async (lead) => {
      // Called when a lead reaches 'qualified' or 'hot' stage
      await myCRM.syncLead(lead);
    },
  },
});
```

---

## API Reference

### `new Funnl(config)`

Create a new agent instance with the given configuration.

### `agent.verifyWebhook(query)`

Verify a Meta webhook challenge. Returns `{ verified: boolean, challenge?: string }`.

### `agent.handleWebhook(payload, platformHint?)`

Process a full webhook payload. Auto-detects event type (DM, comment, mention).
Returns `{ processed: number, events: string[] }`.

### `agent.handleInstagramDM(payload)`

Handle Instagram DM webhook specifically.

### `agent.handleInstagramComment(payload)`

Handle Instagram comment webhook specifically.

### `agent.handleThreadsComment(payload)`

Handle Threads comment webhook specifically.

### `agent.handleThreadsMention(payload)`

Handle Threads mention webhook specifically.

### `agent.publishThreadsPost({ text, mediaType, imageUrl, videoUrl })`

Publish a new Threads post. Returns the published post ID or null.

### `agent.addRule(fn)`

Register a custom rule for the hybrid engine. The function receives the user's message and should return a reply string or `null`.

```javascript
agent.addRule((message) => {
  if (message.toLowerCase().includes('hours')) {
    return "We're open Mon–Fri 9am–6pm EST! 🕐";
  }
  return null;
});
```

### `agent.storage`

Access the underlying storage adapter for direct queries.

---

## Custom Storage Adapter

Implement the `StorageAdapter` interface:

```javascript
const { StorageAdapter } = require('funnl');

class PrismaAdapter extends StorageAdapter {
  constructor(prisma) {
    super();
    this.prisma = prisma;
  }

  async getLeadByHandle(handle, platform) {
    let lead = await this.prisma.lead.findFirst({ where: { handle, platform } });
    if (!lead) {
      lead = await this.prisma.lead.create({
        data: { handle, platform, conversation_history: [], lead_stage: 'new' },
      });
    }
    return lead;
  }

  async addMessage(leadId, sender, text) {
    const lead = await this.prisma.lead.findUnique({ where: { id: leadId } });
    const history = [...(lead.conversation_history || []), { sender, text, timestamp: new Date().toISOString() }];
    return this.prisma.lead.update({
      where: { id: leadId },
      data: { conversation_history: history, last_interaction: new Date() },
    });
  }

  async getHistory(leadId) {
    const lead = await this.prisma.lead.findUnique({ where: { id: leadId } });
    return lead?.conversation_history || [];
  }

  async updateMetadata(leadId, metadata) {
    const { id, created_at, updated_at, ...data } = metadata;
    return this.prisma.lead.update({ where: { id: leadId }, data });
  }
}

// Usage:
const agent = new Funnl({
  meta: { accessToken: '...' },
  storage: new PrismaAdapter(prisma),
});
```

---

## Supported Platforms

| Platform  | DMs | Comments | Mentions | Posting |
|-----------|-----|----------|----------|---------|
| Instagram | ✅  | ✅       | —        | —       |
| Threads   | —   | ✅       | ✅       | ✅      |

---

## License

MIT