BetterMeter
Documentation
Documentation

Suivez tout.
Comprenez tout.

BetterMeter est une plateforme d'analytique respectueuse de la vie privée pour l'ère de l'IA. Suivez vos sites web, outils CLI, serveurs MCP et API -- le tout depuis un seul endroit.

01

Pour commencer

BetterMeter suit quatre types de sources. Chacune utilise une intégration légère qui envoie les événements au même pipeline respectueux de la vie privée.

Sites web
<script> tag
~1 Ko, sans cookies
Outils CLI
Node SDK
trackCommand()
Serveurs MCP
Node SDK
wrapMcpServer()
API
Node SDK
expressMiddleware()

Toutes les sources passent par le même pipeline : événement -> traitement (analyse du référent, détection de robots, géolocalisation, hachage de confidentialité) -> base de données. Vous consultez le tout dans le tableau de bord, le CLI ou via les outils MCP.

02

Suivi web

Ajoutez une seule balise script à votre site. Sans cookies, sans configuration CNAME, sans installation complexe. ~1,3 Ko compressé.

HTML -- add to <head>
<script defer data-site="example.com" src="https://bettermeter.com/api/script"></script>

Optionnel : ajoutez un pixel de suivi no-JS avant la balise de fermeture </body> pour détecter les robots et crawlers qui n'exécutent pas JavaScript :

HTML -- add before </body>
<img src="https://bettermeter.com/api/pixel?s=example.com" alt="" style="position:absolute;width:0;height:0;overflow:hidden" />

Le script de suivi capture automatiquement les pages vues, la navigation SPA (History API) et les retours d'onglet. Pour les événements personnalisés et l'identification des utilisateurs :

JavaScript
// Track custom events
window.bettermeter.track("signup", { plan: "pro" });

// Identify users (optional)
window.bettermeter.identify("user_123");

Attributs

data-siterequis
string
Votre domaine tel qu'enregistré dans BetterMeter
data-api
string
Point d'accès API personnalisé pour les configurations de proxy (ex. : /api/collect)
data-no-heartbeat
flag
Désactiver le suivi des visiteurs en temps réel pour cette page

Détection de robots côté serveur

La plupart des robots (Googlebot, GPTBot, ClaudeBot, etc.) n'exécutent pas JavaScript, donc le script de suivi ne se déclenche jamais pour eux. Pour détecter le trafic des robots et crawlers, ajoutez une ligne à votre middleware Next.js :

middleware.ts
import { reportBotVisit } from "@bettermeter/node/middleware";

export function middleware(request) {
  reportBotVisit(request, "example.com");
  // ... rest of your middleware
}

Installer le SDK :

Terminal
npm install @bettermeter/node

Compatible avec l'edge, non bloquant, sans impact sur la latence. Détecte plus de 25 robots connus, y compris les crawlers IA, les moteurs de recherche et les services de surveillance. Les visites de robots apparaissent automatiquement dans votre tableau de bord Crawlers.

03

Suivi CLI

Suivez comment les développeurs utilisent votre outil CLI. Voyez quelles commandes sont populaires, combien de temps elles prennent et quelles erreurs surviennent -- sans collecter de données personnelles.

Install
npm install @bettermeter/node
Auto-track with Commander.js
import { BetterMeter } from "@bettermeter/node";
import { Command } from "commander";

const bm = new BetterMeter({
  siteId: "my-cli-tool",
  apiKey: "bm_...",
});

const program = new Command();

// Wraps all commands — tracks name, flags, exit code
bm.wrapCommander(program, { version: "1.0.0" });

program.command("deploy").action(() => { /* ... */ });
program.parse();

// Flush on exit
process.on("SIGTERM", () => bm.shutdown());
Manual tracking
bm.trackCommand({
  command: "deploy",
  subcommand: "preview",
  flags: ["--prod", "--verbose"],
  version: "2.1.0",
  durationMs: 4500,
  exitCode: 0,
  isCi: !!process.env.CI,
});

Ce qui est suivi

Uniquement les noms de commandes et de drapeaux. Les valeurs des drapeaux, les arguments et les chemins de fichiers ne sont jamais envoyés. Le SDK capture le système d'exploitation et l'architecture pour les analyses d'environnement.

04

Suivi MCP

Suivez quels outils les clients IA appellent sur votre serveur MCP, à quelle fréquence, quels clients (Claude, Cursor, Windsurf) génèrent le plus d'utilisation, et les taux d'erreur.

Auto-track all tools
import { BetterMeter } from "@bettermeter/node";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

const bm = new BetterMeter({
  siteId: "my-mcp-server",
  apiKey: "bm_...",
});

const server = new McpServer({
  name: "my-server",
  version: "1.0.0",
});

// Wraps server.tool() — auto-tracks every invocation
bm.wrapMcpServer(server);

