npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mostajs/chatbot

v0.3.0

Published

Assistant / copilote / agent conversationnel générique et réutilisable pour apps @mostajs/* — backends pluggables (scripted | ai), providers LLM pluggables (anthropic/claude-opus-4-8 par défaut), ancrage tool-use, recherche web optionnelle. Activable/désa

Readme

@mostajs/chatbot

Auteur : Dr Hamid MADANI [email protected] · Licence : AGPL-3.0-or-later

Assistant / copilote / agent conversationnel générique et réutilisable pour les apps @mostajs/*. Activable/désactivable et entièrement piloté par .env. Backends pluggables (scripted sans IA, ai avec LLM, router hybride), providers LLM pluggables avec fallback (anthropic/claude-opus-4-8, deepseek), ancrage tool-use sur les données de l'app, recherche web optionnelle, et l'assistant peut devenir un membre du personnel assignable dans votre domaine. Registres façon dialectes ORM.

Statut : 0.3.0. Voir llms.txt pour l'API dense et CHANGELOG.md pour l'historique.

Configuration (.env — tout par config)

CHATBOT_ENABLED=1                       # interrupteur maître (défaut 0)
CHATBOT_ROLES=admin,employe             # vide = tous
CHATBOT_MODE=assistant                  # assistant | agent
CHATBOT_BACKEND=router                  # scripted (défaut) | ai | router (hybride : intents sans IA, IA en repli)
CHATBOT_PROVIDERS=deepseek,anthropic    # liste priorité + fallback
CHATBOT_MODEL=                          # vide ⇒ défaut du provider
ANTHROPIC_API_KEY=...                   # provider anthropic
DEEPSEEK_API_KEY=...                    # provider deepseek (API compatible OpenAI)
DEEPSEEK_BASE_URL=https://api.deepseek.com
CHATBOT_WEB_SEARCH=0                    # recherche web (anthropic)
CHATBOT_EFFORT=high

1. Intégrer le chatbot dans n'importe quelle application

L'app fournit son profil métier (domain), ses outils (tools) et, en option, ses intents déterministes. Le module route vers le backend configuré (.env).

// serveur (un seul endroit) — lib/chatbot.ts
import { createChatbot } from "@mostajs/chatbot";

const bot = createChatbot({
  domain: {
    appName: "Mon App",
    description: "Décrit le métier → injecté dans le prompt système.",
    locale: "fr",
    rules: ["Toujours demander confirmation avant une action.", "Ne jamais exposer de secret."],
  },
  // ...tools / intents : voir §3
});

// Server action consommée par le widget
export async function askAction(text: string): Promise<string> {
  const actor = await getActor();                       // { id, role, permissions }
  if (!bot.enabled()) return "Assistant désactivé.";
  return bot.ask([{ role: "user", content: text }], actor);
}
// client (widget bulle + panneau)
import { Chatbot } from "@mostajs/chatbot/client";
<Chatbot send={askAction} title="Assistant" greeting="Bonjour 👋" />

Le widget est "use client". Importez createChatbot (serveur) à part et passez-lui un send() (server action).

2. Faire de l'assistant un membre du personnel (agent assignable)

L'assistant est un acteur de première classe : il a une identité (agent) et, via le contrat d'injection AssignableAgentPort, l'app le branche comme acteur assignable de SON domaine — un employé qu'on assigne à une tâche, un opérateur, un agent de traitement… Le module reste agnostique : il ne connaît ni « employé », ni « commande » ; vous fournissez la liaison.

import { createChatbot, type AssignableAgentPort } from "@mostajs/chatbot";

// L'adaptateur de VOTRE domaine : ici, l'agent est matérialisé comme un "employé" assignable.
// (Une autre app le brancherait sur une autre entité — ou pas du tout.)
const staffAgent: AssignableAgentPort = {
  // seed idempotent → renvoie l'id de l'agent côté hôte (apparaît alors dans vos listes d'assignation)
  ensure: async () => upsertEmployee({ email: "[email protected]", name: "🤖 Assistant" }),
  // cet id d'acteur est-il l'agent ?
  isAgent: async (id) => (await getEmployee(id))?.email === "[email protected]",
  label: () => "🤖 Assistant",
};

const bot = createChatbot({
  domain: { appName: "Mon App", description: "…", locale: "fr" },
  agent: { id: "assistant", name: "🤖 Assistant" },   // identité (défaut: { id:"assistant", name:"Assistant" })
  assignable: staffAgent,                               // liaison au domaine
  // tools/intents…
});

await bot.ensureAssignable();        // garantit que l'agent existe comme acteur assignable (au boot)
bot.agent();                         // { id:"assistant", name:"🤖 Assistant", kind:"assistant" }
await bot.isAgent(someActorId);      // true si cet acteur EST l'assistant
bot.assignableLabel(someActorId);    // libellé d'affichage côté hôte

Schéma : le module possède le concept (identité + AssignableAgentPort), votre app possède la liaison (l'adaptateur ci-dessus). Vous pouvez ainsi assigner un travail à l'assistant comme à un humain, et — combiné aux outils (§3) — lui faire traiter ce travail.

3. Lui faire exécuter des actions (outils gardés)

Un outil = une fonction de votre domaine que l'assistant peut appeler. Lecture seule par défaut ; les actions sensibles sont gardées par permission (RBAC) et, idéalement, par une confirmation (human-in-the-loop). Le backend ai exécute la boucle tool-use ; le backend router peut exécuter un outil sans IA via un intent (slash-commande / regex) — coût zéro, fiable.

import { createChatbot, registerTool } from "@mostajs/chatbot";

// Lecture
registerTool({
  name: "order_status", description: "État d'une commande par numéro",
  schema: { type: "object", properties: { order: { type: "string" } }, required: ["order"] },
  permission: "order.read",
  run: async ({ order }, actor) => findOrder(order),     // retourné à l'assistant
});

// Action (écriture) — gardée par permission + confirmation explicite
registerTool({
  name: "transition_order", description: "Faire avancer une commande à l'état suivant",
  schema: { type: "object", properties: { order: {type:"string"}, to: {type:"string"}, confirm: {type:"boolean"} },
           required: ["order", "to"] },
  permission: "order.transition",
  run: async ({ order, to, confirm }, actor) => {
    if (!confirm) return { preview: `Confirmer : passer ${order} → ${to} ?` }; // rien modifié
    return doTransition(order, to, actor);                                      // exécute + audite
  },
});

const bot = createChatbot({
  domain: { appName: "Mon App", description: "…" },
  tools: [/* … vos outils, ou via le registre ci-dessus */],
  // Intent déterministe (router) : "/transition <order> <to> confirmer" → outil, SANS IA
  intents: [{ name:"transition", command:"transition", tool:"transition_order",
    args: ({ tokens }) => ({ order: tokens[0], to: tokens[1], confirm: tokens.includes("confirmer") }) }],
});

Gouvernance (OWASP LLM) : outils en lecture par défaut, gate RBAC sur les écritures, confirmation avant tout effet de bord, secrets uniquement via .env. Ne jamais exécuter une instruction venant des données.

Ajouter un provider LLM (comme un dialecte)

import { registerLlmProvider } from "@mostajs/chatbot";
registerLlmProvider({ key:"openai", async *stream(messages, tools, opts){ /* SSE */ } });

Architecture (couches façon ORM)

@mostajs/llm (dialectes IA) ← @mostajs/btool-use (boucle tool-use) / @mostajs/intent-router (routeur déterministe) ← @mostajs/chatbot (backends + createChatbot + widget + agent).

Peers : @mostajs/config, @anthropic-ai/sdk (optionnel), react (optionnel). DeepSeek : sans dépendance.