# @getnexorai/sdk

Official JavaScript SDK for [Nexor](https://www.getnexor.ai) — AI-led omni-channel contactability for your leads. One package, two things:

1. **REST client.** `nexor.init({ apiKey })` then `nexor.createLead(...)`, `nexor.getLead(...)`, `nexor.sendMessage(...)`, etc. Works in Node 18+ and modern browsers.
2. **Embeddable chat widget.** `nexor.initChat({ workflowId })` drops a chat bubble in the bottom-right of any web page. Visitors chat; you get a real Nexor lead + AI-driven conversation; cadence follow-ups happen on WhatsApp/email/voice as usual.

Full API reference: https://docs.getnexor.ai

---

## Install

```bash
npm install @getnexorai/sdk
# or
pnpm add @getnexorai/sdk
# or
yarn add @getnexorai/sdk
```

Or use it script-tag-style with no build:

```html
<script src="https://unpkg.com/@getnexorai/sdk/dist/nexor.iife.js"></script>
<script>
  Nexor.init({ apiKey: "nxr_live_…" });
</script>
```

---

## Quick start — push a lead from Node

```js
import nexor from "@getnexorai/sdk";

nexor.init({ apiKey: process.env.NEXOR_API_KEY });

const { lead, workflow_run } = await nexor.createLead({
  first_name: "Ada",
  last_name:  "Lovelace",
  email:      "ada@example.com",
  phone:      "+1 555 0100",
  workflow_id: "your-workflow-uuid",
  metadata:   { plan: "trial", utm_source: "google" },
});

console.log("Created lead:", lead.id);
console.log("Workflow run:", workflow_run?.id);
```

That's it. Nexor's AI agent will now reach out on the channels configured in the workflow.

---

## Quick start — embed a chat widget

```html
<script src="https://unpkg.com/@getnexorai/sdk/dist/nexor.iife.js"></script>
<script>
  Nexor.init({ apiKey: "nxr_live_…" });

  Nexor.initChat({
    workflowId: "your-workflow-uuid",
    title:      "Talk to our team",
    accentColor: "#4f46e5",
    systemPrompt: "You are a friendly sales assistant for Acme Corp.",
    capture: { fields: ["first_name", "email"] },
  });
</script>
```

A chat bubble appears in the bottom-right. Visitors fill the (configurable) capture form, chat with your AI agent, and become real leads in your Nexor account — primed for follow-up on WhatsApp/email/voice.

---

## API

### Initialisation

```ts
nexor.init({
  apiKey: string;          // required
  baseUrl?: string;        // default "https://api.getnexor.ai"
  timeoutMs?: number;      // default 30_000
  maxRetries?: number;     // default 2 (retries on 429 + 5xx + network errors)
  fetch?: typeof fetch;    // override (e.g. for tests / Node <18)
  userAgentSuffix?: string;
});
```

For multi-tenant / server-side use where a singleton doesn't fit, instantiate the client directly:

```ts
import { NexorClient } from "@getnexorai/sdk";

const client = new NexorClient({ apiKey });
await client.createLead({ first_name: "Ada", … });
```

### Leads

```ts
nexor.createLead(input)
nexor.createLeadsBulk(inputs)        // up to 1,000 per call
nexor.updateLead(leadId, updates)    // metadata is shallow-merged server-side
nexor.getLead(leadId)                // lead + collected variables + per-channel engagement
nexor.getLeadHistory(leadId, { channel, limit, offset })

nexor.syncLeadTags({ lead_id | email, tags })  // tags param is the full active set; [] clears

nexor.isLeadPaused(leadId, { workflow_id })
nexor.stopAutomation(leadId, { workflow_id })           // human takeover
nexor.resumeAutomation(leadId, { workflow_id, resume_cadence })

nexor.listLeadMeetings(leadId, { status })
```

`createLead` accepts an optional `force_first_message: { channel, content, subject?, sender_id? }` that pushes the very first message synchronously (WhatsApp or email) rather than waiting for cadence.

### Workflows / Campaigns / Templates

```ts
nexor.listWorkflows()
nexor.listCampaigns()
nexor.listTemplates({ status, category })
```

### Messages

```ts
nexor.sendMessage({
  lead_id, workflow_id,
  channel: "whatsapp" | "email" | "call",
  content?: string,        // free-form text (whatsapp/email)
  html?: string,           // email body
  subject?: string,        // email subject
  template_id?: string,    // whatsapp template (alternative to content)
  components?: unknown,    // whatsapp template parameters
  begin_message?: string,  // first utterance for outbound calls
});
```

### Meetings

```ts
nexor.createMeeting({ lead_email, title, starts_at, duration_minutes, … });
nexor.createMeetingNotes({ lead_email, notes, transcript_text, summary, action_items, … });
```

---

## Chat widget

```ts
nexor.initChat({
  // Required
  workflowId: string;

  // Branding
  title?: string;             // header text
  subtitle?: string;          // small line below header
  greeting?: string;          // first bot message
  accentColor?: string;       // hex, default "#111827"
  accentTextColor?: string;   // hex, default "#ffffff"
  showBranding?: boolean;     // "Powered by Nexor" footer, default true

  // Behaviour
  position?: "bottom-right" | "bottom-left";
  openOnLoad?: boolean;
  container?: HTMLElement;    // default document.body

  // Speed-dial channels (optional) — hover (desktop) or tap (mobile) the
  // launcher and it fans out round buttons instead of opening chat right away.
  // Only the channels you supply are shown, in the order listed. On phones the
  // chat panel opens full-screen with a clear close button in the header.
  channels?: {
    whatsapp?: string;        // "+56912345678" → opens wa.me
    whatsappText?: string;    // pre-filled WhatsApp message
    call?: string;            // "+56912345678" → tel:
    email?: string;           // → mailto:
    instagram?: string;       // handle "nexor.ai" or full URL
    chat?: boolean;           // include an "open chat" button, default true
    order?: ("whatsapp" | "call" | "email" | "instagram" | "chat")[];
    labels?: Partial<Record<"whatsapp"|"call"|"email"|"instagram"|"chat", string>>;
  };

  // Per-widget prompt overrides — forwarded to the agent on every turn
  systemPrompt?: string;
  clientPrompt?: string;

  // Lead linking
  lead?: { first_name?, last_name?, email?, phone?, metadata? };   // pre-known visitor → skips capture
  capture?: {
    fields?: ("first_name" | "last_name" | "email" | "phone")[];   // default ["first_name", "email"]
    mode?: "before" | "skip";                                      // default "before"
    label?: string;
    submitLabel?: string;
  };
  metadata?: Record<string, unknown>;                              // attached to every turn (page URL, UTM, …)

  // Callbacks
  onOpen?: () => void;
  onClose?: () => void;
  onMessage?: ({ role, text }) => void;
  onLeadCaptured?: (leadId) => void;
  onError?: (err) => void;
});
```

Returns a handle:

```ts
const chat = nexor.initChat({ … });

chat.open();
chat.close();
chat.toggle();
await chat.send("Hello from JS");   // programmatic message
chat.destroy();
chat.getSessionId();                // stable per-browser id (persisted to localStorage)
```

### How it works under the hood

Each turn POSTs to `/api/public/chat` with:

```json
{
  "workflow_id": "…",
  "session_id":  "sess_…",          // generated + persisted by the SDK
  "message":     "user text",
  "system_prompt": "…",             // optional override
  "client_prompt": "…",             // optional override
  "lead": { "first_name": "Ada", "email": "ada@example.com" },
  "metadata": { "page_url": "…" }
}
```

The server matches/creates a `web-chat` lead, runs the workflow's AI agent, and returns:

```json
{ "success": true, "reply": "…", "session_id": "…", "lead_id": "…", "workflow_run_id": "…" }
```

Subsequent turns reuse the same session and lead — including across page reloads.

---

## Errors

All API failures throw a `NexorAPIError` (or one of its subclasses):

```ts
import { NexorAPIError, NexorAuthError, NexorValidationError, NexorNetworkError } from "@getnexorai/sdk";

try {
  await nexor.createLead({ first_name: "" });
} catch (err) {
  if (err instanceof NexorValidationError) {
    console.error("Bad input:", err.message);
  } else if (err instanceof NexorAuthError) {
    console.error("API key rejected");
  } else if (err instanceof NexorAPIError) {
    console.error(`HTTP ${err.status}: ${err.message}`, err.requestId);
  } else if (err instanceof NexorNetworkError) {
    console.error("Network problem:", err.cause);
  }
}
```

429 and 5xx responses are automatically retried with exponential backoff (configurable via `maxRetries`). Honors `Retry-After`.

---

## Examples

- [`examples/node-create-lead.mjs`](./examples/node-create-lead.mjs) — push leads from a Node script.
- [`examples/browser-chat.html`](./examples/browser-chat.html) — embed the chat widget via script tag.

---

## Building from source

```bash
npm install
npm run typecheck
npm run build      # outputs dist/{index.js,index.cjs,chat.js,chat.cjs,nexor.iife.js} + .d.ts
```

---

## License

MIT
