# Documentation

Deploy your Gamaro Conversion Agent on your site in minutes. Script tag or NPM, your choice.

---

## What is a Gamaro Agent?

A Gamaro Agent is a conversational entity you configure in the portal: its business vertical, brand, conversion objectives and data sources. The widget is just how that Agent shows up on your site — a single line of code embeds it.

- **The Agent lives in the portal** — You configure it once (vertical, brand, objectives). The widget is its display channel, not the Agent itself.

- **Your data grounds the answers** — Connect your sources (inventory, CRM, knowledge base): the Agent answers from your real data, not the public-web average.

- **The publishable key ties it together** — A pk_* key binds the embedded widget to an Agent and its configuration, resolved at load time.

---

## Your first Agent in 5 minutes

From portal to first conversation, no build step.

### 1. Create the Agent in the portal

In /app/portal, create an Agent: give it a name and pick its vertical (real estate, B2B…).

### 2. Grab a test key

Generate a pk_test_* key from /app/portal/api-keys. It works on localhost with no domain configuration.

### 3. Embed the widget

Paste the script tag (below) with your key into a locally-served page. No build step.

### 4. Converse

Open the page: the Agent starts the conversation. Hook the ready or qualified event to react on your side.

---

## Quick Start

Add a single line of code to integrate the widget.

```html
<!-- Add this script to your page -->
<!-- Production tip: pin a version + SRI, e.g. @0.2.0 + integrity="sha384-..." -->
<script
  src="https://unpkg.com/@dolard.eu/versiq-widget@latest/dist/widget.umd.js"
  data-api-key="pk_your_key"
></script>
```

---

## Environments: dev → staging → prod

How to test the widget locally then roll it out to production without surprises. Each environment uses a different key and a dedicated domain declaration.

The widget intentionally splits two kinds of publishable keys. A test key (pk_test_*) is designed for local development: it always works on localhost, 127.0.0.1 and *.localtest.me, with zero domain configuration. A live key (pk_live_*) is designed for public environments: it refuses localhost and requires an explicit domain whitelist.

### 1. Local development

- Key: pk_test_*

- Domain: no declaration required — test keys always allow localhost, 127.0.0.1 and *.localtest.me.

Create a test key from /app/portal/api-keys, embed the widget on http://localhost:3000 (or your dev server port), and exercise every feature without touching the Domains page.

### 2. Staging / preview

- Key: pk_live_* (dedicated to staging)

- Domain to declare: https://staging.mysite.com (exact URL of your preview environment)

Create a live key dedicated to staging — never reuse the production key. Add the exact URL of your preview environment in /app/portal/domains. If your staging runs on a custom port (e.g. preview-42.mysite.com:8443), add the full URL with port.

### 3. Production

- Key: pk_live_*

- Domain to declare: https://mysite.com (or wildcard *.mysite.com to cover several subdomains at once)

For a site that serves several subdomains (app, www, checkout…), a *.mysite.com wildcard is more maintainable than multiple exact entries. Note: the wildcard only covers direct subdomains — add mysite.com as an extra entry if the apex domain also serves the widget.

> **Why split test and live?**
>
> A pk_live_* live key refuses localhost even in dev and requires a domain whitelist. This is intentional: if a live key is stolen (leak in a public GitHub repo, compromised browser extension, accidental screenshot), it cannot be used from a local HTML file or from any undeclared third-party site. An attacker who gets the key cannot do anything with it without also controlling one of the domains you explicitly allowed. This is the seat belt that compensates for the key being publicly exposed in your pages' HTML.

### Wildcard syntax

A *.mysite.com wildcard matches app.mysite.com, www.mysite.com, checkout.mysite.com — but not mysite.com itself. To cover the apex domain, add it as a separate entry. Wildcards only support one level (*.mysite.com ≠ **.mysite.com): a.b.mysite.com is not matched, you need to add *.b.mysite.com as well.

### What happens if the domain is not allowed?