// Tools registered after wrapping are automatically tracked
server.tool("search_docs", { query: z.string() }, async (args) => {
  // ... your tool logic (unchanged)
});
Manual tracking
bm.trackTool({
  tool: "search_docs",
  client: "claude-code",
  protocolVersion: "2024-11-05",
  durationMs: 250,
  success: true,
  inputTokens: 150,
  outputTokens: 800,
});

Ce qui est suivi

Noms des outils, noms des clients, durée, succès/échec et compteurs de tokens optionnels. Les paramètres d'entrée et le contenu de sortie des outils ne sont jamais envoyés.

05

Suivi API

Suivez l'utilisation des endpoints API, la latence, les taux d'erreur et les profils d'utilisation avec le middleware Express ou des appels manuels.

Express middleware
import { BetterMeter } from "@bettermeter/node";
import express from "express";

const bm = new BetterMeter({
  siteId: "my-api",
  apiKey: "bm_...",
});

const app = express();

// Auto-track all requests
app.use(bm.expressMiddleware());
Manual tracking
bm.trackApi({
  method: "POST",
  endpoint: "/api/users",    // Use patterns, not actual paths with IDs
  statusCode: 201,
  durationMs: 45,
});

Ce qui est suivi

Méthode HTTP, modèle d'endpoint, code de statut et durée. Les corps de requête/réponse, les en-têtes, les paramètres de requête et les paramètres de chemin ne sont jamais envoyés. Utilisez des modèles d'endpoint (/api/users/:id) et non des chemins réels (/api/users/abc123).

06

Référence SDK

Le SDK @bettermeter/node est sans dépendance (utilise fetch et crypto intégrés). Nécessite Node.js 18+.

Constructeur

const bm = new BetterMeter(config);
siteIdrequis
string
Domaine ou identifiant enregistré dans BetterMeter
apiKeyrequis
string
Clé API (jeton Bearer) depuis les paramètres du tableau de bord
apiUrl
string
URL de l'API BetterMeter. Par défaut : "https://bettermeter.com"
disabled
boolean
Désactiver tout le suivi. Par défaut : false
batch
boolean
Mettre les événements en file d'attente et vider à intervalle. Par défaut : false
batchInterval
number
Intervalle de vidage en ms. Par défaut : 5000

trackCommand(options)

commandrequis
string
Nom de la commande
subcommand
string
Sous-commande (ex. : "deploy preview")
flags
string[]
Noms des drapeaux utilisés (valeurs supprimées)
version
string
Version du CLI
durationMs
number
Durée d'exécution en millisecondes
exitCode
number
Code de sortie du processus (0 = succès)
isCi
boolean
Exécution dans un environnement CI
userId
string
Identifiant utilisateur personnalisé
properties
object
Propriétés personnalisées supplémentaires

trackTool(options)

toolrequis
string
Nom de l'outil MCP
client
string
Nom du client IA (ex. : "claude-code", "cursor")
protocolVersion
string
Version du protocole MCP
durationMs
number
Durée d'exécution en millisecondes
success
boolean
Indique si l'appel a réussi
errorType
string
Classification de l'erreur (ex. : "validation_error")
inputTokens
number
Nombre de tokens en entrée
outputTokens
number
Nombre de tokens en sortie
userId
string
Identifiant utilisateur personnalisé
properties
object
Propriétés personnalisées supplémentaires

trackApi(options)

methodrequis
string
Méthode HTTP (GET, POST, etc.)
endpointrequis
string
Modèle d'endpoint (utilisez :param pour les segments dynamiques)
statusCode
number
Code de statut de la réponse HTTP
durationMs
number
Temps de réponse en millisecondes
userId
string
Identifiant utilisateur personnalisé
properties
object
Propriétés personnalisées supplémentaires

Encapsuleurs automatiques

wrapCommander(program, options?)

S'accroche au postAction de Commander.js pour suivre automatiquement toutes les commandes.

wrapMcpServer(server)

Patch dynamique de server.tool() pour encapsuler tous les gestionnaires avec chronométrage et suivi des erreurs.

expressMiddleware()

Retourne un middleware Express/Connect qui suit chaque requête sur res.end.

Détection de robots côté serveur

La plupart des robots n'exécutent pas JavaScript, donc le script de suivi ne se déclenche jamais pour eux. Utilisez reportBotVisit() dans votre middleware serveur pour détecter les robots au niveau de la requête.

middleware.ts
// Next.js middleware
import { reportBotVisit } from "@bettermeter/node/middleware";

export function middleware(request) {
  reportBotVisit(request, "my-site.com");
  // ... rest of your middleware
}
requestrequis
Request
Requête entrante (NextRequest, Request, etc.)
siteIdrequis
string
Votre identifiant de site BetterMeter
options.apiUrl
string
URL de l'API BetterMeter. Par défaut : https://bettermeter.com

Compatible avec l'edge (sans dépendances Node.js). Non bloquant -- n'ajoute aucune latence aux réponses. Ignore automatiquement les ressources statiques, les routes API et les fichiers internes Next.js.

Cycle de vie

flush(): Promise<void>

Envoyer immédiatement tous les événements en file d'attente.

