# @peopl-health/nexus

A concise, configurable messaging and assistant toolkit for WhatsApp. It supports Twilio (production‑ready) and Baileys (limited), optional Mongo storage, OpenAI assistants, and templates/flows via Twilio Content API.

## Install

```bash
npm install @peopl-health/nexus
# Providers / AI
npm install twilio baileys openai
```

## Quick Start (Twilio)

```js
const express = require('express');
const { Nexus, setupDefaultRoutes } = require('@peopl-health/nexus');

const nexus = new Nexus();
await nexus.initialize({
  provider: 'twilio',
  providerConfig: {
    accountSid: process.env.TWILIO_ACCOUNT_SID,
    authToken: process.env.TWILIO_AUTH_TOKEN,
    phoneNumber: process.env.TWILIO_PHONE_NUMBER
  },
  // Storage (MongoStorage) and Mongo convenience
  storage: 'mongo',
  storageConfig: { dbName: 'nexus' },   // other options
  mongoUri: process.env.MONGODB_URI,    // convenience: passed into storageConfig.mongoUri

  // Media convenience (inject only bucket name)
  media: { bucketName: process.env.AWS_S3_BUCKET_NAME },

  // Airtable convenience (pick default base or pass a Base ID)
  airtable: {
    base: 'calendar',                   // friendly key or base ID
    apiKey: process.env.AIRTABLE_API_KEY
  },

  // Optional LLM (OpenAI)
  llm: 'openai',
  llmConfig: { apiKey: process.env.OPENAI_API_KEY }
});

// Built‑in routes (assistant, conversation, media, message, template)
const app = express();
app.use(express.json());
setupDefaultRoutes(app);

// Incoming webhooks
app.post('/webhook', async (req, res) => {
  await nexus.processMessage(req.body);
  res.sendStatus(200);
});
```

## Templates & Approvals (Twilio)

Nexus auto‑injects the active Twilio provider into the template controllers during `initialize`. Default routes under `/api/template` work immediately:
- `GET /api/template` list templates
- `GET /api/template/:id` get one
- `POST /api/template/text` create text template
- `POST /api/template/approval` submit for approval
- `GET /api/template/status/:sid` check status
- `DELETE /api/template/:id` delete

Programmatic use:
```js
const provider = nexus.getMessaging().getProvider();
const content = await provider.createTemplate({
  friendly_name: 'hello_' + Date.now(),
  language: 'es',
  variables: { '1': 'Nombre' },
  types: { 'twilio/text': { body: 'Hola {{1}}' } }
});
await provider.submitForApproval(content.sid, 'hello', 'UTILITY');
const status = await provider.checkApprovalStatus(content.sid);
```

## Interactive & Flows

Provider‑agnostic APIs with Twilio mapping (Baileys: not supported):
```js
const { registerFlow, sendInteractive, registerInteractiveHandler, attachInteractiveRouter } = require('@peopl-health/nexus');

registerFlow('greeting_qr', {
  type: 'quick-reply', language: 'es', body: 'Hola {{1}}', variables: { '1': 'Nombre' },
  buttons: [{ text: 'Sí' }, { text: 'No' }]
});
await sendInteractive(nexus, { code: '+521555...', id: 'greeting_qr' });

registerInteractiveHandler({ type: 'button', id: /sí|si/i }, async (msg, messaging) => {
  await messaging.sendMessage({ code: msg.from, message: '¡Confirmado!' });
});
attachInteractiveRouter(nexus);
```

## Storage (Adapters)

- Built‑in: `mongo` (default), `noop`.
- Register your adapter or pass an instance directly.
```js
const { registerStorage } = require('@peopl-health/nexus/lib/storage/registry');
registerStorage('src', MyStorageClass);
await nexus.initialize({ storage: 'src', storageConfig: { /* ... */ } });
// OR
await nexus.initialize({ storage: new MyStorageClass(/* ... */) });
```

## Events

Subscribe to internal events via the event bus.
```js
const bus = nexus.getMessaging().getEventBus();
bus.on('message:received', (m) => console.log('rx', m.id));
```

## Assistants (Optional)