The Gamaro server returns a 403 ORIGIN_NOT_ALLOWED error and the widget stays silent (no chat window opens). Enable the widget's debug option during integration to see the response in the console — it is usually the first thing to check when the widget fails to appear in staging.

---

## NPM + TypeScript

```bash
npm install @dolard.eu/versiq-widget
```

```typescript
import { createWidget } from "@dolard.eu/versiq-widget";

const widget = createWidget({ apiKey: "pk_your_key" });

// Control the widget
widget.open();
widget.close();
widget.reset();
```

---

## Configuration

Customize widget behavior with data-attributes. Theme, position and language are now configured from the Gamaro portal and resolved at load time via the publishable key.

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| data-api-key | string | required | Publishable API key (pk_*) — required, identifies your Agent |
| data-container | string | - | CSS selector for the inline mode container (e.g., '#chat'). The programmatic SDK also accepts a raw HTMLElement. |
| data-base-url | string | Production URL | Base URL for the embed |
| data-debug | boolean | false | Enable debug logging |
| data-email | string | - | Pre-identified visitor email (HMAC verification) |
| data-user-id | string | - | Host-side unique user identifier (HMAC verification) |
| data-user-hash | string | - | HMAC-SHA256 of the userId (or email as fallback), signed with the identity secret |

---

## Real-world Examples

Discover integration scenarios adapted to your context.

### Real estate showcase site

Floating widget + events → qualified lead by email

- **Approach**: Script tag
- **Result**: Complete buyer profile sent to your CRM

### Next.js B2B Application

NPM + TypeScript + CRM webhook

- **Approach**: NPM Package
- **Result**: Type-safe integration with Pipedrive/HubSpot

### E-commerce Marketplace

Inline mode + custom styling

- **Approach**: Inline
- **Result**: Widget embedded in your existing design

---

## Display Modes

Choose between a floating or embedded widget.

### Floating

The widget appears as a floating button in a corner of the page. This is the default mode — no HTML option required, position is set in /app/portal/widget.

### Inline

The widget is embedded directly into a container on your page. Ideal for a contact page or dedicated landing page. Pass a CSS selector via data-container, or an HTMLElement via the programmatic SDK.

```typescript
import { createWidget } from "@dolard.eu/versiq-widget";

// container accepts a CSS selector OR an HTMLElement
const widget = createWidget({
  apiKey: "pk_your_key",
  container: "#chat-container",
});
```

---

## Multiple Agents on one page

Run multiple widgets on the same page — each one a distinct Agent with its own vertical.

### Script Tags

```html
<!-- B2B qualification widget -->
<script
  src="https://unpkg.com/@dolard.eu/versiq-widget@latest/dist/widget.umd.js"
  data-api-key="pk_your_b2b_key"
></script>

<!-- Real estate widget (same page) -->
<script
  src="https://unpkg.com/@dolard.eu/versiq-widget@latest/dist/widget.umd.js"
  data-api-key="pk_your_immo_key"
></script>
```

### Programmatic API

```typescript
import { createWidget } from "@dolard.eu/versiq-widget";

// Each API key maps to an Agent with its own vertical, theme, and
// position — all configured from the admin portal.
const b2bWidget = createWidget({ apiKey: "pk_your_b2b_key" });
const immoWidget = createWidget({
  apiKey: "pk_your_immo_key",
  // container pins the widget to a specific DOM node (inline mode).
  container: document.getElementById("demo-immo"),
});

// Access instances after they're ready
b2bWidget.on("ready", () => {
  // window.VersiqApps.get(b2bWidget.applicationId) → VersiqAPI
  console.log("B2B widget ready:", b2bWidget.applicationId);
});
```

---

## Theming

Match the widget appearance to your brand.

> **Theming is configured in the portal**
>
> Since release 2024.11, theming (colors, typography, border radius, light/dark scheme, position) is managed from /app/portal/widget. The widget resolves these settings at load time via the publishable key — no HTML changes required.