shutdown(): Promise<void>

Arrêter le minuteur de lot et vider les événements restants. Appeler avant la fin du processus.

07

Référence CLI

Le CLI BetterMeter vous permet d'interroger les analyses depuis le terminal avec un affichage visuel élégant. Toutes les commandes acceptent -s/--site, -r/--range, -l/--limit et --json.

Install & authenticate
npm install -g bettermeter
bettermeter login -t <apiKey> -u <dashboardUrl>

Formats de sortie

Par défaut, le CLI affiche un rendu visuel riche avec des graphiques ASCII, du texte coloré, des sparklines et des cartes de statistiques. Toute la sortie utilise des caractères Unicode compatibles avec tous les terminaux modernes. Les couleurs détectent automatiquement les capacités du terminal et respectent la variable d'environnement NO_COLOR.

Par défaut (visuel)Graphiques linéaires, barres horizontales, sparklines, tableaux stylisés avec couleurs
--jsonDonnées JSON brutes -- idéal pour le scripting, le piping vers jq ou l'utilisation programmatique

La sortie visuelle inclut :

  • Graphiques linéaires pour les données temporelles (visiteurs quotidiens, invocations)
  • Barres horizontales pour les listes classées (pages, sources, pays)
  • Sparklines intégrées aux statistiques d'ensemble pour une visualisation rapide des tendances
  • Cartes de statistiques avec indicateurs de variation colorés pour les vues d'ensemble
  • Tableaux stylisés avec bordures pour les données détaillées
Example: visual vs JSON
# Visual output (default)
bettermeter stats -s example.com

# JSON output for scripting
bettermeter stats -s example.com --json | jq '.visitors'

Authentification

login -t <key> -u <url>S'authentifier avec une clé API et l'URL du tableau de bord
logoutSupprimer les identifiants enregistrés
whoamiAfficher l'utilisateur actuellement authentifié

Temps réel

live -s <siteId>Nombre de visiteurs en direct (--json pour la sortie brute)

Analytique web

statsAperçu : visiteurs, pages vues, sessions + % de variation
pagesPages les plus visitées par nombre de visiteurs
sourcesSources de trafic (--filter all|ai|traditional)
ai-trafficRépartition des référencements IA par plateforme
botsTrafic robots/crawlers (--category all|ai-crawler|search|monitoring|scraper)
timeseriesTendance quotidienne visiteurs/pages vues
countriesVisiteurs par pays
devicesRépartition par appareil
browsersRépartition par navigateur
visitorsVisiteurs récents avec résumé d'activité
eventsÉvénements personnalisés avec compteurs
campaignsAttribution des URL de campagne (UTM + identifiants de clic)
channelsRépartition par canal (Direct, Recherche payante, Organique, etc.)
exportRapport complet (--format json|csv|md)

Analytique CLI

cli-overviewInvocations, appelants, taux de succès, durée moyenne
cli-commandsCommandes les plus utilisées par nombre d'invocations
cli-timeseriesActivité CLI quotidienne

Analytique MCP

mcp-overviewInvocations, appelants, taux de succès, durée moyenne
mcp-toolsOutils MCP les plus utilisés par nombre d'invocations
mcp-clientsRépartition par client (Claude, Cursor, etc.)
mcp-timeseriesActivité MCP quotidienne

Analytique API

api-overviewInvocations, appelants, taux d'erreur, durée moyenne
api-endpointsEndpoints les plus sollicités par nombre d'invocations
api-timeseriesActivité API quotidienne

Pulse AI

pulse insightsAnomalies, tendances, opportunités, jalons
pulse healthScore de santé produit (0-100, note A-F)
pulse briefingBriefing quotidien/hebdomadaire (-p/--period daily|weekly)
pulse forecastPrévision de trafic (-m/--metric, -d/--days)
pulse compareComparer deux périodes (-r/--range, --from2, --to2)
pulse alertsLister les règles d'alerte de surveillance
pulse alerts:createCréer une alerte (-t/--type, -n/--name, -c/--condition)
pulse alerts:deleteSupprimer une alerte (-i/--id)
pulse notificationsNotifications récentes (--unread pour les non lues uniquement)

Classements de recherche et visibilité IA

brand-report <domain>Générer un rapport de classements de recherche (-q/--queries)
brand-config <domain>Voir/mettre à jour la configuration de surveillance de marque
brand-compare <domain>Comparer les classements avec les concurrents (-q, -c)
brand-alerts <domain>Gérer les alertes de classement (-a list|create|delete)
ai-mentions <domain>Mentions de marque par chatbots IA (-q/--queries, -p/--providers)
backlinks <domain>Profil de backlinks : rang du domaine, domaines référents

Gestion des sites

