# Documentation

Déployez votre Agent de conversion Gamaro sur votre site en quelques minutes. Script tag ou NPM, à vous de choisir.

---

## Qu'est-ce qu'un Agent Gamaro ?

Un Agent Gamaro est une entité conversationnelle que vous configurez dans le portail : sa verticale métier, sa marque, ses objectifs de conversion et ses sources de données. Le widget n'est que la façon dont cet Agent s'incarne sur votre site — une seule ligne de code l'y embarque.

- **L'Agent vit dans le portail** — Vous le configurez une fois (verticale, marque, objectifs). Le widget est son canal d'affichage, pas l'Agent lui-même.

- **Vos données ancrent les réponses** — Branchez vos sources (inventaire, CRM, base de connaissances) : l'Agent répond depuis vos données réelles, pas depuis la moyenne du web.

- **La clé publiable relie le tout** — Une clé pk_* rattache le widget embarqué à un Agent et à sa configuration, résolue au chargement.

---

## Votre premier Agent en 5 minutes

Du portail à la première conversation, sans build.

### 1. Créez l'Agent dans le portail

Dans /app/portal, créez un Agent : donnez-lui un nom et choisissez sa verticale (immobilier, B2B…).

### 2. Récupérez une clé test

Générez une clé pk_test_* depuis /app/portal/api-keys. Elle fonctionne sur localhost sans configuration de domaine.

### 3. Embarquez le widget

Collez le script tag (ci-dessous) avec votre clé dans une page servie en local. Aucune étape de build.

### 4. Conversez

Ouvrez la page : l'Agent démarre la conversation. Branchez l'événement ready ou qualified pour réagir côté site.

---

## Démarrage rapide

Ajoutez une seule ligne de code pour intégrer le 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>
```

---

## Environnements : dev → staging → prod

Comment tester le widget en local puis le déployer sans surprise. Chaque environnement utilise une clé différente et une déclaration de domaine dédiée.

Le widget sépare volontairement deux types de clés publiques. Une clé test (pk_test_*) est conçue pour le développement local : elle fonctionne toujours sur localhost, 127.0.0.1 et *.localtest.me, sans aucune configuration de domaine. Une clé live (pk_live_*) est conçue pour vos environnements publics : elle refuse localhost et exige une whitelist de domaines explicite.

### 1. Développement local

- Clé : pk_test_*

- Domaine : aucune déclaration requise — les clés test autorisent toujours localhost, 127.0.0.1 et *.localtest.me.

Créez une clé test depuis /app/portal/api-keys, embarquez le widget sur http://localhost:3000 (ou le port de votre dev-serveur), et exercez toutes les fonctionnalités sans toucher à la page Domaines.

### 2. Staging / preview

- Clé : pk_live_* (dédiée au staging)

- Domaine à déclarer : https://staging.monsite.fr (URL exacte de votre environnement de preview)

Créez une clé live dédiée au staging — ne réutilisez jamais la clé de prod. Ajoutez l'URL exacte de votre environnement de preview dans /app/portal/domains. Si votre staging tourne sur un port custom (ex : preview-42.monsite.fr:8443), ajoutez l'URL complète avec port.

### 3. Production

- Clé : pk_live_*

- Domaine à déclarer : https://monsite.fr (ou wildcard *.monsite.fr pour couvrir plusieurs sous-domaines d'un coup)

Pour un site qui sert plusieurs sous-domaines (app, www, checkout…), un wildcard *.monsite.fr est plus maintenable que plusieurs entrées exactes. Attention : le wildcard couvre uniquement les sous-domaines directs — ajoutez monsite.fr en plus si le domaine apex sert aussi le widget.

> **Pourquoi cette séparation ?**
>
> Une clé live pk_live_* refuse localhost même en dev, et exige une whitelist de domaines. C'est intentionnel : si une clé live est volée (fuite dans un repo GitHub public, extension navigateur compromise, capture d'écran accidentelle), elle ne peut pas être utilisée depuis une page HTML locale ni depuis un site tiers non déclaré. L'attaquant qui récupère la clé ne peut rien en faire sans aussi contrôler un des domaines que vous avez explicitement autorisés. C'est la ceinture de sécurité qui compense l'exposition publique de la clé dans le HTML de vos pages.

### Syntaxe des wildcards

Un wildcard *.monsite.fr matche app.monsite.fr, www.monsite.fr, checkout.monsite.fr — mais pas monsite.fr lui-même. Pour couvrir le domaine apex, ajoutez-le en entrée séparée. Les wildcards ne supportent qu'un seul niveau (*.monsite.fr ≠ **.monsite.fr) : a.b.monsite.fr n'est pas matché, il faut ajouter *.b.monsite.fr en plus.

### Que se passe-t-il si le domaine n'est pas autorisé ?

Le serveur Gamaro renvoie une erreur 403 ORIGIN_NOT_ALLOWED et le widget reste silencieux (aucune fenêtre de chat ne s'ouvre). Activez l'option debug du widget pendant l'intégration pour voir la réponse dans la console — c'est souvent la première chose à vérifier quand le widget ne s'affiche pas en 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

Personnalisez le comportement du widget avec les data-attributes. Le thème, la position et la langue sont configurés depuis le portail Gamaro et résolus au chargement via la clé publiable.

| Option | Type | Défaut | Description |
| --- | --- | --- | --- |
| data-api-key | string | required | Clé API publiable (pk_*) — obligatoire, identifie votre Agent |
| data-container | string | - | Sélecteur CSS du conteneur pour le mode inline (ex: '#chat'). Côté SDK programmatique, accepte aussi un HTMLElement directement. |
| data-base-url | string | Production URL | URL de base pour l'embed |
| data-debug | boolean | false | Activer les logs de debug |
| data-email | string | - | Email pré-identifié du visiteur (vérification HMAC) |
| data-user-id | string | - | Identifiant utilisateur côté site hôte (vérification HMAC) |
| data-user-hash | string | - | HMAC-SHA256 du userId (ou email en fallback), signé avec le secret d'identité |

---

## Exemples concrets

Découvrez des scénarios d'intégration adaptés à votre contexte.

### Site vitrine immobilier

Widget flottant + événements → lead qualifié par email

- **Approche**: Script tag
- **Résultat**: Profil acheteur complet envoyé à votre CRM

### Application Next.js B2B

NPM + TypeScript + webhook CRM

- **Approche**: Package NPM
- **Résultat**: Intégration type-safe avec Pipedrive/HubSpot

### Marketplace e-commerce

Mode inline + styling personnalisé

- **Approche**: Mode inline
- **Résultat**: Widget intégré à votre design existant

---

## Modes d'affichage

Choisissez entre un widget flottant ou intégré.

### Flottant

Le widget apparaît comme un bouton flottant dans un coin de la page. C'est le mode par défaut : aucune option HTML n'est requise — la position se règle dans /app/portal/widget.

### Intégré

Le widget est intégré directement dans un conteneur de votre page. Idéal pour une page contact ou une landing page dédiée. Indiquez un sélecteur CSS via data-container, ou un HTMLElement via le SDK programmatique.

```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",
});
```

---

## Plusieurs Agents sur une page

Plusieurs widgets sur la même page — chacun étant un Agent distinct avec sa verticale.

### 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>
```