> **Toggle light/dark at runtime**
>
> To sync the widget's color scheme with your host site (user toggle, system preference), use the JavaScript API window.Versiq.setColorScheme('light' | 'dark' | 'auto').

---

## Events

React to user interactions with events.

| Event | Payload | Description |
| --- | --- | --- |
| ready | - | Widget is loaded and ready |
| open | - | Widget was opened |
| close | - | Widget was closed |
| message | WidgetMessage | New message in the conversation |
| profile-update | - | User profile was updated (signal only — subscribe to webhooks for the data) |
| qualified | - | Lead qualified (signal only — subscribe to the lead.qualified webhook for the data) |
| quota-warning | { remaining, limit } | API quota is running low |
| quota-exceeded | - | API quota has been exceeded |
| error | { code, message } | An error occurred |
| identity-verified | { email, userId? } | Visitor identity was verified via HMAC |

### Usage example

```javascript
// Subscribe to events
window.Versiq.on("ready", () => {
  console.log("Agent is ready");
});

window.Versiq.on("profile-update", () => {
  console.log("Profile was updated"); // signal only, no data
});

window.Versiq.on("qualified", () => {
  console.log("Lead qualified"); // signal only, no data
  // Full profile: subscribe to the lead.qualified webhook instead
});

window.Versiq.on("identity-verified", (data) => {
  console.log("Identity verified:", data.email);
});

window.Versiq.on("quota-warning", (data) => {
  console.warn("Quota low:", data.remaining, "/", data.limit);
});

window.Versiq.on("error", (data) => {
  console.error("Error:", data.code, data.message);
});
```

---

## Data Sources

Connect your product or service data to power the AI conversation. Without a data source, the conversion agent performs conversational profiling.

Gamaro connects to your data via a generic interface. Each source declares its capabilities, and AI tools adapt automatically.

### DataSource Interface

Implement this TypeScript interface to connect your own data source.

```typescript
interface DataSource {
  readonly id: string;
  readonly displayName: string;
  readonly coverageDescription: string;

  getCapabilities(): ReadonlySet<DataSourceCapability>;

  search?(params: Record<string, unknown>): Promise<unknown[]>;
  getStats?(zone: string): Promise<Record<string, unknown>>;
  getAreaMetrics?(zone: string, filters: Record<string, unknown>): Promise<Record<string, unknown>>;
  estimate?(params: Record<string, unknown>): Promise<Record<string, unknown>>;
  count?(areas: string[], filters: Record<string, unknown>): Promise<number>;
  checkCoverage?(zone: string): Promise<{ covered: boolean; message?: string }>;
}
```

### Capabilities

Each capability enables one or more AI tools in the conversation.

| Capability | Method | Tools | Description |
| --- | --- | --- | --- |
| search | search() | searchProperties | Search items/products with filters |
| stats | getStats() | getCityStats | Aggregated statistics by zone |
| areaMetrics | getAreaMetrics() | getAreasOverview | Detailed metrics by zone (overview) |
| estimate | estimate() | estimateProperty, estimateRent | Price or value estimation |
| count | count() | getPropertyCount | Count with filters |
| coverage | checkCoverage() | checkZoneCoverage | Zone coverage verification |

### Example: REST API

A concrete example connecting to a third-party REST API.