sites listLister tous les sites
sites add <domain>Ajouter un nouveau site (-n/--name pour le nom d'affichage)
sites remove <siteId>Supprimer un site
sites info <siteId>Afficher les détails du site et le code de suivi
install <siteId>Obtenir le code de suivi pour un site

Gestion de l'équipe

members list -s <siteId>Lister les membres du site
members add <email>Ajouter un membre (-s, -r viewer|editor|admin, --all-sites)
members remove <id>Supprimer un membre (-s)
members update-role <id>Mettre à jour le rôle d'un membre (-s, -r)

Facturation

billingAfficher le forfait actuel, l'utilisation et les informations de facturation (--json)

Options

-s, --siterequis
string
Identifiant du site (domaine)
-r, --range
string
Plage de dates : today, 7d, 30d, 90d, 12m. Par défaut : 30d
-l, --limit
number
Nombre maximum de résultats. Par défaut : 10
--json
flag
Sortie JSON brute au lieu des graphiques et tableaux visuels
08

Référence des outils MCP

BetterMeter fonctionne comme un serveur MCP pour les assistants IA. Chaque commande CLI a un outil MCP correspondant avec le préfixe bettermeter_.

.mcp.json
{
  "mcpServers": {
    "bettermeter": {
      "command": "npx",
      "args": ["bettermeter"]
    }
  }
}

Tous les outils d'analytique acceptent site_id (string), range (today|7d|30d|90d|12m) et limit (number). L'outil MCP bettermeter_export supporte les formats json et md (la commande CLI export supporte aussi csv). Exemples d'utilisation pour les assistants IA :

"How many live visitors right now?"bettermeter_live_visitors
"How's my traffic?"bettermeter_stats
"Show AI referral breakdown"bettermeter_ai_traffic
"Which CLI commands are most used?"bettermeter_cli_commands
"What AI clients call my MCP server?"bettermeter_mcp_clients
"Top API endpoints this week"bettermeter_api_endpoints with range=7d
"How healthy is my product?"bettermeter_pulse_health
"Any anomalies I should know about?"bettermeter_pulse_insights
"Give me a weekly briefing"bettermeter_pulse_briefing with period=weekly

Liste complète des outils

Temps réel

bettermeter_live_visitors

Analytique web

bettermeter_statsbettermeter_pagesbettermeter_sourcesbettermeter_ai_trafficbettermeter_botsbettermeter_timeseriesbettermeter_countriesbettermeter_devicesbettermeter_browsersbettermeter_visitorsbettermeter_visitorbettermeter_eventsbettermeter_campaignsbettermeter_channelsbettermeter_export

Analytique CLI

bettermeter_cli_overviewbettermeter_cli_commandsbettermeter_cli_timeseries

Analytique MCP

bettermeter_mcp_overviewbettermeter_mcp_toolsbettermeter_mcp_clientsbettermeter_mcp_timeseries

Analytique API

bettermeter_api_overviewbettermeter_api_endpointsbettermeter_api_timeseries

Gestion des sites

bettermeter_sites_listbettermeter_sites_addbettermeter_sites_removebettermeter_sites_infobettermeter_install

Gestion de l'équipe

bettermeter_members_listbettermeter_members_addbettermeter_members_removebettermeter_members_update_role

Classements de recherche et visibilité IA

bettermeter_brand_reportbettermeter_brand_configbettermeter_brand_comparebettermeter_brand_alertsbettermeter_ai_mentionsbettermeter_backlinks

Facturation

bettermeter_billing

Pulse AI

bettermeter_pulse_insightsbettermeter_pulse_healthbettermeter_pulse_briefingbettermeter_pulse_forecastbettermeter_pulse_comparebettermeter_pulse_alerts_listbettermeter_pulse_alerts_createbettermeter_pulse_alerts_deletebettermeter_pulse_notifications
09

Référence API

Tous les endpoints d'analytique acceptent les requêtes GET avec des paramètres de requête. Authentifiez-vous avec Authorization: Bearer <api_key>.

Ingestion d'événements

POST /api/eventIngérer un événement (web, CLI, MCP ou API). Retourne 202.
Event payload
{
  "site_id": "example.com",
  "event_name": "pageview",        // or "cli.command", "mcp.tool", "api.request"
  "event_source": "web",           // "web" | "cli" | "mcp" | "api"
  "url": "https://example.com/page",
  "pathname": "/page",
  "hostname": "example.com",
  "referrer": "https://google.com",
  "screen_width": 1920,
  "timezone": "America/New_York",
  "user_id": "optional_user_id",
  "properties": { "key": "value" }
}

Temps réel et heartbeat

GET /api/analytics/liveNombre de visiteurs en direct. Retourne { live: number }. Accepte ?siteId=...
POST /api/heartbeatRecevoir les heartbeats du navigateur pour le suivi des visiteurs en direct
POST /api/hAlias furtif pour /api/heartbeat (résistant aux bloqueurs de publicités)

Endpoints de requête

Tous acceptent ?siteId=...&from=YYYY-MM-DD&to=YYYY-MM-DD.

Analytique web

GET /api/analytics/overviewVisiteurs, pages vues, sessions + % de variation
GET /api/analytics/pagesPages les plus visitées par nombre de visiteurs
GET /api/analytics/sourcesSources de trafic avec détection IA
GET /api/analytics/timeseriesTendance quotidienne visiteurs/pages vues
GET /api/analytics/ai-trafficRépartition des référencements IA par plateforme
GET /api/analytics/botsTrafic robots/crawlers
GET /api/analytics/countriesVisiteurs par pays
GET /api/analytics/devicesRépartition par type d'appareil
GET /api/analytics/browsersRépartition par navigateur
GET /api/analytics/visitorsListe des visiteurs avec activité
GET /api/analytics/visitors/[visitorId]Profil du visiteur avec chronologie des événements
GET /api/analytics/eventsÉvénements personnalisés
GET /api/analytics/campaignsAttribution des URL de campagne (UTM + identifiants de clic)
GET /api/analytics/campaigns/[campaign]Détail d'une seule campagne
GET /api/analytics/channelsRépartition par canal
GET /api/analytics/session-statsStatistiques de sessions
GET /api/analytics/sessionsListe des sessions

Analytique CLI

GET /api/analytics/cli-overviewInvocations, appelants, taux de succès
GET /api/analytics/cli-commandsCommandes les plus utilisées
GET /api/analytics/cli-timeseriesActivité CLI quotidienne

Analytique MCP

GET /api/analytics/mcp-overviewInvocations, appelants, taux de succès
GET /api/analytics/mcp-toolsOutils les plus utilisés
GET /api/analytics/mcp-clientsRépartition par client
GET /api/analytics/mcp-timeseriesActivité MCP quotidienne

Analytique API

GET /api/analytics/api-overviewInvocations, appelants, taux d'erreur
GET /api/analytics/api-endpointsEndpoints les plus sollicités
GET /api/analytics/api-timeseriesActivité API quotidienne

Classements de recherche et visibilité IA

GET /api/analytics/brandRapport de visibilité des classements de recherche
GET /api/analytics/brand/historyDonnées historiques de classement
GET /api/analytics/brand/compareComparaison avec les concurrents
GET /api/analytics/brand/alertsRègles d'alerte de classement
GET /api/analytics/brand/exportExporter les données de classement
GET /api/analytics/backlinksProfil de backlinks
GET /api/analytics/ai-mentionsMentions de marque par chatbots IA
GET /api/analytics/ai-mentions/historyHistorique des mentions IA

Pulse AI

GET /api/pulse/insightsAnomalies, tendances, opportunités
GET /api/pulse/healthScore de santé produit (0-100)
GET /api/pulse/briefingBriefing quotidien ou hebdomadaire
GET /api/pulse/forecastPrévision de trafic/utilisation
GET /api/pulse/compareComparaison de périodes
GET /api/pulse/alertsLister les alertes de surveillance
POST /api/pulse/alertsCréer une règle d'alerte
DELETE /api/pulse/alertsSupprimer une règle d'alerte
GET /api/pulse/notificationsObtenir les notifications
PATCH /api/pulse/notificationsMarquer les notifications comme lues
POST /api/pulse/chatChat Pulse AI (streaming)
10

Pulse AI

Pulse AI est la couche d'intelligence analytique proactive de BetterMeter. Au lieu d'attendre que vous consultiez les tableaux de bord, Pulse détecte automatiquement les anomalies, fait remonter les insights, évalue la santé du produit, prédit les tendances et livre des briefings quotidiens ou hebdomadaires.

Capacités

Détection d'anomalies
Détecte les pics de trafic, les baisses, les variations de taux d'erreur, les afflux de robots et les nouvelles sources de référencement
Insights proactifs
Fait remonter les tendances, les opportunités de croissance et les jalons avant que vous ne demandiez
Score de santé
Score unifié de 0 à 100 (note A-F) couvrant trafic, engagement, erreurs et croissance
Prévisions
Extrapole les tendances de trafic et d'utilisation avec intervalles de confiance
Comparaison de périodes
Analyse approfondie côte à côte de deux périodes avec métriques différentielles
Briefings
Résumés quotidiens ou hebdomadaires avec métriques clés, faits saillants et actions à entreprendre

Types d'alertes

Créez des alertes de surveillance qui génèrent des notifications lorsque les conditions sont remplies :

traffic_spikeAlerte lorsque le trafic dépasse un seuil
traffic_dropAlerte lorsque le trafic descend sous un seuil
error_rateAlerte lorsque le taux d'erreur dépasse un seuil
bot_surgeAlerte en cas de pics inhabituels d'activité de robots/crawlers
new_ai_crawlerAlerte lorsqu'un nouveau crawler IA est détecté
performanceAlerte en cas de dégradation des performances

Endpoints API

GET /api/pulse/insightsAnomalies, tendances, opportunités, jalons
GET /api/pulse/healthScore de santé produit (0-100) avec répartition par catégorie
GET /api/pulse/briefingRésumé de briefing quotidien ou hebdomadaire
GET /api/pulse/forecastPrévision de trafic/utilisation avec intervalles de confiance
GET /api/pulse/compareComparaison approfondie période par période
GET /api/pulse/alertsLister les règles d'alerte de surveillance
POST /api/pulse/alertsCréer une nouvelle règle d'alerte
DELETE /api/pulse/alertsSupprimer une règle d'alerte
GET /api/pulse/notificationsObtenir les notifications récentes
PATCH /api/pulse/notificationsMarquer les notifications comme lues

Commandes CLI

pulse insightsObtenir les insights proactifs et les anomalies
pulse healthScore de santé produit avec note
pulse briefingBriefing quotidien ou hebdomadaire (-p/--period daily|weekly)
pulse forecastPrévision de trafic (-m/--metric, -d/--days)
pulse compareComparer deux périodes (-r/--range, --from2, --to2)
pulse alertsLister les alertes de surveillance
pulse alerts:createCréer une alerte (-t/--type, -n/--name, -c/--condition)
pulse alerts:deleteSupprimer une alerte (-i/--id)
pulse notificationsNotifications récentes (--unread pour les non lues uniquement)

Outils MCP

bettermeter_pulse_insightsbettermeter_pulse_healthbettermeter_pulse_briefingbettermeter_pulse_forecastbettermeter_pulse_comparebettermeter_pulse_alerts_listbettermeter_pulse_alerts_createbettermeter_pulse_alerts_deletebettermeter_pulse_notifications
11

Configuration du proxy

Les bloqueurs de publicités bloquent parfois les scripts d'analytique tiers. En relayant BetterMeter via votre propre domaine, le script et les événements apparaissent comme des requêtes de première partie -- les rendant invisibles aux bloqueurs. Cela fonctionne car le navigateur voit des requêtes vers yourdomain.com/bm/... au lieu de bettermeter.com/api/....

Comment ça fonctionne

Vous configurez des réécritures d'URL sur votre serveur pour que trois chemins de votre domaine redirigent vers BetterMeter :

/bm/s→ https://bettermeter.com/api/sScript de suivi
/bm/e→ https://bettermeter.com/api/eEndpoint d'événements
/bm/p→ https://bettermeter.com/api/pPixel noscript

Ensuite, mettez à jour votre code de suivi pour utiliser les chemins du proxy :

Proxied snippet
<script defer data-site="example.com" data-api="/bm/e" src="/bm/s"></script>
<img src="/bm/p?s=example.com" alt="" style="position:absolute;width:0;height:0;overflow:hidden" />

L'attribut data-api indique au script d'envoyer les événements à votre endpoint proxy au lieu de directement à BetterMeter. Le src charge le script depuis votre proxy. Pour le navigateur (et les bloqueurs de publicités), tout est une requête de même origine.

Guides par plateforme

Next.js

next.config.js
module.exports = {
  async rewrites() {
    return [
      { source: "/bm/s", destination: "https://bettermeter.com/api/s" },
      { source: "/bm/e", destination: "https://bettermeter.com/api/e" },
      { source: "/bm/p", destination: "https://bettermeter.com/api/p" },
    ];
  },
};

Nuxt

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    "/bm/s": { proxy: "https://bettermeter.com/api/s" },
    "/bm/e": { proxy: "https://bettermeter.com/api/e" },
    "/bm/p": { proxy: "https://bettermeter.com/api/p" },
  },
});