Register assistant classes and (optionally) a custom resolver. OpenAI is supported via `llm: 'openai'`.
```js
await nexus.initialize({
  provider: 'twilio',
  llm: 'openai', llmConfig: { apiKey: process.env.OPENAI_API_KEY },
  assistants: {
    registry: { SUPPORT: SupportAssistantClass, SALES: SalesAssistantClass },
    getAssistantById: (id, thread) => null // optional override
  }
});
```

## Routes (Importable)

Use `setupDefaultRoutes(app)` to mount everything, or pick from `routes` + `createRouter()` to mount subsets.
The route helpers intentionally leave authentication to the host application. Mount them behind
tenant authentication and authorization middleware before exposing them; patient routes return
protected health information and must never be public.

## Support-session routing

`supportCandidateService` is exported for consumers that coordinate first-session
nutrition and psychology invitations. Its candidate lifecycle provides atomic
invitation claims and one lifecycle per patient and service, including first-session
and follow-up invitations with cooldown enforcement.

Triage consumers opt into classification explicitly:

```js
handleTriageCompleted(payload, {
  supportRouting: {
    enabled: true,
    autoEligible: false, // classify and persist without patient-facing automation
  },
});
```

Psychology routing enforces the specialist traffic light: green can enter a group,
orange routes to individual care first, and red routes to clinical escalation.

## Examples

General, illustrative examples meant to be copied into your own app:
- examples/basic-usage.js — minimal server: initialize Nexus, default routes, webhook
- examples/consumer-server.js — a fuller server setup
- examples/assistants/ — custom assistants and tool registration

For advanced topics see docs/batching-hooks.md (pre/post-processing hooks) and docs/STATUS_CALLBACK_SETUP.md (delivery status callbacks).



## Configuration

You can configure Nexus via environment variables or at runtime using simple injection helpers. For production apps, prefer passing options or DI, and use envs as a fallback.

- Providers/AI
  - TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_PHONE_NUMBER
  - OPENAI_API_KEY

- Mongo
  - MONGODB_URI (or pass `mongoUri` when calling `initializeMongoDB()`)

- Airtable
  - AIRTABLE_API_KEY
  - AIRTABLE_BASE_ID (or specific IDs below)
  - AIRTABLE_CALENDAR_ID, AIRTABLE_CONFIG_ID, AIRTABLE_HISTORIAL_CLINICO_ID
  - AIRTABLE_LOGGING_ID, AIRTABLE_MONITOREO_ID, AIRTABLE_PROGRAMA_JUNTAS_ID
  - AIRTABLE_SYMPTOMS_ID, AIRTABLE_WEBINARS_LEADS_ID

- LLM tool monitor
  - `LLM_MONITOR_ENABLED` (`false` by default)
  - `LLM_MONITOR_PASS_SAMPLE_RATE` (`0.05` by default)

  Configure the active `llm_tool_monitor_contingency` preset and the
  `llm_monitor_findings` table before enabling monitoring. Nexus registers the
  contingency specification with the messaging queue and starts reconciliation
  during initialization. Monitor startup and processing failures never block a
  patient response.

  ```js
  const nexus = new Nexus({
    messaging: {
      queue: { type: 'redis', config: { redis } },
      llmMonitor: { enabled: true },
    },
  });
  ```

  A custom service can be injected with `messaging.llmMonitor.service`. Use an
  explicit `reconcile({ from, to })` range for intentional historical backfills.

- AWS (S3)
  - AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION (default: us-east-1)
  - AWS_S3_BUCKET_NAME (or inject via `configureMediaController`)

- Misc
  - NODE_ENV (affects logging and helpers)
  - USER_DB_MONGO (used as author in message controller)

Injection points (dependency injection)

- Media (inject only your bucket name; AWS SDK is loaded by the lib):
```js
const { configureMediaController } = require('@peopl-health/nexus/lib/controllers/mediaController');
configureMediaController({ bucketName: process.env.AWS_S3_BUCKET_NAME });
```

- Storage settings (MongoStorage only):
```js
// Store once; Nexus auto-injects media bucket from storage at startup
await nexus.getStorage().setConfig('media.bucketName', process.env.AWS_S3_BUCKET_NAME);
```

Tips
- Connect Mongo before `app.listen()` to avoid Mongoose buffering timeouts.
- If you initialize OpenAI, pass `llm: 'openai'` and `llmConfig: { apiKey }` to `nexus.initialize`.
- Use the event bus for observability and custom reactions without forking the default handlers.
