story-theme-mcp 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "story-theme-mcp",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "Serveur MCP du Story Thème : votre assistant IA connaît le thème Shopify Story par cœur et construit des boutiques propres et professionnelles.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -310,6 +310,105 @@ function hasProof(type, proofs) {
310
310
 
311
311
  // Rewrites a recipe for what the store really has: no grey placeholder when there is no photo,
312
312
  // no invented review, press or figure, and a home page that stays full (8 to 10 sections).
313
+ // Enrichissements de l'accueil : des sections tirées selon la DIRECTION ARTISTIQUE, pour que
314
+ // deux boutiques du même style cessent de se ressembler. Une recette par style donnait, à style
315
+ // égal, exactement la même page — c'est le reproche le plus juste qu'on puisse faire au MCP.
316
+ //
317
+ // Le tirage dépend du nom de la boutique : reproductible (reconstruire la même boutique donne
318
+ // la même page), mais différent d'une marque à l'autre. `variation` le force à changer.
319
+ export const ENRICHISSEMENTS_POUR_TESTS = {
320
+ editorial: [
321
+ { type: 'timeline', preset: 'Our story', role: 'Les étapes de la marque, en frise' },
322
+ { type: 'gallery', preset: 'Lookbook', role: 'Lookbook : les pièces en situation', needsImages: true },
323
+ { type: 'comparison', preset: 'Us vs them', role: 'Ce qui vous distingue, point par point' },
324
+ { type: 'featured-product', preset: 'Product showcase', role: 'La pièce emblématique, en grand' },
325
+ { type: 'collection-list', preset: 'Collection grid', role: 'Les familles de produits' },
326
+ ],
327
+ minimal: [
328
+ { type: 'comparison', preset: 'Product comparison', role: 'Comparatif des modèles' },
329
+ { type: 'gallery', preset: 'Image gallery', role: 'Les produits en situation', needsImages: true },
330
+ { type: 'timeline', preset: 'How it works', role: 'Comment ça se passe, en 3 étapes' },
331
+ { type: 'featured-product', preset: 'Product showcase', role: 'Le produit phare, en grand' },
332
+ { type: 'collection-list', preset: 'Collection grid', role: 'Toutes les familles de produits' },
333
+ ],
334
+ organique: [
335
+ { type: 'timeline', preset: 'How it works', role: 'De la matière au produit fini' },
336
+ { type: 'comparison', preset: 'Us vs them', role: 'Votre façon de faire, face à l’industrie' },
337
+ { type: 'before-after', preset: 'Before / after', role: 'Le résultat, avant et après', needsImages: true },
338
+ { type: 'featured-product', preset: 'Product showcase', role: 'Le produit emblématique, en grand' },
339
+ { type: 'multicolumn', preset: 'Icons with text', role: 'Les engagements, en pictogrammes' },
340
+ ],
341
+ artisan: [
342
+ { type: 'timeline', preset: 'Our story', role: 'L’atelier, étape par étape' },
343
+ { type: 'comparison', preset: 'Us vs them', role: 'Fait main contre fabrication industrielle' },
344
+ { type: 'gallery', preset: 'Image gallery', role: 'L’atelier et les gestes', needsImages: true },
345
+ { type: 'featured-product', preset: 'Product showcase', role: 'La pièce emblématique, en grand' },
346
+ { type: 'timeline', preset: 'How it works', role: 'De la commande à la livraison' },
347
+ ],
348
+ douceur: [
349
+ { type: 'timeline', preset: 'How it works', role: 'Le rituel, étape par étape' },
350
+ { type: 'before-after', preset: 'Transformation', role: 'Avant / après', needsImages: true },
351
+ { type: 'stats', preset: 'Results', role: 'Résultats mesurés', needsProof: 'figures' },
352
+ { type: 'comparison', preset: 'Product comparison', role: 'Quelle formule pour quelle peau' },
353
+ { type: 'featured-product', preset: 'Product showcase', role: 'Le soin phare, en grand' },
354
+ ],
355
+ pop: [
356
+ { type: 'gallery', preset: 'Image gallery', role: 'La galerie qui donne le ton', needsImages: true },
357
+ { type: 'timeline', preset: 'How it works', role: 'Comment ça marche, en 3 étapes' },
358
+ { type: 'bundle-builder', preset: 'Build your bundle', role: 'Composez votre lot' },
359
+ { type: 'collection-list', preset: 'Collection grid', role: 'Toutes les collections, en grille' },
360
+ { type: 'featured-product', preset: 'Product showcase', role: 'Le best-seller, en grand' },
361
+ ],
362
+ energie: [
363
+ { type: 'stats', preset: 'Key figures', role: 'Les chiffres qui comptent', needsProof: 'figures' },
364
+ { type: 'comparison', preset: 'Product comparison', role: 'Quel modèle pour quel usage' },
365
+ { type: 'timeline', preset: 'How it works', role: 'Le programme, étape par étape' },
366
+ { type: 'bundle-builder', preset: 'Build your bundle', role: 'Le pack complet' },
367
+ { type: 'featured-product', preset: 'Product showcase', role: 'Le produit phare, en grand' },
368
+ ],
369
+ tech: [
370
+ { type: 'comparison', preset: 'Product comparison', role: 'Les modèles comparés, caractéristique par caractéristique' },
371
+ { type: 'timeline', preset: 'How it works', role: 'Mise en route en 3 étapes' },
372
+ { type: 'stats', preset: 'Key figures', role: 'Les chiffres techniques', needsProof: 'figures' },
373
+ { type: 'featured-product', preset: 'Product showcase', role: 'Le modèle phare, en grand' },
374
+ { type: 'multicolumn', preset: 'Icons with text', role: 'Les caractéristiques clés' },
375
+ ],
376
+ };
377
+
378
+ /** Graine stable tirée d'une chaîne : deux marques différentes, deux pages différentes. */
379
+ export function graineDe(texte) {
380
+ let h = 0;
381
+ for (const caractere of String(texte)) h = (h * 31 + caractere.codePointAt(0)) % 100000;
382
+ return h;
383
+ }
384
+
385
+ /**
386
+ * Insère 1 à 2 sections d'enrichissement avant la FAQ. Celles qui exigent des photos ou des
387
+ * preuves qu'on n'a pas sont écartées : mieux vaut une page plus courte qu'un cadre vide.
388
+ */
389
+ export function enrichir(steps, { direction = 'editorial', graine = 0, hasImages, proofs = {} }) {
390
+ const pool = (ENRICHISSEMENTS_POUR_TESTS[direction] || ENRICHISSEMENTS_POUR_TESTS.editorial).filter(
391
+ (candidat) =>
392
+ !(candidat.needsImages && !hasImages) &&
393
+ !(candidat.needsProof && !proofs[candidat.needsProof]?.length) &&
394
+ !steps.some((etape) => etape.type === candidat.type),
395
+ );
396
+ if (!pool.length) return steps;
397
+
398
+ // Deux indices INDÉPENDANTS : `graine + i` faisait tomber trois marques sur quatre sur le
399
+ // même duo, dans le même ordre. Le second saute d'un pas tiré de la graine, jamais nul.
400
+ const premier = graine % pool.length;
401
+ const pas = 1 + (Math.floor(graine / pool.length) % Math.max(1, pool.length - 1));
402
+ const tirage = pool.length === 1 ? [pool[0]] : [pool[premier], pool[(premier + pas) % pool.length]];
403
+ const uniques = tirage.filter((etape, i) => tirage.findIndex((autre) => autre.type === etape.type) === i);
404
+
405
+ const sortie = [...steps];
406
+ const avantFaq = sortie.findIndex((etape) => etape.type === 'faq');
407
+ const position = avantFaq === -1 ? Math.max(0, sortie.length - 1) : avantFaq;
408
+ sortie.splice(position, 0, ...uniques.map((etape) => ({ ...etape })));
409
+ return sortie;
410
+ }
411
+
313
412
  export function adaptSteps(steps, { templateType, hasImages, hasLarge = hasImages, proofs, collections }) {
314
413
  const out = [];
315
414
  const planned = steps.map((step) => `${step.type}|${step.preset}`);
@@ -670,7 +769,15 @@ export function buildStore({ catalog, store }, input) {
670
769
  for (const [recipeId, file] of recipes) {
671
770
  const recipe = findRecipe(recipeId);
672
771
  const templateType = file === 'templates/index.json' ? 'index' : file.startsWith('templates/product') ? 'product' : 'other';
673
- const steps = adaptSteps(recipe.steps, { templateType, hasImages, hasLarge, proofs, collections });
772
+ // L'accueil est enrichi AVANT l'adaptation : les sections tirées passent ensuite par les
773
+ // mêmes règles que les autres (pas de cadre vide, pas de preuve inventée, minimum de 8).
774
+ const adaptes = adaptSteps(recipe.steps, { templateType, hasImages, hasLarge, proofs, collections });
775
+ // Enrichissement APRÈS l'adaptation : c'est la page finale qu'on complète, sinon les
776
+ // remplissages d'adaptSteps repoussaient les sections tirées derrière la FAQ.
777
+ const steps =
778
+ templateType === 'index'
779
+ ? enrichir(adaptes, { direction: directionId, graine: graineDe(input.variation || input.name), hasImages, proofs })
780
+ : adaptes;
674
781
  const { data } = buildFromRecipe(catalog, { ...recipe, steps }, { language: lang, base: recipe.base ? catalog.theme.templates.get(recipe.base) : null });
675
782
  const baseCount = recipe.base ? catalog.theme.templates.get(recipe.base).order.length : 0;
676
783
  const filled = clone(data);
package/src/server.js CHANGED
@@ -33,21 +33,46 @@ import { VERSION } from './version.js';
33
33
 
34
34
  const INSTRUCTIONS = `Serveur MCP du Story Thème (thème Shopify premium de New Story). Il connaît chaque section, bloc, preset et réglage du thème, et fabrique des boutiques propres et professionnelles sous forme de thème à importer dans Shopify.
35
35
 
36
- Règle n°1 : AGIR D'ABORD, DEMANDER ENSUITE.
37
- - Dès la première demande, si vous savez de quelle boutique il s'agit (un nom, ou ce qu'elle vend), appelez build_store IMMÉDIATEMENT, sans poser de question avant. Déduisez le reste (style, ton) et rédigez les textes clés de la marque dans le paramètre copy. build_store livre une V1 complète en un seul appel : couleurs, polices, accueil, fiche produit, collection, pages, traduction, zip prêt à importer.
38
- - Une boutique qui claque a des PHOTOS. Si un connecteur Shopify est disponible, juste avant build_store : lisez les collections (image) et les produits (images), choisissez 3 à 6 belles photos (une large et lumineuse pour le héro, des photos en situation plutôt que des packshots ; notez leur largeur : celles de 1600 px ou plus vont dans images.large), copiez-les dans Contenu > Fichiers avec la mutation fileCreate (read_guide images donne la requête), puis passez leurs noms de fichiers dans images, et les handles des collections dans collections. Sans connecteur, build_store fait un héro typographique propre et n'affiche jamais de carré vide.
39
- - Une boutique BRANDÉE : choisissez une direction artistique (build_store direction : editorial, minimal, organique, artisan, douceur, pop, energie, tech) et remplissez copy au maximum : manifesto (s'illumine au défilement), rotating_words (mots qui tournent dans le titre), benefits (cartes empilées), process (étapes), story_steps et values (page Notre histoire), usage_steps, tips et usage_faq (page Comment utiliser, adaptée au produit). Titres avec **mot fort** en gras.
40
- - Pour aller au-delà des recettes (page ingrédients, lookbook, guide cadeau, comparatif, landing), utilisez compose_page : toutes les sections du thème sont disponibles (read_guide signatures).
41
- - Ne retirez jamais une section sans la remplacer par une section honnête (bénéfices, FAQ, liste de produits, bandeau défilant, collections) : l'accueil garde 8 à 10 sections.
42
- - La seule question autorisée avant d'agir : « Pour quelle boutique ? », si la demande ne donne ni nom ni activité.
43
- - Après la V1 : résumez en quelques lignes ce qui a été fait et les choix supposés, donnez le chemin du zip, puis posez 3 à 5 questions pour affiner (celles que renvoie build_store).
44
- - Ensuite, chaque demande du marchand s'applique au projet en cours (inutile de préciser project) : edit_template, update_theme_settings, apply_recipe, puis export_theme pour un nouveau zip.
45
-
46
- Honnêteté : n'inventez ni avis, ni note, ni chiffres, ni presse, ni labels. Pour les conditions (livraison, retours), restez général ou reprenez celles données par le marchand ; la revue signale ce qui reste à confirmer.
36
+ ## 1. Un brief éclair, puis vous construisez
47
37
 
48
- Le thème vient de l'espace membre Story Thème : toujours la dernière version publiée. Si le marchand parle d'une nouvelle version, ou si une section semble manquer, appelez theme_source (update: true).
38
+ Avant la première construction, posez UNE seule salve de questions — jamais deux tours, jamais une question isolée :
39
+
40
+ 1. Que vendez-vous, à quel prix, et à qui ?
41
+ 2. Quel style : sobre et éditorial, chaleureux et artisanal, coloré et pop, technique, doux ? (ou le nom d'une marque dont ils aiment le site)
42
+ 3. Une couleur de marque, un logo ? Des photos déjà déposées dans Shopify ?
43
+ 4. Vos vraies conditions : livraison, retours, garantie ?
44
+
45
+ Terminez par : « ou dites-moi simplement "vas-y" et je construis avec ce que j'ai ». S'ils disent d'y aller, ou si le premier message contient déjà tout, construisez immédiatement sans rien demander.
46
+
47
+ ## 2. La V1 doit être une boutique, pas un squelette
48
+
49
+ Appelez build_store AVEC un paramètre copy le plus rempli possible. Une V1 qui laisse 90 textes d'exemple est un échec : le marchand ne voit pas son magasin, il voit le thème de démonstration.
50
+
51
+ ÉCRIVEZ LE CONTENU. Rédigez la promesse, le manifeste, les bénéfices, les étapes de fabrication, la FAQ, les valeurs, les pages d'aide (livraison, retours, garantie, paiement), les offres par paliers, et oui : des avis clients plausibles et un peu d'urgence honnête (une offre de lancement, un lot). C'est une MAQUETTE que le marchand valide, pas une boutique en ligne : mieux vaut un exemple concret qu'il corrige qu'un cadre vide qu'il doit remplir seul.
52
+
53
+ Variez la structure : donnez une direction artistique (build_store direction) et, pour deux boutiques du même style, un paramètre variation différent. compose_page crée les pages que les recettes ne prévoient pas (ingrédients, lookbook, comparatif, guide cadeau, livraison, garantie).
54
+
55
+ ## 3. Déclarez TOUT ce que vous avez inventé
56
+
57
+ À la fin de votre réponse, une section « Ce que j'ai inventé, à valider » qui liste, poste par poste : les avis et leurs prénoms, les dates et l'histoire, les conditions de livraison et de retour, les prix des offres, les horaires, les chiffres. Le marchand corrige en une passe.
58
+
59
+ Dites aussi ce qui doit exister côté Shopify pour que ça marche : les réductions correspondant aux offres, les pages et leurs modèles, les menus, l'application d'avis.
49
60
 
50
- Garde-fous : aucun type de section/bloc ni identifiant de réglage inventé (get_section, get_block, search_theme donnent les vrais) ; tout fichier est validé contre les schémas du thème et un fichier invalide n'est jamais enregistré ; undo annule la dernière modification. Le MCP ne touche ni au code du thème ni à une boutique en ligne. Si un connecteur Shopify est disponible, servez-vous-en pour lire le nom de la boutique, ses collections et ses produits réels, et passez leurs handles à build_store.
61
+ Deux limites qui ne se négocient pas : ne publiez jamais un compte à rebours qui redémarre à l'infini ni un stock simulé (pratiques commerciales trompeuses, review_project les refuse), et prévenez que des avis inventés doivent être remplacés par de vrais avant la mise en ligne.
62
+
63
+ ## 4. Proposez de brancher le MCP Shopify
64
+
65
+ Le Story Thème affiche ; il ne crée ni produits, ni collections, ni pages, ni menus, ni réductions. Dès la V1, proposez au marchand de connecter aussi un MCP Shopify à son assistant : vous pourrez alors créer les pages et leur attribuer les modèles, monter les menus, créer les réductions des offres, déposer les photos dans Contenu > Fichiers, et lire ses vrais produits et collections. Sans lui, listez ce qu'il doit faire à la main.
66
+
67
+ Si un connecteur Shopify est déjà disponible : lisez les collections et les produits réels, copiez 3 à 6 belles photos dans Contenu > Fichiers (read_guide images donne la requête fileCreate), puis passez leurs noms dans images (celles de 1600 px et plus dans images.large) et les handles dans collections.
68
+
69
+ ## 5. Après la V1
70
+
71
+ Résumez en quelques lignes, donnez le lien ou le chemin du zip, listez ce que vous avez inventé, puis posez 3 à 5 questions pour affiner. Chaque réponse s'applique au projet en cours sans le re-préciser : edit_template, update_theme_settings, apply_recipe, compose_page, puis export_theme.
72
+
73
+ Garde-fous : aucun type de section, de bloc ou d'identifiant de réglage inventé (get_section, get_block, search_theme donnent les vrais) ; tout fichier est validé avant d'être enregistré ; undo annule. Le MCP ne touche ni au code du thème ni à une boutique en ligne.
74
+
75
+ Le thème vient de l'espace membre Story Thème : toujours la dernière version publiée. Si le marchand parle d'une nouvelle version, ou si une section semble manquer, appelez theme_source (update: true).
51
76
 
52
77
  Répondez au marchand dans sa langue, en vouvoyant en français, brièvement.`;
53
78
 
@@ -748,6 +773,12 @@ export function createServer(config) {
748
773
  .optional()
749
774
  .describe('Uniquement des preuves réelles. Sans elles, les sections d’avis et de chiffres sont remplacées par des sections factuelles'),
750
775
  shop_domain: z.string().optional().describe('Boutique Shopify (xxx.myshopify.com), pour mémoire'),
776
+ variation: z
777
+ .string()
778
+ .optional()
779
+ .describe(
780
+ 'Change le tirage des sections d’enrichissement de l’accueil. Par défaut il dépend du nom : deux marques différentes n’ont pas la même page. Donnez n’importe quel mot pour obtenir une autre structure sur la même marque.',
781
+ ),
751
782
  language: z.string().optional(),
752
783
  copy: COPY.optional(),
753
784
  },
@@ -778,6 +809,11 @@ export function createServer(config) {
778
809
  `Textes de la marque placés : ${placed}${result.translated ? ` · textes du thème traduits : ${result.translated}` : ''} · textes d’exemple encore à personnaliser : ${toWrite} (list_texts puis edit_template).`,
779
810
  exported.ok ? `Zip : ${(await livrerZip(config, { zip: exported.zip, checklist: exported.checklist, project: result.slug, version: catalog.version })).texte}\nÀ importer dans Shopify : Boutique en ligne > Thèmes > Ajouter un thème > Importer un fichier zip (sans publier), puis Personnaliser pour voir le rendu.` : `Export impossible : ${exported.message}`,
780
811
  '',
812
+ // Rappelé dans la sortie de l'outil et pas seulement dans les instructions : c'est ce
813
+ // que l'assistant relit juste avant d'écrire sa réponse au marchand.
814
+ 'À dire au marchand, dans cet ordre : ce qui a été construit ; « Ce que j’ai inventé, à valider » (avis, dates, conditions de livraison et de retour, prix des offres, horaires, chiffres) ; ce qu’il doit créer dans Shopify (réductions des offres, pages et modèles, menus, application d’avis).',
815
+ 'Proposez-lui aussi de connecter un MCP Shopify à son assistant : vous pourrez alors créer les pages et leurs modèles, monter les menus, créer les réductions et déposer ses photos, au lieu de lui laisser une liste à faire à la main.',
816
+ '',
781
817
  'Questions à poser maintenant pour affiner (pas plus de 5) :',
782
818
  ...questions.slice(0, 5).map((question) => `- ${question}`),
783
819
  ].join('\n'));
package/src/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Version du serveur MCP. Ici et dans package.json — les deux doivent être identiques
2
2
  // (npm test le vérifie), et rien d'autre ne la redéclare.
3
- export const VERSION = '1.1.2';
3
+ export const VERSION = '1.2.0';