SvelteKit

svelte.config.js
const config = {
  kit: {
    // SvelteKit doesn't have built-in rewrites.
    // Use a server hook instead:
  },
};
src/hooks.server.ts
import type { Handle } from "@sveltejs/kit";

const PROXY_MAP: Record<string, string> = {
  "/bm/s": "https://bettermeter.com/api/s",
  "/bm/e": "https://bettermeter.com/api/e",
  "/bm/p": "https://bettermeter.com/api/p",
};

export const handle: Handle = async ({ event, resolve }) => {
  const target = PROXY_MAP[event.url.pathname];
  if (target) {
    const url = new URL(target);
    url.search = event.url.search;
    const res = await fetch(url, {
      method: event.request.method,
      headers: event.request.headers,
      body: event.request.method !== "GET" ? event.request.body : undefined,
      // @ts-expect-error — needed for streaming
      duplex: "half",
    });
    return new Response(res.body, {
      status: res.status,
      headers: res.headers,
    });
  }
  return resolve(event);
};

Astro

src/pages/bm/[...proxy].ts
import type { APIRoute } from "astro";

const PROXY_MAP: Record<string, string> = {
  s: "https://bettermeter.com/api/s",
  e: "https://bettermeter.com/api/e",
  p: "https://bettermeter.com/api/p",
};