### API programmatique

```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);
});
```

---

## Personnalisation

Adaptez l'apparence du widget à votre charte graphique.

> **Configuration centralisée dans le portail**
>
> Depuis la version 2024.11, la personnalisation (couleurs, typographie, rayon des bordures, mode clair/sombre, position) est gérée depuis /app/portal/widget. Le widget résout ces réglages au chargement via la clé publiable — aucune modification côté HTML n'est nécessaire.

> **Basculer le mode clair/sombre à l'exécution**
>
> Pour synchroniser le mode d'affichage du widget avec celui de votre site hôte (toggle utilisateur, préférence système), utilisez l'API JavaScript window.Versiq.setColorScheme('light' | 'dark' | 'auto').

---

## Événements

Réagissez aux interactions utilisateur avec les événements.

| Event | Payload | Description |
| --- | --- | --- |
| ready | - | Le widget est chargé et prêt |
| open | - | Le widget a été ouvert |
| close | - | Le widget a été fermé |
| message | WidgetMessage | Nouveau message dans la conversation |
| profile-update | - | Le profil utilisateur a été mis à jour (signal seul — les webhooks portent la donnée) |
| qualified | - | Lead qualifié (signal seul — le webhook lead.qualified porte la donnée) |
| quota-warning | { remaining, limit } | Le quota API approche de la limite |
| quota-exceeded | - | Le quota API est dépassé |
| error | { code, message } | Une erreur s'est produite |
| identity-verified | { email, userId? } | L'identité du visiteur a été vérifiée par HMAC |

### Exemple d'utilisation

```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);
});
```

---

## Sources de données

Connectez vos données produit ou service pour alimenter la conversation IA. Sans source de données, l'agent de conversion assure le profilage conversationnel.

Gamaro se connecte à vos données via une interface générique. Chaque source déclare ses capabilities, et les outils IA s'adaptent automatiquement.

