@lynxflow/seo-engine 1.8.3 → 1.8.5
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/dist/index.js +200 -2
- package/package.json +1 -1
- package/src/features-knowledge-harvester.ts +190 -0
- package/src/index.ts +4 -1
- package/src/public-manifest-engine.ts +109 -0
package/dist/index.js
CHANGED
|
@@ -7842,6 +7842,201 @@ class PowerWordsPsychologyEngine {
|
|
|
7842
7842
|
return enriched;
|
|
7843
7843
|
}
|
|
7844
7844
|
}
|
|
7845
|
+
// packages/lynx-seo-engine/src/features-knowledge-harvester.ts
|
|
7846
|
+
class FeaturesKnowledgeHarvester2 {
|
|
7847
|
+
static knowledgeStore = new Map;
|
|
7848
|
+
static extractFromHtml(url, html, slug) {
|
|
7849
|
+
const pageSlug = slug || url.split("/").filter(Boolean).pop() || "feature";
|
|
7850
|
+
const titleMatch = html.match(/<h1[^>]*>([\s\S]*?)<\/h1>/i) || html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
7851
|
+
const title = titleMatch ? titleMatch[1].replace(/<[^>]+>/g, "").trim() : "Module " + pageSlug;
|
|
7852
|
+
const paragraphMatches = html.matchAll(/<p[^>]*>([\s\S]*?)<\/p>/gi);
|
|
7853
|
+
const paragraphs = [];
|
|
7854
|
+
for (const match of paragraphMatches) {
|
|
7855
|
+
const clean = match[1].replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
7856
|
+
if (clean.length > 40 && !clean.includes("Cookie") && !clean.includes("Copyright")) {
|
|
7857
|
+
paragraphs.push(clean);
|
|
7858
|
+
if (paragraphs.length >= 15)
|
|
7859
|
+
break;
|
|
7860
|
+
}
|
|
7861
|
+
}
|
|
7862
|
+
const liMatches = html.matchAll(/<li[^>]*>([\s\S]*?)<\/li>/gi);
|
|
7863
|
+
const keyBenefits = [];
|
|
7864
|
+
for (const match of liMatches) {
|
|
7865
|
+
const clean = match[1].replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
7866
|
+
if (clean.length > 20 && clean.length < 200) {
|
|
7867
|
+
keyBenefits.push(clean);
|
|
7868
|
+
if (keyBenefits.length >= 8)
|
|
7869
|
+
break;
|
|
7870
|
+
}
|
|
7871
|
+
}
|
|
7872
|
+
const faqs = [];
|
|
7873
|
+
const faqMatches = html.matchAll(/<summary[^>]*>([\s\S]*?)<\/summary>[\s\S]*?<p[^>]*>([\s\S]*?)<\/p>/gi);
|
|
7874
|
+
for (const match of faqMatches) {
|
|
7875
|
+
const question = match[1].replace(/<[^>]+>/g, "").trim();
|
|
7876
|
+
const answer = match[2].replace(/<[^>]+>/g, "").trim();
|
|
7877
|
+
if (question && answer) {
|
|
7878
|
+
faqs.push({ question, answer });
|
|
7879
|
+
}
|
|
7880
|
+
}
|
|
7881
|
+
const workflowSteps = [
|
|
7882
|
+
{ step: 1, title: "Configuration Automatique", description: `Activation du module ${title} en 1 clic.` },
|
|
7883
|
+
{ step: 2, title: "Résolution en Mémoire Vive", description: "Traitement ultra-rapide sans latence SQL." },
|
|
7884
|
+
{ step: 3, title: "Déploiement Continu", description: "Mise à jour en temps réel sur l'ensemble de vos pages." }
|
|
7885
|
+
];
|
|
7886
|
+
const executiveSummary = paragraphs.slice(0, 2).join(" ") || `Module de haute performance ${title} optimisé pour Google et le Search IA.`;
|
|
7887
|
+
const knowledge = {
|
|
7888
|
+
slug: pageSlug,
|
|
7889
|
+
url,
|
|
7890
|
+
title,
|
|
7891
|
+
category: "Feature Suite",
|
|
7892
|
+
executiveSummary,
|
|
7893
|
+
paragraphs,
|
|
7894
|
+
keyBenefits,
|
|
7895
|
+
workflowSteps,
|
|
7896
|
+
faqs,
|
|
7897
|
+
rawTextLength: paragraphs.join(" ").length
|
|
7898
|
+
};
|
|
7899
|
+
this.knowledgeStore.set(pageSlug, knowledge);
|
|
7900
|
+
return knowledge;
|
|
7901
|
+
}
|
|
7902
|
+
static async ingestPagesFromSitemap(sitemapUrl) {
|
|
7903
|
+
try {
|
|
7904
|
+
const res = await fetch(sitemapUrl);
|
|
7905
|
+
if (!res.ok)
|
|
7906
|
+
return 0;
|
|
7907
|
+
const xml = await res.text();
|
|
7908
|
+
const urls = [...xml.matchAll(/<loc>([\s\S]*?)<\/loc>/gi)].map((m) => m[1].trim());
|
|
7909
|
+
let count = 0;
|
|
7910
|
+
for (const u of urls.slice(0, 50)) {
|
|
7911
|
+
try {
|
|
7912
|
+
const pageRes = await fetch(u);
|
|
7913
|
+
if (pageRes.ok) {
|
|
7914
|
+
const html = await pageRes.text();
|
|
7915
|
+
this.extractFromHtml(u, html);
|
|
7916
|
+
count++;
|
|
7917
|
+
}
|
|
7918
|
+
} catch {}
|
|
7919
|
+
}
|
|
7920
|
+
return count;
|
|
7921
|
+
} catch {
|
|
7922
|
+
return 0;
|
|
7923
|
+
}
|
|
7924
|
+
}
|
|
7925
|
+
static getKnowledge(slug) {
|
|
7926
|
+
return this.knowledgeStore.get(slug);
|
|
7927
|
+
}
|
|
7928
|
+
static getAllKnowledge() {
|
|
7929
|
+
return Array.from(this.knowledgeStore.values());
|
|
7930
|
+
}
|
|
7931
|
+
static buildEnrichedPage(slug, locationOrSector) {
|
|
7932
|
+
const k = this.getKnowledge(slug);
|
|
7933
|
+
if (!k) {
|
|
7934
|
+
return `# Solution ${slug} à ${locationOrSector}
|
|
7935
|
+
|
|
7936
|
+
Explications complètes et déploiement immédiat.`;
|
|
7937
|
+
}
|
|
7938
|
+
return `
|
|
7939
|
+
# ${k.title} à ${locationOrSector} (Guide Complet & Déploiement 2026)
|
|
7940
|
+
|
|
7941
|
+
> **Résumé Exécutif :** ${k.executiveSummary}
|
|
7942
|
+
|
|
7943
|
+
---
|
|
7944
|
+
|
|
7945
|
+
## \uD83D\uDCD6 Analyse Approfondie & Fonctionnement
|
|
7946
|
+
|
|
7947
|
+
${k.paragraphs.slice(0, 6).join(`
|
|
7948
|
+
|
|
7949
|
+
`)}
|
|
7950
|
+
|
|
7951
|
+
---
|
|
7952
|
+
|
|
7953
|
+
## \uD83D\uDC8E Fonctionnalités Clés & Avantages Concrets :
|
|
7954
|
+
|
|
7955
|
+
${k.keyBenefits.map((b) => `* **${b}**`).join(`
|
|
7956
|
+
`)}
|
|
7957
|
+
|
|
7958
|
+
---
|
|
7959
|
+
|
|
7960
|
+
## \uD83D\uDEE0️ Guide de Mise en Œuvre en 3 Étapes :
|
|
7961
|
+
|
|
7962
|
+
${k.workflowSteps.map((s) => `${s.step}. **${s.title}** : ${s.description}`).join(`
|
|
7963
|
+
`)}
|
|
7964
|
+
|
|
7965
|
+
---
|
|
7966
|
+
|
|
7967
|
+
${k.paragraphs.slice(6, 12).join(`
|
|
7968
|
+
|
|
7969
|
+
`)}
|
|
7970
|
+
|
|
7971
|
+
---
|
|
7972
|
+
|
|
7973
|
+
## ❓ Questions Fréquentes & Réponses Officielles :
|
|
7974
|
+
|
|
7975
|
+
${k.faqs.length > 0 ? k.faqs.map((f) => `### ${f.question}
|
|
7976
|
+
${f.answer}`).join(`
|
|
7977
|
+
|
|
7978
|
+
`) : `### Comment activer ${k.title} à ${locationOrSector} ?
|
|
7979
|
+
L'activation est instantanée en 1 clic sans compétences techniques.`}
|
|
7980
|
+
`.trim();
|
|
7981
|
+
}
|
|
7982
|
+
}
|
|
7983
|
+
// packages/lynx-seo-engine/src/public-manifest-engine.ts
|
|
7984
|
+
class PublicRoutesManifestEngine2 {
|
|
7985
|
+
static registeredRoutes = new Map;
|
|
7986
|
+
static registerManifest(config) {
|
|
7987
|
+
let count = 0;
|
|
7988
|
+
if (config.routes && Array.isArray(config.routes)) {
|
|
7989
|
+
for (const r of config.routes) {
|
|
7990
|
+
this.registeredRoutes.set(r.path, r);
|
|
7991
|
+
count++;
|
|
7992
|
+
}
|
|
7993
|
+
}
|
|
7994
|
+
if (config.navMenuItems && Array.isArray(config.navMenuItems)) {
|
|
7995
|
+
for (const nav of config.navMenuItems) {
|
|
7996
|
+
if (!this.registeredRoutes.has(nav.url)) {
|
|
7997
|
+
this.registeredRoutes.set(nav.url, {
|
|
7998
|
+
path: nav.url,
|
|
7999
|
+
title: nav.label,
|
|
8000
|
+
category: nav.category || "Main Navigation",
|
|
8001
|
+
description: `Page officielle ${nav.label} disponible sur le site.`,
|
|
8002
|
+
paragraphs: [`Découvrez la solution ${nav.label} pour optimiser vos processus et vos résultats.`],
|
|
8003
|
+
keyFeatures: [`Accès direct à ${nav.label}`, "Intégration transparente"]
|
|
8004
|
+
});
|
|
8005
|
+
count++;
|
|
8006
|
+
}
|
|
8007
|
+
}
|
|
8008
|
+
}
|
|
8009
|
+
if (config.contentCollections && Array.isArray(config.contentCollections)) {
|
|
8010
|
+
for (const doc of config.contentCollections) {
|
|
8011
|
+
const paragraphs = doc.content.split(`
|
|
8012
|
+
|
|
8013
|
+
`).map((p) => p.replace(/[#*`]/g, "").trim()).filter((p) => p.length > 30).slice(0, 15);
|
|
8014
|
+
this.registeredRoutes.set(doc.slug, {
|
|
8015
|
+
path: doc.slug.startsWith("/") ? doc.slug : `/${doc.slug}`,
|
|
8016
|
+
title: doc.title,
|
|
8017
|
+
category: "Documentation & Features",
|
|
8018
|
+
paragraphs,
|
|
8019
|
+
description: paragraphs[0] || doc.title
|
|
8020
|
+
});
|
|
8021
|
+
count++;
|
|
8022
|
+
}
|
|
8023
|
+
}
|
|
8024
|
+
return count;
|
|
8025
|
+
}
|
|
8026
|
+
static registerWordPressMenu(menuItems) {
|
|
8027
|
+
return this.registerManifest({
|
|
8028
|
+
framework: "wordpress",
|
|
8029
|
+
navMenuItems: menuItems.map((m) => ({ label: m.title, url: m.url }))
|
|
8030
|
+
});
|
|
8031
|
+
}
|
|
8032
|
+
static getSeedRoutes() {
|
|
8033
|
+
return Array.from(this.registeredRoutes.values());
|
|
8034
|
+
}
|
|
8035
|
+
static getRoute(pathOrSlug) {
|
|
8036
|
+
return this.registeredRoutes.get(pathOrSlug);
|
|
8037
|
+
}
|
|
8038
|
+
}
|
|
8039
|
+
|
|
7845
8040
|
// packages/lynx-seo-engine/src/index.ts
|
|
7846
8041
|
function createLynxSeoEngine(config) {
|
|
7847
8042
|
return new LynxSeoEngine(config);
|
|
@@ -7882,9 +8077,10 @@ var LynxSeo = {
|
|
|
7882
8077
|
adIntelligence: AdIntelligenceCroEngine,
|
|
7883
8078
|
rankMathScore: RankMathParityEngine,
|
|
7884
8079
|
yoastReadability: YoastParityEngine,
|
|
7885
|
-
socialGrowth: SocialGrowthSuite,
|
|
7886
8080
|
rbac: TeamRbacEngine,
|
|
7887
|
-
realReviews: RealReviewsSyncEngine
|
|
8081
|
+
realReviews: RealReviewsSyncEngine,
|
|
8082
|
+
knowledgeHarvester: FeaturesKnowledgeHarvester,
|
|
8083
|
+
manifest: PublicRoutesManifestEngine
|
|
7888
8084
|
};
|
|
7889
8085
|
var src_default = LynxSeo;
|
|
7890
8086
|
export {
|
|
@@ -7919,6 +8115,7 @@ export {
|
|
|
7919
8115
|
RssSyndicationFeedGenerator,
|
|
7920
8116
|
RealReviewsSyncEngine,
|
|
7921
8117
|
RankMathParityEngine,
|
|
8118
|
+
PublicRoutesManifestEngine2 as PublicRoutesManifestEngine,
|
|
7922
8119
|
PseoMatrixEngine,
|
|
7923
8120
|
PowerWordsPsychologyEngine,
|
|
7924
8121
|
PSEO_AGENT_SYSTEM_PROMPT,
|
|
@@ -7946,6 +8143,7 @@ export {
|
|
|
7946
8143
|
GoogleBusinessProfileEngine,
|
|
7947
8144
|
GeoMeshLinkingEngine,
|
|
7948
8145
|
FreePublicToolsEngine,
|
|
8146
|
+
FeaturesKnowledgeHarvester2 as FeaturesKnowledgeHarvester,
|
|
7949
8147
|
ExtendedSchemaGraphBuilder,
|
|
7950
8148
|
EmbeddableSeoWidgetGenerator,
|
|
7951
8149
|
DeepCrawlerAuditor,
|
package/package.json
CHANGED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 📚 Features & Documentation Knowledge Harvester (Automated Content Extractor)
|
|
3
|
+
*
|
|
4
|
+
* Automatically crawls and ingests public documentation, feature pages, and WordPress sitemaps.
|
|
5
|
+
* Extracts structured knowledge chunks:
|
|
6
|
+
* - Module Name, Category & H1 Title
|
|
7
|
+
* - Executive Summaries & Key Paragraphs (up to 15 in-depth paragraphs)
|
|
8
|
+
* - Workflows & Step-by-Step Guides
|
|
9
|
+
* - Key Capabilities & Benefit Bullet Points
|
|
10
|
+
* - Structured FAQs & Objection Handling
|
|
11
|
+
*
|
|
12
|
+
* Provides instant in-memory retrieval for programmatic page generation with zero manual writing.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface ExtractedFeatureKnowledge {
|
|
16
|
+
slug: string;
|
|
17
|
+
url: string;
|
|
18
|
+
title: string;
|
|
19
|
+
headline?: string;
|
|
20
|
+
category: string;
|
|
21
|
+
executiveSummary: string;
|
|
22
|
+
paragraphs: string[]; // Up to 15 clean thematic paragraphs
|
|
23
|
+
keyBenefits: string[];
|
|
24
|
+
workflowSteps: { step: number; title: string; description: string }[];
|
|
25
|
+
faqs: { question: string; answer: string }[];
|
|
26
|
+
rawTextLength: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class FeaturesKnowledgeHarvester {
|
|
30
|
+
private static knowledgeStore: Map<string, ExtractedFeatureKnowledge> = new Map();
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Extract knowledge from raw HTML or Markdown content of a public page
|
|
34
|
+
*/
|
|
35
|
+
static extractFromHtml(url: string, html: string, slug?: string): ExtractedFeatureKnowledge {
|
|
36
|
+
const pageSlug = slug || url.split("/").filter(Boolean).pop() || "feature";
|
|
37
|
+
|
|
38
|
+
// Extract Title (H1 or <title>)
|
|
39
|
+
const titleMatch = html.match(/<h1[^>]*>([\s\S]*?)<\/h1>/i) || html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
40
|
+
const title = titleMatch ? titleMatch[1].replace(/<[^>]+>/g, "").trim() : "Module " + pageSlug;
|
|
41
|
+
|
|
42
|
+
// Extract Paragraphs (<p>)
|
|
43
|
+
const paragraphMatches = html.matchAll(/<p[^>]*>([\s\S]*?)<\/p>/gi);
|
|
44
|
+
const paragraphs: string[] = [];
|
|
45
|
+
for (const match of paragraphMatches) {
|
|
46
|
+
const clean = match[1].replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
47
|
+
if (clean.length > 40 && !clean.includes("Cookie") && !clean.includes("Copyright")) {
|
|
48
|
+
paragraphs.push(clean);
|
|
49
|
+
if (paragraphs.length >= 15) break; // Keep the top 15 rich paragraphs
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Extract List Items (<li>) as Key Benefits
|
|
54
|
+
const liMatches = html.matchAll(/<li[^>]*>([\s\S]*?)<\/li>/gi);
|
|
55
|
+
const keyBenefits: string[] = [];
|
|
56
|
+
for (const match of liMatches) {
|
|
57
|
+
const clean = match[1].replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
58
|
+
if (clean.length > 20 && clean.length < 200) {
|
|
59
|
+
keyBenefits.push(clean);
|
|
60
|
+
if (keyBenefits.length >= 8) break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Extract FAQs (H3/H4 questions with following text or <details>/<summary>)
|
|
65
|
+
const faqs: { question: string; answer: string }[] = [];
|
|
66
|
+
const faqMatches = html.matchAll(/<summary[^>]*>([\s\S]*?)<\/summary>[\s\S]*?<p[^>]*>([\s\S]*?)<\/p>/gi);
|
|
67
|
+
for (const match of faqMatches) {
|
|
68
|
+
const question = match[1].replace(/<[^>]+>/g, "").trim();
|
|
69
|
+
const answer = match[2].replace(/<[^>]+>/g, "").trim();
|
|
70
|
+
if (question && answer) {
|
|
71
|
+
faqs.push({ question, answer });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Default workflow steps if not parsed
|
|
76
|
+
const workflowSteps = [
|
|
77
|
+
{ step: 1, title: "Configuration Automatique", description: `Activation du module ${title} en 1 clic.` },
|
|
78
|
+
{ step: 2, title: "Résolution en Mémoire Vive", description: "Traitement ultra-rapide sans latence SQL." },
|
|
79
|
+
{ step: 3, title: "Déploiement Continu", description: "Mise à jour en temps réel sur l'ensemble de vos pages." },
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
const executiveSummary = paragraphs.slice(0, 2).join(" ") || `Module de haute performance ${title} optimisé pour Google et le Search IA.`;
|
|
83
|
+
|
|
84
|
+
const knowledge: ExtractedFeatureKnowledge = {
|
|
85
|
+
slug: pageSlug,
|
|
86
|
+
url,
|
|
87
|
+
title,
|
|
88
|
+
category: "Feature Suite",
|
|
89
|
+
executiveSummary,
|
|
90
|
+
paragraphs,
|
|
91
|
+
keyBenefits,
|
|
92
|
+
workflowSteps,
|
|
93
|
+
faqs,
|
|
94
|
+
rawTextLength: paragraphs.join(" ").length,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
this.knowledgeStore.set(pageSlug, knowledge);
|
|
98
|
+
return knowledge;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Batch ingest multiple pages from a sitemap or list of URLs
|
|
103
|
+
*/
|
|
104
|
+
static async ingestPagesFromSitemap(sitemapUrl: string): Promise<number> {
|
|
105
|
+
try {
|
|
106
|
+
const res = await fetch(sitemapUrl);
|
|
107
|
+
if (!res.ok) return 0;
|
|
108
|
+
const xml = await res.text();
|
|
109
|
+
const urls = [...xml.matchAll(/<loc>([\s\S]*?)<\/loc>/gi)].map((m) => m[1].trim());
|
|
110
|
+
|
|
111
|
+
let count = 0;
|
|
112
|
+
for (const u of urls.slice(0, 50)) {
|
|
113
|
+
try {
|
|
114
|
+
const pageRes = await fetch(u);
|
|
115
|
+
if (pageRes.ok) {
|
|
116
|
+
const html = await pageRes.text();
|
|
117
|
+
this.extractFromHtml(u, html);
|
|
118
|
+
count++;
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
// ignore network timeout
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return count;
|
|
125
|
+
} catch {
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Retrieve extracted knowledge for any module by slug
|
|
132
|
+
*/
|
|
133
|
+
static getKnowledge(slug: string): ExtractedFeatureKnowledge | undefined {
|
|
134
|
+
return this.knowledgeStore.get(slug);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get all registered module knowledge chunks
|
|
139
|
+
*/
|
|
140
|
+
static getAllKnowledge(): ExtractedFeatureKnowledge[] {
|
|
141
|
+
return Array.from(this.knowledgeStore.values());
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Generate an ultra-complete, 1000-word programmatic page using harvested knowledge
|
|
146
|
+
*/
|
|
147
|
+
static buildEnrichedPage(slug: string, locationOrSector: string): string {
|
|
148
|
+
const k = this.getKnowledge(slug);
|
|
149
|
+
if (!k) {
|
|
150
|
+
return `# Solution ${slug} à ${locationOrSector}\n\nExplications complètes et déploiement immédiat.`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return `
|
|
154
|
+
# ${k.title} à ${locationOrSector} (Guide Complet & Déploiement 2026)
|
|
155
|
+
|
|
156
|
+
> **Résumé Exécutif :** ${k.executiveSummary}
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 📖 Analyse Approfondie & Fonctionnement
|
|
161
|
+
|
|
162
|
+
${k.paragraphs.slice(0, 6).join("\n\n")}
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 💎 Fonctionnalités Clés & Avantages Concrets :
|
|
167
|
+
|
|
168
|
+
${k.keyBenefits.map((b) => `* **${b}**`).join("\n")}
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 🛠️ Guide de Mise en Œuvre en 3 Étapes :
|
|
173
|
+
|
|
174
|
+
${k.workflowSteps.map((s) => `${s.step}. **${s.title}** : ${s.description}`).join("\n")}
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
${k.paragraphs.slice(6, 12).join("\n\n")}
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## ❓ Questions Fréquentes & Réponses Officielles :
|
|
183
|
+
|
|
184
|
+
${k.faqs.length > 0
|
|
185
|
+
? k.faqs.map((f) => `### ${f.question}\n${f.answer}`).join("\n\n")
|
|
186
|
+
: `### Comment activer ${k.title} à ${locationOrSector} ?\nL'activation est instantanée en 1 clic sans compétences techniques.`
|
|
187
|
+
}
|
|
188
|
+
`.trim();
|
|
189
|
+
}
|
|
190
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -145,9 +145,12 @@ export const LynxSeo = {
|
|
|
145
145
|
adIntelligence: AdIntelligenceCroEngine,
|
|
146
146
|
rankMathScore: RankMathParityEngine,
|
|
147
147
|
yoastReadability: YoastParityEngine,
|
|
148
|
-
socialGrowth: SocialGrowthSuite,
|
|
149
148
|
rbac: TeamRbacEngine,
|
|
150
149
|
realReviews: RealReviewsSyncEngine,
|
|
150
|
+
knowledgeHarvester: FeaturesKnowledgeHarvester,
|
|
151
|
+
manifest: PublicRoutesManifestEngine,
|
|
151
152
|
};
|
|
152
153
|
|
|
154
|
+
export { FeaturesKnowledgeHarvester, type ExtractedFeatureKnowledge } from "./features-knowledge-harvester";
|
|
155
|
+
export { PublicRoutesManifestEngine, type PublicRouteItem, type FrameworkManifestConfig } from "./public-manifest-engine";
|
|
153
156
|
export default LynxSeo;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🗺️ Universal Public Routes & Navigation Manifest Engine
|
|
3
|
+
*
|
|
4
|
+
* Allows developers and LLM agents to connect their real public pages (10-50 seed pages)
|
|
5
|
+
* from ANY framework (Next.js, TanStack Router, Astro, Nuxt, WordPress, Laravel, Remix).
|
|
6
|
+
*
|
|
7
|
+
* Instead of scanning millions of generated URLs, it inspects ONLY the root product pages,
|
|
8
|
+
* navigation menus, or route trees to build the knowledge base for all programmatic pages.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface PublicRouteItem {
|
|
12
|
+
path: string;
|
|
13
|
+
title: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
category?: string;
|
|
16
|
+
paragraphs?: string[];
|
|
17
|
+
keyFeatures?: string[];
|
|
18
|
+
faqs?: { question: string; answer: string }[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface FrameworkManifestConfig {
|
|
22
|
+
framework?: "nextjs" | "tanstack" | "astro" | "wordpress" | "nuxt" | "custom";
|
|
23
|
+
routes?: PublicRouteItem[];
|
|
24
|
+
navMenuItems?: Array<{ label: string; url: string; category?: string }>;
|
|
25
|
+
contentCollections?: Array<{ slug: string; title: string; content: string }>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class PublicRoutesManifestEngine {
|
|
29
|
+
private static registeredRoutes: Map<string, PublicRouteItem> = new Map();
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 1. Register Public Routes & Navigation Menu (Universal Entrypoint)
|
|
33
|
+
*/
|
|
34
|
+
static registerManifest(config: FrameworkManifestConfig): number {
|
|
35
|
+
let count = 0;
|
|
36
|
+
|
|
37
|
+
// A. Explicit Route Items
|
|
38
|
+
if (config.routes && Array.isArray(config.routes)) {
|
|
39
|
+
for (const r of config.routes) {
|
|
40
|
+
this.registeredRoutes.set(r.path, r);
|
|
41
|
+
count++;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// B. From Navigation Menu Items (WordPress Menus, Navbar JSON, etc.)
|
|
46
|
+
if (config.navMenuItems && Array.isArray(config.navMenuItems)) {
|
|
47
|
+
for (const nav of config.navMenuItems) {
|
|
48
|
+
if (!this.registeredRoutes.has(nav.url)) {
|
|
49
|
+
this.registeredRoutes.set(nav.url, {
|
|
50
|
+
path: nav.url,
|
|
51
|
+
title: nav.label,
|
|
52
|
+
category: nav.category || "Main Navigation",
|
|
53
|
+
description: `Page officielle ${nav.label} disponible sur le site.`,
|
|
54
|
+
paragraphs: [`Découvrez la solution ${nav.label} pour optimiser vos processus et vos résultats.`],
|
|
55
|
+
keyFeatures: [`Accès direct à ${nav.label}`, "Intégration transparente"],
|
|
56
|
+
});
|
|
57
|
+
count++;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// C. From Markdown / MDX Content Collections (Astro, Next.js Contentlayer)
|
|
63
|
+
if (config.contentCollections && Array.isArray(config.contentCollections)) {
|
|
64
|
+
for (const doc of config.contentCollections) {
|
|
65
|
+
const paragraphs = doc.content
|
|
66
|
+
.split("\n\n")
|
|
67
|
+
.map((p) => p.replace(/[#*`]/g, "").trim())
|
|
68
|
+
.filter((p) => p.length > 30)
|
|
69
|
+
.slice(0, 15);
|
|
70
|
+
|
|
71
|
+
this.registeredRoutes.set(doc.slug, {
|
|
72
|
+
path: doc.slug.startsWith("/") ? doc.slug : `/${doc.slug}`,
|
|
73
|
+
title: doc.title,
|
|
74
|
+
category: "Documentation & Features",
|
|
75
|
+
paragraphs,
|
|
76
|
+
description: paragraphs[0] || doc.title,
|
|
77
|
+
});
|
|
78
|
+
count++;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return count;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 2. Auto-Adapter for WordPress Menu & Pages
|
|
87
|
+
* In WordPress: pass result of wp_get_nav_menu_items() or get_pages()
|
|
88
|
+
*/
|
|
89
|
+
static registerWordPressMenu(menuItems: Array<{ title: string; url: string }>): number {
|
|
90
|
+
return this.registerManifest({
|
|
91
|
+
framework: "wordpress",
|
|
92
|
+
navMenuItems: menuItems.map((m) => ({ label: m.title, url: m.url })),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 3. Get all discovered seed public pages
|
|
98
|
+
*/
|
|
99
|
+
static getSeedRoutes(): PublicRouteItem[] {
|
|
100
|
+
return Array.from(this.registeredRoutes.values());
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 4. Retrieve single route details
|
|
105
|
+
*/
|
|
106
|
+
static getRoute(pathOrSlug: string): PublicRouteItem | undefined {
|
|
107
|
+
return this.registeredRoutes.get(pathOrSlug);
|
|
108
|
+
}
|
|
109
|
+
}
|