Sno Station
Sno Station은 AI 에이전트를 하나의 팀으로 묶는 오픈소스 워크스테이션으로, 로컬 머신에서 에이전트 협업을 가능하게 함
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Le chatbot de duhalle-boutique.fr : un conseiller en ligne qui répond aux questions des clients sur les produits Duhallé et sur le savoir-faire du bouchage, de la mise en bouteille, du cidre, du vinaigre et des conserves maison.README 전문 보기 ↗
Le chatbot de duhalle-boutique.fr : un conseiller en ligne qui répond aux questions des clients sur les produits Duhallé et sur le savoir-faire du bouchage, de la mise en bouteille, du cidre, du vinaigre et des conserves maison.
L'algorithme s'inspire de l'« assistant déterministe » d'APISAAS (lib/assistant-deterministe). On n'y trouve pas de fiches question-réponse entières, mais :
| Élément | Rôle | Où |
|---|---|---|
| Concepts | Une notion, un produit ou une rubrique, avec ses alias et sa famille (bouchon-naturel → bouchon-liege → bouchon → bouchage → catalogue) | src/savoir/*.ts |
| Faits | Un énoncé court rattaché à un ou plusieurs concepts, avec son aspect (définition, choix, procédure, durée, prix…), sa source et sa date de vérification | src/savoir/*.ts |
| Types de question | Ce que la question demande, lu dans sa forme : « comment » (procédure), « combien de temps » (durée), « lequel » (choix), « où acheter » (lieu)… Une table ordonnée : la première marque qui s'applique gagne | src/moteur/types-de-question.ts |
| Plans de réponse | Pour chaque type, les aspects cherchés dans l'ordre | src/savoir/types.ts |
Pour chaque question, le moteur procède ainsi :
Cas particuliers :
…/mustimetre-c2x35079023) obtient la réponse sur ce produit, car l'adresse en contient le nom ; un lien vers un autre site n'est pas lisible, et le chatbot le dit. Seul le texte de l'adresse est lu : aucune page n'est ouverte.src/savoir/ventes.ts, sans toucher au moteur ; les tests vérifient que chaque bouton mène à une vraie réponse.trace qui dit pourquoi (type lu, concepts, faits retenus).Aujourd'hui : 252 concepts et 704 faits, répartis en 10 fichiers de rubriques : entreprise, commande (livraison, paiement, retours), mise en bouteille, bouchage, cire, cave et service du vin, cidre, vinaigre, conserves, droguerie.
test/banc.test.ts) : 186 questions formulées comme les clients les posent, avec la réponse attendue. Tout doit passer.test/clients-reels.test.ts) : les questions posées par les vrais clients (lues dans la base D1), puis des questions voisines (autres formulations, cas proches) pour que la réponse vise plus large que la phrase vue. Chaque lot relu y est ajouté.test/inedit.test.ts) : 25 questions écrites après le réglage, qu'on ne règle jamais une par une. Il est mesuré à 68 % de premières réponses idéales, et les écarts restent en général dans le bon sujet. Quand un jeu inédit a servi à corriger, il rejoint le banc et on en écrit un nouveau. Les trois premiers jeux, mesurés à 57 %, 57 % puis 77 %, ont fait progresser les règles.src/
index.ts le Worker : /api/chat, /api/accueil, /api/sante, CORS
moteur/ l'algorithme (aucun mot de métier)
texte.ts normalisation, mots vides, racinisation, fautes de frappe
types-de-question.ts
concepts.ts détection des concepts, lignée
reponse.ts choix des faits selon le plan
recherche.ts filet de sécurité : recherche plein texte dans les faits
assistant.ts orchestration, politesse, relances, repli
savoir/ LA BASE DE CONNAISSANCES
coordonnees.ts téléphone, e-mail, seuils de livraison (à un seul endroit)
ventes.ts les boutons de suggestion : questions de départ, vitrine, compléments
liens.ts les URL du site
entreprise.ts, commande.ts, mise-en-bouteille.ts, bouchage.ts, cire.ts,
cave-service.ts, cidre.ts, vinaigre.ts, conserves.ts, droguerie.ts
widget/widget.ts le script affiché sur le site (bulle + fenêtre)
public/
v/<version>.js les versions compilées du widget (npm run build, versionnées)
widget.js ancienne adresse unique du widget, figée
index.html page de démonstration
oxatis/ les codes à coller dans le back-office Oxatis
test/ base, banc, jeu inédit, Worker, robustesse
Le déploiement passe par Workers Builds : Cloudflare se branche sur ce dépôt GitHub et redéploie à chaque modification de main.
name du fichier wrangler.jsonc : aujourd'hui chatbot-duhalle. Pour le renommer, changez les deux.npx wrangler deploy. Le widget compilé est déjà dans le dépôt.https://chatbot-duhalle.<votre-sous-domaine>.workers.dev.https://chatbot-duhalle.<votre-sous-domaine>.workers.dev/ affiche la page de démonstration, et /api/sante renvoie {"ok":true,…}.Aucune clé secrète n'est nécessaire. Le plan gratuit de Cloudflare Workers (100 000 requêtes par jour) suffit largement.
Sites autorisés. La variable ALLOWED_ORIGINS de wrangler.jsonc liste les sites qui peuvent interroger le chatbot : https://www.duhalle-boutique.fr et https://duhalle-boutique.fr. Pour tester depuis un autre domaine, par exemple l'aperçu Oxatis, ajoutez-le, séparé par une virgule. Évitez * en production : n'importe quel site pourrait alors afficher votre chatbot.
Deux codes sont prêts dans le dossier oxatis/.
oxatis/1-script-chatbot.htmlVOTRE-SOUS-DOMAINE par celui de l'adresse workers.dev.<head> du site, et dans celui du site mobile si Oxatis en a un séparé.Chaque version du widget a sa propre adresse (/v/<version>.js), qui ne change plus et reste en ligne (sauf une version reconnue défectueuse, retirée). Publier une nouvelle version ne touche donc pas au site : il garde la précédente. Pour passer à la nouvelle, attendez la fin du déploiement Cloudflare, puis recopiez la balise du fichier oxatis/1-script-chatbot.html dans Oxatis (ordinateur et mobile). Vérifiez après l'enregistrement que le bloc se termine bien par </script> : un bloc trop long est coupé par Oxatis, et une balise coupée rend la page blanche.
Après 6 secondes sur une page, une petite carte au-dessus de la bulle invite le client à poser sa question, avec un message et une question adaptés à la page (cidre, cire, bouchons, conserves…, voir src/widget/invitations.ts). Elle apparaît sur deux pages au plus par visite, jamais au panier ni pendant la commande, et plus du tout une fois fermée ou le conseiller ouvert ; une pastille « 1 » reste ensuite sur la bulle.
Le visuel (bulle, fenêtre, vert et ocre Duhallé) est dessiné par le script lui-même, isolé du thème : il ne déforme pas le site et le site ne le déforme pas. Sur mobile, la discussion s'ouvre en plein écran. Réglages facultatifs, en attributs de la balise : data-titre, data-couleur, data-position (droite ou gauche), data-decalage (distance au bas de l'écran en pixels, 84 par défaut), data-telephone.
oxatis/2-bloc-bouton-conseiller.htmlUn bloc de design à coller dans un élément « Code HTML » d'une page (contact, FAQ, conseils…). Son bouton ouvre le chatbot et ses questions cliquables le lancent directement. Si le script du chatbot n'est pas chargé, les liens mènent à la page Contact.
Conseil : testez d'abord sur une page brouillon. Si votre offre Oxatis filtre les balises
<script>, contactez le support Oxatis pour savoir où les scripts sont autorisés.
Tout se passe dans src/savoir/. Un ajout sur GitHub, dans main, est redéployé automatiquement.
Ajouter un fait à un concept existant, dans le fichier de la rubrique :
r.fait("bouchon-naturel", "duree", `
Un bouchon en liège naturel de 45 mm est fait pour les vins de longue garde.`);
Ajouter un concept avec ses alias (pluriels, accents et majuscules sont gérés tout seuls) :
r.concept("muselet", "Muselets", ["muselet", "muselets", "agrafe de bouchon"], {
famille: "bouchage",
lien: page("/muselets-c102x…", "Muselets"),
});
Un fait qui croise deux concepts ne sort que si les deux sont cités :
r.fait(["bouchon", "vin-de-garde"], "choix", `Pour un vin de garde, …`);
Une mise en garde (loi, sécurité) se marque avec avertissement: true : elle accompagne toute réponse qui cite son concept, même quand la question porte surtout sur autre chose (aujourd'hui : la distillation à domicile, interdite sans autorisation).
r.fait("distillation", "condition", `En France, la distillation à domicile est interdite…`, { avertissement: true });
Un même énoncé peut être rattaché à plusieurs aspects (par exemple « procédure » et « condition ») pour être trouvé quel que soit le type de la question : la réponse ne l'affiche qu'une fois.
Les aspects possibles sont : definition, usage, gamme, choix, dimension, procedure, duree, moment, condition, raison, erreur, entretien, lieu, prix.
Règles de la maison :
formules.exclusif: true sur un concept (la disponibilité, le mustimètre, le Larroseptol…) fait que ses faits répondent seuls quand il est cité, sans les faits des autres concepts de la phrase (« quand les mèches soufrées reviennent-elles en stock ? » ne rappelle pas le mode d'emploi des mèches). Sans fait de ce concept pour le type de la question, la réponse suit son cours.formules, qui exigent l'expression entière.npm test. Si une question du banc change de réponse, vérifiez que la nouvelle est meilleure avant de mettre le banc à jour.Modifier les boutons de suggestion (questions de départ, vitrine, compléments par produit) : tout est dans src/savoir/ventes.ts. Chaque question y est rangée avec le concept dont sa réponse parle, par exemple q("Quelle boucheuse choisir ?", "boucheuse") ; npm test vérifie que le chatbot y répond. Le widget affiche 5 boutons au plus.
Les questions posées sont enregistrées, anonymisées, dans la base D1 chatbot-duhalle (table questions_chatbot, décrite dans schema/questions_chatbot.sql) : une ligne par question, avec son nombre d'occurrences et la nature de la dernière réponse. C'est la meilleure source pour savoir quels alias et quels faits ajouter. Les plus fréquentes restées sans réponse :
SELECT question, occurrences, dernier_jour FROM questions_chatbot
WHERE statut = 'a_revoir' AND nature_derniere IN ('inconnu', 'recherche')
ORDER BY occurrences DESC LIMIT 50;
Après tri, passez le statut à retenue, integree ou ignoree. Chaque nuit, une tâche planifiée du Worker supprime les questions a_revoir ou ignoree vues pour la dernière fois il y a plus de 90 jours.
Le chatbot ajoute un script à une boutique en ligne : c'est le point à protéger. Ce qui est en place :
| Risque | Protection |
|---|---|
Un widget.js modifié (compte GitHub ou Cloudflare piraté) qui lirait les pages de la boutique | Empreinte d'intégrité (SRI) sur la balise : le navigateur refuse tout fichier qui n'est pas exactement celui publié. Chaque version est publiée à une adresse fixe (/v/<version>.js) ; npm run build reporte adresse et empreinte dans oxatis/1-script-chatbot.html, et la CI vérifie qu'elles sont à jour. |
| Injection de code dans la fenêtre de discussion | Aucun texte reçu (réponse, stockage) n'est inséré comme HTML : tout passe par textContent. Les liens ne mènent qu'à duhalle-boutique.fr en https. Les réponses et la conversation gardée en sessionStorage sont vérifiées avant affichage. |
| Robot qui inonde l'API (quota, coûts) | 30 appels par minute et par adresse IP (binding ratelimits de Cloudflare), puis réponse 429 et message « merci de patienter ». |
| Message énorme ou piégé | Corps limité à 4 000 octets, lu en flux et coupé au-delà ; question lue sur 40 mots au plus ; correcteur de fautes indexé : un message piégé coûte moins de 5 ms de calcul (plus de 50 ms avant). Testé dans test/robustesse.test.ts. |
| Autre site qui utiliserait le chatbot | CORS : seuls les sites de ALLOWED_ORIGINS sont acceptés (403 sinon). |
| Données personnelles | Le chatbot ne demande rien. Les questions sont enregistrées anonymisées (e-mails, numéros, références de commande, adresses, noms, liens masqués — src/questions.ts), et le client en est informé sous la zone de saisie. Les questions non retenues sont effacées après 90 jours. La conversation reste dans l'onglet du client et disparaît à sa fermeture. Pensez à le mentionner dans la politique de confidentialité du site. |
| Fuite d'erreurs techniques | Toute erreur renvoie un message générique ; le détail reste dans les journaux Cloudflare. |
| En-têtes HTTP | API : nosniff, Content-Security-Policy: default-src 'none', no-store. Fichiers statiques (public/_headers) : CSP stricte, X-Frame-Options: DENY, noindex. |
À faire de votre côté : activer la double authentification sur les comptes GitHub et Cloudflare, et protéger la branche main (relecture obligatoire avant fusion).
Le site n'était pas accessible depuis l'environnement de développement. Les informations commerciales viennent donc d'extraits indexés du site et sont à confirmer. Elles sont centralisées dans src/savoir/coordonnees.ts et src/savoir/commande.ts :
02 47 53 00 26 et e-mail [email protected] (le seul numéro à jour, confirmé par l'équipe le 12/10/2026 ; l'ancien numéro ne doit figurer nulle part dans le chatbot).HORAIRES dans src/savoir/coordonnees.ts) : du lundi au jeudi 9h-12h et 14h-18h, le vendredi 9h-12h et 14h-16h, identiques sur la page Contact et le pied de page du site au 28/09/2026.src/savoir/liens.ts), relevées le 24/09/2026.Les faits de savoir-faire général (étapes de la mise en bouteille, du cidre, du vinaigre, conserves…) portent la source « Savoir-faire général ». Ils méritent une relecture par l'équipe Duhallé.
npm install
npm test # base, banc de questions, jeu inédit, Worker, robustesse
npm run typecheck # types du Worker et du widget
npm run build # compile une nouvelle version public/v/<version>.js et son empreinte (à commiter)
npm run dev # Worker en local sur http://localhost:8787 (page de démonstration)
Le widget compilé ne contient que de l'ASCII. Les pages Oxatis ne sont pas en UTF-8 : un navigateur qui lit le fichier avec l'encodage de la page (Windows-1252) abîme chaque accent, et un accent dans une expression régulière fait planter tout le script (« Range out of order in character class »), donc plus de bulle. esbuild échappe les accents des textes mais pas ceux des expressions régulières. npm run build refuse donc tout caractère non ASCII et dit où il se trouve ; dans src/widget/, écrivez \u0300 ou \u00e9, jamais le caractère. test/widget-ascii.test.mjs le vérifie aussi, en compilant le fichier lu en Windows-1252. Les en-têtes de public/_headers annoncent en plus charset=utf-8.
API :
POST /api/chat {"message": "Quel bouchon pour un vin de garde ?", "contexte": {…}}
GET /api/accueil message d'accueil et questions de départ
GET /api/sante {"ok": true, "concepts": 252, "faits": 704}
La réponse de /api/chat contient texte (gras, listes, paragraphes), liens, suggestions, contexte (à renvoyer avec la question suivante pour les relances) et trace.