### Interface DataSource

Implémentez cette interface TypeScript pour brancher votre propre source de données.

```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

Chaque capability active un ou plusieurs outils IA dans la conversation.

| Capability | Méthode | Outils | Description |
| --- | --- | --- | --- |
| search | search() | searchProperties | Recherche de biens/produits avec filtres |
| stats | getStats() | getCityStats | Statistiques agrégées par zone |
| areaMetrics | getAreaMetrics() | getAreasOverview | Métriques détaillées par zone (overview) |
| estimate | estimate() | estimateProperty, estimateRent | Estimation de prix ou valeur |
| count | count() | getPropertyCount | Comptage avec filtres |
| coverage | checkCoverage() | checkZoneCoverage | Vérification de zone couverte |

### Exemple : API REST

Un exemple concret d'implémentation connectée à une API REST tierce.

```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;
  }
}
```

> **Dégradation gracieuse**
>
> Quand une source ne supporte que certaines capabilities, les outils non supportés sont automatiquement masqués. Le partenaire n'a rien à configurer côté outils.

### Modes d'accès

Gamaro s'adapte à votre infrastructure. Le mode custom (DataSource) est disponible aujourd'hui ; les connecteurs natifs et les protocoles standard arrivent.

- Intégrations natives : connecteurs pré-construits (CRM, e-commerce, etc.) _(Bientôt)_

- Mode Custom (DataSource) : interface TypeScript à implémenter _(Disponible)_

- MCP (Model Context Protocol) : exposez vos données via un serveur MCP _(Bientôt)_

- A2A (Agent-to-Agent) : communication inter-agents pour systèmes tiers _(Bientôt)_

---

## Vérification d'identité

Quand vos visiteurs sont déjà connectés sur votre site, vérifiez silencieusement leur identité via HMAC-SHA256 — zéro friction, leads vérifiés.

> **Quand l'utiliser**
>
> La vérification d'identité est conçue pour les zones authentifiées où les visiteurs sont déjà connectés — dashboards SaaS, portails clients, espaces membres. Elle n'est pas destinée aux pages marketing publiques où les visiteurs sont anonymes.

### Niveaux d'identité

| Level | Mechanism | Description |
| --- | --- | --- |
| Anonyme | Cookie / ID de session | Par défaut pour tous les visiteurs — aucune configuration requise |
| Identifié | Email partagé dans le chat | Le visiteur partage son email pendant la conversation |
| Vérifié | HMAC attesté par le site hôte | Le site hôte atteste cryptographiquement l'identité du visiteur — zéro friction |

### Étape 1 : Calculer le HMAC côté serveur

Le HMAC doit être calculé sur votre backend. N'exposez jamais le secret dans le code client.

**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()
```

### Étape 2 : Transmettre l'identité au widget

Ajoutez les attributs d'identité à votre balise script widget, ou utilisez l'API programmatique après le login.

**Balise script**

```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>
```

**Dynamique (post-login)**

Pour les SPA, identifiez le visiteur après le login sans recharger la page.

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

> **Entrée HMAC**
>
> Le HMAC est calculé sur le userId s'il est fourni (identifiant stable, recommandé), ou l'email en fallback. Utiliser le userId permet de garder le HMAC valide même si l'utilisateur change d'email.

> **Sécurité**
>
> Le secret d'identité ne doit jamais apparaître dans le code client. Générez-le dans votre tableau de bord Gamaro sous Paramètres > Vérification d'identité.

---

## API JavaScript

Contrôlez le widget programmatiquement via window.Versiq.

| Method | Return | Description |
| --- | --- | --- |
| `open()` | void | Ouvrir le widget |
| `close()` | void | Fermer le widget |
| `reset()` | void | Réinitialiser la conversation |
| `setTheme(theme)` | void | Changer le thème dynamiquement |
| `setColorScheme(scheme)` | void | Basculer le mode d'affichage ('light' \| 'dark' \| 'auto') — utile pour câbler le toggle du site host |
| `on(event, handler)` | void | S'abonner à un événement |
| `off(event, handler)` | void | Se désabonner d'un événement |
| `destroy()` | void | Supprimer le widget et libérer les ressources |
| `identify(params)` | void | Identifier un visiteur avec vérification HMAC |
| `version` | string | Version courante du SDK (lecture seule) |
| `applicationId` | string | ID de l'Agent (applicationId) résolu après chargement (vide tant que ready n'a pas été émis) |
| `window.VersiqApps` | Map<string, VersiqAPI> | Map globale des instances widget — clé : applicationId, valeur : VersiqAPI |