export const ALL: APIRoute = async ({ params, request }) => {
  const target = PROXY_MAP[params.proxy ?? ""];
  if (!target) return new Response("Not found", { status: 404 });

  const url = new URL(target);
  url.search = new URL(request.url).search;
  return fetch(url, {
    method: request.method,
    headers: request.headers,
    body: request.method !== "GET" ? request.body : undefined,
    // @ts-expect-error — needed for streaming
    duplex: "half",
  });
};

Remix / React Router

app/routes/bm.$.tsx
import type { LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node";

const PROXY_MAP: Record<string, string> = {
  s: "https://bettermeter.com/api/s",
  e: "https://bettermeter.com/api/e",
  p: "https://bettermeter.com/api/p",
};

async function proxy({ request, params }: LoaderFunctionArgs | ActionFunctionArgs) {
  const target = PROXY_MAP[params["*"] ?? ""];
  if (!target) return new Response("Not found", { status: 404 });

  const url = new URL(target);
  url.search = new URL(request.url).search;
  return fetch(url, {
    method: request.method,
    headers: request.headers,
    body: request.method !== "GET" ? request.body : undefined,
    // @ts-expect-error — needed for streaming
    duplex: "half",
  });
}

export const loader = proxy;
export const action = proxy;

Nginx

nginx.conf
location /bm/s {
    proxy_pass https://bettermeter.com/api/s;
    proxy_ssl_server_name on;
    proxy_set_header Host bettermeter.com;
}

location /bm/e {
    proxy_pass https://bettermeter.com/api/e;
    proxy_ssl_server_name on;
    proxy_set_header Host bettermeter.com;
}

location /bm/p {
    proxy_pass https://bettermeter.com/api/p;
    proxy_ssl_server_name on;
    proxy_set_header Host bettermeter.com;
}

Apache

.htaccess or httpd.conf
RewriteEngine On
RewriteRule ^bm/s$ https://bettermeter.com/api/s [P,L]
RewriteRule ^bm/e$ https://bettermeter.com/api/e [P,L]
RewriteRule ^bm/p$ https://bettermeter.com/api/p [P,L]

# Requires mod_proxy and mod_proxy_http enabled:
# a2enmod proxy proxy_http

Caddy

Caddyfile
example.com {
    handle_path /bm/s {
        reverse_proxy https://bettermeter.com/api/s {
            header_up Host bettermeter.com
        }
    }
    handle_path /bm/e {
        reverse_proxy https://bettermeter.com/api/e {
            header_up Host bettermeter.com
        }
    }
    handle_path /bm/p {
        reverse_proxy https://bettermeter.com/api/p {
            header_up Host bettermeter.com
        }
    }
}