```typescript
class MyApiDataSource implements DataSource {
  readonly id = "my-api";
  readonly displayName = "Mon API Produits";
  readonly coverageDescription = "Catalogue produit via API REST";

  getCapabilities() {
    return new Set(["search", "count"]);
  }

  async search(params) {
    const res = await fetch(`https://api.example.com/products?${qs(params)}`);
    return res.json();
  }

  async count(areas, filters) {
    const res = await fetch("https://api.example.com/products/count", {
      method: "POST",
      body: JSON.stringify({ areas, filters }),
    });
    const data = await res.json();
    return data.count;
  }
}
```

> **Graceful Degradation**
>
> When a source only supports certain capabilities, unsupported tools are automatically hidden. Partners don't need to configure anything on the tools side.

### Access modes

Gamaro adapts to your infrastructure. The custom mode (DataSource) is available today; native connectors and standard protocols are on the way.

- Native Integrations: pre-built connectors (CRM, e-commerce, etc.) _(Soon)_

- Custom Mode (DataSource): TypeScript interface to implement _(Available)_

- MCP (Model Context Protocol): expose your data via an MCP server _(Soon)_

- A2A (Agent-to-Agent): inter-agent communication for third-party systems _(Soon)_

---

## Identity Verification

When visitors are already logged in on your site, silently verify their identity via HMAC-SHA256 — zero friction, verified leads.

> **When to use**
>
> Identity verification is designed for authenticated areas where visitors are already logged in — SaaS dashboards, client portals, member areas. It is not intended for public marketing pages where visitors are anonymous.

### Identity Levels

| Level | Mechanism | Description |
| --- | --- | --- |
| Anonymous | Cookie / session ID | Default for all visitors — no setup required |
| Identified | Email shared in chat | Visitor shares their email during conversation |
| Verified | HMAC attested by host | Host site cryptographically attests the visitor's identity — zero friction |

### Step 1: Compute HMAC server-side

The HMAC must be computed on your backend. Never expose the secret in client-side code.

**Node.js**

```javascript
import crypto from "node:crypto";

// Sign the stable userId when you have one (recommended), else the email.
// Send both data-user-id and data-user-hash; data-email stays optional.
const signedValue = user.id ?? user.email;
const hmac = crypto
  .createHmac("sha256", process.env.VERSIQ_IDENTITY_SECRET)
  .update(signedValue)
  .digest("hex");
```

**Python**

```python
import hmac
import hashlib
import os

# Sign the stable user id when you have one (recommended), else the email.
# Send both data-user-id and data-user-hash; data-email stays optional.
signed_value = user.id or user.email
user_hash = hmac.new(
    os.environ["VERSIQ_IDENTITY_SECRET"].encode(),
    signed_value.encode(),
    hashlib.sha256,
).hexdigest()
```

### Step 2: Pass identity to the widget

Add the identity attributes to your widget script tag, or use the programmatic API after login.

**Script tag**

```html
<script
  src="https://unpkg.com/@dolard.eu/versiq-widget@latest/dist/widget.umd.js"
  data-api-key="pk_live_..."
  data-email="${user.email}"
  data-user-id="${user.id}"
  data-user-hash="${hmac}"
></script>
```

**Dynamic (post-login)**

For single-page apps, identify the visitor after login without reloading the page.

```javascript
// After user logs in, identify them dynamically
window.Versiq.identify({
  email: "user@example.com",
  userId: "usr_123",
  userHash: "<computed_hmac>",
});
```

> **HMAC Input**
>
> The HMAC is computed on userId if provided (stable identifier, recommended), or email as fallback. Using userId means the HMAC stays valid even if the user changes their email.

> **Security**
>
> The identity secret must never appear in client-side code. Generate it in your Gamaro admin dashboard under Settings > Identity Verification.

---

## JavaScript API

Control the widget programmatically via window.Versiq.

| Method | Return | Description |
| --- | --- | --- |
| `open()` | void | Open the widget |
| `close()` | void | Close the widget |
| `reset()` | void | Reset the conversation |
| `setTheme(theme)` | void | Change theme dynamically |
| `setColorScheme(scheme)` | void | Toggle the color scheme ('light' \| 'dark' \| 'auto') — handy for wiring the host site's own theme switch |
| `on(event, handler)` | void | Subscribe to an event |
| `off(event, handler)` | void | Unsubscribe from an event |
| `destroy()` | void | Remove widget and cleanup resources |
| `identify(params)` | void | Identify a visitor with HMAC verification |
| `version` | string | Current SDK version (read-only) |
| `applicationId` | string | Agent ID (applicationId) resolved after load (empty until ready is emitted) |
| `window.VersiqApps` | Map<string, VersiqAPI> | Global map of widget instances — key: applicationId, value: VersiqAPI |