Cloudflare Workers

worker.js
export default {
  async fetch(request) {
    const url = new URL(request.url);
    const map = {
      "/bm/s": "https://bettermeter.com/api/s",
      "/bm/e": "https://bettermeter.com/api/e",
      "/bm/p": "https://bettermeter.com/api/p",
    };
    const target = map[url.pathname];
    if (!target) return fetch(request);

    const dest = new URL(target);
    dest.search = url.search;
    return fetch(dest.toString(), {
      method: request.method,
      headers: { ...Object.fromEntries(request.headers), Host: "bettermeter.com" },
      body: request.method !== "GET" ? request.body : undefined,
    });
  },
};

Vercel (without Next.js)

vercel.json
{
  "rewrites": [
    { "source": "/bm/s", "destination": "https://bettermeter.com/api/s" },
    { "source": "/bm/e", "destination": "https://bettermeter.com/api/e" },
    { "source": "/bm/p", "destination": "https://bettermeter.com/api/p" }
  ]
}

Netlify

netlify.toml
[[redirects]]
  from = "/bm/s"
  to = "https://bettermeter.com/api/s"
  status = 200
  force = true

[[redirects]]
  from = "/bm/e"
  to = "https://bettermeter.com/api/e"
  status = 200
  force = true

[[redirects]]
  from = "/bm/p"
  to = "https://bettermeter.com/api/p"
  status = 200
  force = true

WordPress

Ajoutez ces règles de réécriture au functions.php de votre thème ou à un plugin personnalisé :

functions.php
add_action('init', function() {
    add_rewrite_rule('^bm/s$', 'index.php?bm_proxy=s', 'top');
    add_rewrite_rule('^bm/e$', 'index.php?bm_proxy=e', 'top');
    add_rewrite_rule('^bm/p$', 'index.php?bm_proxy=p', 'top');
});

add_filter('query_vars', function($vars) {
    $vars[] = 'bm_proxy';
    return $vars;
});

add_action('template_redirect', function() {
    $proxy = get_query_var('bm_proxy');
    if (!$proxy) return;

    $map = [
        's' => 'https://bettermeter.com/api/s',
        'e' => 'https://bettermeter.com/api/e',
        'p' => 'https://bettermeter.com/api/p',
    ];
    if (!isset($map[$proxy])) return;

    $url = $map[$proxy];
    if ($_SERVER['QUERY_STRING']) $url .= '?' . $_SERVER['QUERY_STRING'];

    $args = ['method' => $_SERVER['REQUEST_METHOD'], 'timeout' => 10];
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $args['body'] = file_get_contents('php://input');
        $args['headers'] = ['Content-Type' => $_SERVER['CONTENT_TYPE'] ?? 'application/json'];
    }

    $response = wp_remote_request($url, $args);
    if (is_wp_error($response)) { status_header(502); exit; }

    status_header(wp_remote_retrieve_response_code($response));
    foreach (wp_remote_retrieve_headers($response) as $k => $v) {
        if (in_array(strtolower($k), ['content-type', 'cache-control'])) header("$k: $v");
    }
    echo wp_remote_retrieve_body($response);
    exit;
});

// After adding, flush rewrite rules: Settings → Permalinks → Save

Django

urls.py + views.py
# urls.py
from django.urls import path
from . import views

urlpatterns = [
    path("bm/<slug:endpoint>", views.bm_proxy),
]

# views.py
import requests
from django.http import HttpResponse

PROXY_MAP = {
    "s": "https://bettermeter.com/api/s",
    "e": "https://bettermeter.com/api/e",
    "p": "https://bettermeter.com/api/p",
}

def bm_proxy(request, endpoint):
    target = PROXY_MAP.get(endpoint)
    if not target:
        return HttpResponse("Not found", status=404)
    url = target + ("?" + request.META["QUERY_STRING"] if request.META.get("QUERY_STRING") else "")
    resp = requests.request(
        method=request.method, url=url,
        headers={"Host": "bettermeter.com", "Content-Type": request.content_type},
        data=request.body if request.method == "POST" else None,
        timeout=10,
    )
    return HttpResponse(resp.content, status=resp.status_code, content_type=resp.headers.get("Content-Type"))

Rails

config/routes.rb + controller
# config/routes.rb
match "/bm/:endpoint", to: "bm_proxy#proxy", via: [:get, :post], constraints: { endpoint: /[sep]/ }

# app/controllers/bm_proxy_controller.rb
class BmProxyController < ApplicationController
  skip_before_action :verify_authenticity_token

  PROXY_MAP = { "s" => "/api/s", "e" => "/api/e", "p" => "/api/p" }.freeze

  def proxy
    path = PROXY_MAP[params[:endpoint]]
    return head(:not_found) unless path

    uri = URI("https://bettermeter.com#{path}")
    uri.query = request.query_string.presence

    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    req = (request.post? ? Net::HTTP::Post : Net::HTTP::Get).new(uri)
    req["Host"] = "bettermeter.com"
    if request.post?
      req.body = request.body.read
      req["Content-Type"] = request.content_type
    end

    resp = http.request(req)
    render body: resp.body, status: resp.code.to_i, content_type: resp["Content-Type"]
  end
end

Vérification

Après avoir configuré le proxy, vérifiez qu'il fonctionne :

Test
# Should return the tracker JavaScript
curl -s https://yourdomain.com/bm/s | head -5

# Should return 202 (event accepted)
curl -s -o /dev/null -w "%{http_code}" -X POST https://yourdomain.com/bm/e \
  -H "Content-Type: application/json" \
  -d '{"site_id":"yourdomain.com","event_name":"test"}'
12

Modèle de données

Les quatre sources d'événements partagent une seule table Event. La colonne eventSource les distingue. Les métadonnées spécifiques à chaque source sont dans la colonne JSON properties.

Correspondance des champs

Champ
Web
CLI
MCP
API
eventName
pageview
cli.command
mcp.tool
api.request
eventSource
web
cli
mcp
api
pathname
Chemin URL
/command
/tool_name
/endpoint
referrer
document.referrer
(empty)
mcp://client
(empty)
properties
propriétés personnalisées
command, flags, duration, exit_code, os
tool, client, duration, success
method, endpoint, status_code, duration
13

Confidentialité

BetterMeter est conçu dès le départ pour respecter la vie privée des utilisateurs. Aucune bannière de consentement requise. Conforme au RGPD par défaut.

01

Sans cookies

Zéro cookie, zéro stockage local, zéro fingerprinting. Le script de suivi est sans état.

02

Identifiants hachés

Les adresses IP sont hachées en SHA-256 avec la date actuelle incluse dans l'entrée du hachage. L'IP brute n'est jamais stockée. Les hachages de visiteurs quotidiens changent toutes les 24 heures. Un identifiant de visiteur stable (haché sans date) permet les profils de visiteurs au sein d'un site mais ne peut pas être inversé pour révéler l'IP d'origine.

03

Pas de collecte de données personnelles

Le SDK suit les noms de commandes, les noms d'outils et les modèles d'endpoints -- jamais les arguments, les chemins de fichiers, les corps de requête ou les données personnelles.

04

Envoi et oubli

Les appels d'analytique sont asynchrones et ne lèvent jamais d'exception. Un échec réseau abandonne silencieusement l'événement. L'analytique ne doit jamais interrompre l'application hôte.

05

Désactivation

Définissez disabled: true dans la configuration du SDK ou BETTERMETER_DISABLED=1 comme variable d'environnement.

06

Pipeline ouvert

Chaque événement passe par la même fonction processEvent(). Vous pouvez auditer exactement ce qui est collecté et stocké.

BetterMeter Analytics -- Conçu pour le nouvel internet