@lynxflow/seo-engine 1.4.0 → 1.5.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/README.md +84 -339
- package/TUTORIEL_INTEGRATION_SITE.md +283 -0
- package/connectors/wordpress/lynxseo-connector.php +5 -5
- package/connectors/wordpress/lynxseo-connector.zip +0 -0
- package/dist/ad-intelligence-cro.d.ts +58 -0
- package/dist/ai-bots-log-analyzer.d.ts +36 -0
- package/dist/analytics-client.d.ts +48 -11
- package/dist/auth-key.d.ts +9 -0
- package/dist/brand-dna-calendar.d.ts +35 -0
- package/dist/copy-frameworks-master.d.ts +56 -0
- package/dist/cro-copywriting-engine.d.ts +46 -0
- package/dist/crosslink-scorer.d.ts +33 -0
- package/dist/embeddable-seo-widget.d.ts +12 -0
- package/dist/engine.d.ts +32 -1
- package/dist/engine.test.d.ts +1 -0
- package/dist/extended-schemas.d.ts +106 -0
- package/dist/geo-mesh-linking.d.ts +33 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.js +4164 -130
- package/dist/index.mjs +4092 -100
- package/dist/instant-matrix-search.d.ts +19 -0
- package/dist/isr-cache-manager.d.ts +35 -0
- package/dist/knowledge-graph-linker.d.ts +29 -0
- package/dist/legal-disclaimers.d.ts +46 -0
- package/dist/llm-content-cleaner.d.ts +16 -0
- package/dist/llm-prompt.d.ts +13 -0
- package/dist/master-marketing-engine.d.ts +114 -0
- package/dist/matrix-engine.d.ts +201 -0
- package/dist/mcp-seo-server.d.ts +21 -0
- package/dist/ngram-density-analyzer.d.ts +35 -0
- package/dist/rank-math-parity.d.ts +58 -0
- package/dist/real-reviews-sync.d.ts +73 -0
- package/dist/rss-syndication-feed.d.ts +25 -0
- package/dist/schema-builder.d.ts +6 -0
- package/dist/seo-opportunities-decay.d.ts +54 -0
- package/dist/serp-history-alerts.d.ts +27 -0
- package/dist/slug-engine.d.ts +32 -0
- package/dist/social-ads-seo.d.ts +77 -0
- package/dist/social-growth-suite.d.ts +125 -0
- package/dist/social-trend-seo.d.ts +38 -0
- package/dist/social-video-seo.d.ts +62 -0
- package/dist/team-rbac.d.ts +17 -0
- package/dist/technical-rules-auditor.d.ts +50 -0
- package/dist/types.d.ts +4 -44
- package/dist/yoast-parity.d.ts +45 -0
- package/package.json +8 -5
- package/src/ad-intelligence-cro.ts +140 -0
- package/src/ai-bots-log-analyzer.ts +127 -0
- package/src/analytics-client.ts +222 -48
- package/src/auth-key.ts +60 -1
- package/src/brand-dna-calendar.ts +120 -0
- package/src/copy-frameworks-master.ts +89 -0
- package/src/cro-copywriting-engine.ts +105 -0
- package/src/crosslink-scorer.ts +101 -0
- package/src/embeddable-seo-widget.ts +57 -0
- package/src/engine.test.ts +136 -0
- package/src/engine.ts +229 -28
- package/src/extended-schemas.ts +279 -0
- package/src/geo-mesh-linking.ts +93 -0
- package/src/index.ts +89 -0
- package/src/instant-matrix-search.ts +48 -0
- package/src/isr-cache-manager.ts +78 -0
- package/src/knowledge-graph-linker.ts +81 -0
- package/src/legal-disclaimers.ts +407 -0
- package/src/llm-content-cleaner.ts +64 -0
- package/src/llm-prompt.ts +70 -0
- package/src/master-marketing-engine.ts +314 -0
- package/src/matrix-engine.ts +818 -0
- package/src/mcp-seo-server.ts +108 -0
- package/src/ngram-density-analyzer.ts +108 -0
- package/src/rank-math-parity.ts +235 -0
- package/src/real-reviews-sync.ts +207 -0
- package/src/rss-syndication-feed.ts +56 -0
- package/src/schema-builder.ts +71 -24
- package/src/seo-opportunities-decay.ts +154 -0
- package/src/serp-history-alerts.ts +75 -0
- package/src/slug-engine.ts +183 -0
- package/src/social-ads-seo.ts +211 -0
- package/src/social-growth-suite.ts +310 -0
- package/src/social-trend-seo.ts +103 -0
- package/src/social-video-seo.ts +100 -0
- package/src/team-rbac.ts +61 -0
- package/src/technical-rules-auditor.ts +171 -0
- package/src/types.ts +22 -47
- package/src/yoast-parity.ts +161 -0
- package/tsconfig.json +2 -1
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extended Universal Schema.org Rich Graph Builder
|
|
3
|
+
* Supports 50+ Schema.org official types for Google Search Rich Results:
|
|
4
|
+
* - SoftwareApplication, WebApplication, MobileApplication
|
|
5
|
+
* - 35+ LocalBusiness Subtypes (Plumber, HVACBusiness, Electrician, Dentist, LegalService, AccountingService, etc.)
|
|
6
|
+
* - Organization with Entity SEO sameAs links (Wikidata, Crunchbase, LinkedIn)
|
|
7
|
+
* - Product, Offer, MerchantReturnPolicy
|
|
8
|
+
* - Article, TechArticle, NewsArticle
|
|
9
|
+
* - HowTo, HowToStep
|
|
10
|
+
* - FAQPage
|
|
11
|
+
* - BreadcrumbList
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export const SCHEMA_LOCAL_BUSINESS_MAP: Record<string, string> = {
|
|
15
|
+
// Trades & Construction
|
|
16
|
+
plumber: "Plumber",
|
|
17
|
+
plumbing: "Plumber",
|
|
18
|
+
hvac: "HVACBusiness",
|
|
19
|
+
electrician: "Electrician",
|
|
20
|
+
locksmith: "Locksmith",
|
|
21
|
+
roofing: "RoofingContractor",
|
|
22
|
+
painting: "HousePainter",
|
|
23
|
+
painter: "HousePainter",
|
|
24
|
+
carpenter: "HomeAndConstructionBusiness",
|
|
25
|
+
masonry: "GeneralContractor",
|
|
26
|
+
contractor: "GeneralContractor",
|
|
27
|
+
landscaping: "LandscapeContractor",
|
|
28
|
+
gardener: "LandscapeContractor",
|
|
29
|
+
pestcontrol: "PestControlService",
|
|
30
|
+
cleaning: "CleaningService",
|
|
31
|
+
towing: "TowingService",
|
|
32
|
+
|
|
33
|
+
// Healthcare & Wellness
|
|
34
|
+
dentist: "Dentist",
|
|
35
|
+
doctor: "MedicalBusiness",
|
|
36
|
+
physician: "MedicalBusiness",
|
|
37
|
+
medical: "MedicalBusiness",
|
|
38
|
+
psychologist: "MedicalBusiness",
|
|
39
|
+
osteopath: "MedicalBusiness",
|
|
40
|
+
physiotherapist: "MedicalBusiness",
|
|
41
|
+
veterinarian: "VeterinaryCare",
|
|
42
|
+
pharmacy: "Pharmacy",
|
|
43
|
+
dayspa: "DaySpa",
|
|
44
|
+
spa: "DaySpa",
|
|
45
|
+
hairsalon: "HairSalon",
|
|
46
|
+
barber: "BarberShop",
|
|
47
|
+
beautysalon: "BeautySalon",
|
|
48
|
+
healthclub: "HealthClub",
|
|
49
|
+
gym: "HealthClub",
|
|
50
|
+
|
|
51
|
+
// Professional Services
|
|
52
|
+
lawyer: "LegalService",
|
|
53
|
+
attorney: "LegalService",
|
|
54
|
+
legalservice: "LegalService",
|
|
55
|
+
accountant: "AccountingService",
|
|
56
|
+
accounting: "AccountingService",
|
|
57
|
+
insurance: "InsuranceAgency",
|
|
58
|
+
realestate: "RealEstateAgent",
|
|
59
|
+
realtor: "RealEstateAgent",
|
|
60
|
+
notary: "Notary",
|
|
61
|
+
moving: "MovingCompany",
|
|
62
|
+
education: "EducationalOrganization",
|
|
63
|
+
school: "EducationalOrganization",
|
|
64
|
+
|
|
65
|
+
// Automotive & Hospitality
|
|
66
|
+
autorepair: "AutoRepair",
|
|
67
|
+
mechanic: "AutoRepair",
|
|
68
|
+
carrental: "AutoRental",
|
|
69
|
+
restaurant: "Restaurant",
|
|
70
|
+
hotel: "Hotel",
|
|
71
|
+
store: "Store",
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export interface SchemaOrgOrganizationOpts {
|
|
75
|
+
name: string;
|
|
76
|
+
url: string;
|
|
77
|
+
logo?: string;
|
|
78
|
+
sameAs?: string[];
|
|
79
|
+
contactPoint?: {
|
|
80
|
+
telephone: string;
|
|
81
|
+
contactType: string;
|
|
82
|
+
availableLanguage: string[];
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface SchemaOrgLocalBusinessOpts {
|
|
87
|
+
name: string;
|
|
88
|
+
url: string;
|
|
89
|
+
schemaType?: string;
|
|
90
|
+
telephone?: string;
|
|
91
|
+
email?: string;
|
|
92
|
+
streetAddress?: string;
|
|
93
|
+
city: string;
|
|
94
|
+
region?: string;
|
|
95
|
+
postalCode?: string;
|
|
96
|
+
country: string;
|
|
97
|
+
latitude?: number;
|
|
98
|
+
longitude?: number;
|
|
99
|
+
openingHours?: string[];
|
|
100
|
+
priceRange?: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface SchemaOrgProductOpts {
|
|
104
|
+
name: string;
|
|
105
|
+
description: string;
|
|
106
|
+
url: string;
|
|
107
|
+
brandName: string;
|
|
108
|
+
price: number;
|
|
109
|
+
currency: string;
|
|
110
|
+
image?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface SchemaOrgArticleOpts {
|
|
114
|
+
headline: string;
|
|
115
|
+
description: string;
|
|
116
|
+
url: string;
|
|
117
|
+
datePublished?: string;
|
|
118
|
+
dateModified?: string;
|
|
119
|
+
authorName: string;
|
|
120
|
+
publisherName: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export class ExtendedSchemaGraphBuilder {
|
|
124
|
+
/**
|
|
125
|
+
* Resolves the most accurate Schema.org type for a business sector or keyword.
|
|
126
|
+
*/
|
|
127
|
+
static resolveBusinessType(industryOrSectorSlug: string): string {
|
|
128
|
+
const clean = industryOrSectorSlug.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
129
|
+
return SCHEMA_LOCAL_BUSINESS_MAP[clean] || "LocalBusiness";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 1. Organization with Entity SEO sameAs links
|
|
134
|
+
*/
|
|
135
|
+
static buildOrganization(opts: SchemaOrgOrganizationOpts): Record<string, unknown> {
|
|
136
|
+
return {
|
|
137
|
+
"@context": "https://schema.org",
|
|
138
|
+
"@type": "Organization",
|
|
139
|
+
name: opts.name,
|
|
140
|
+
url: opts.url,
|
|
141
|
+
logo: opts.logo,
|
|
142
|
+
sameAs: opts.sameAs || [],
|
|
143
|
+
contactPoint: opts.contactPoint
|
|
144
|
+
? {
|
|
145
|
+
"@type": "ContactPoint",
|
|
146
|
+
telephone: opts.contactPoint.telephone,
|
|
147
|
+
contactType: opts.contactPoint.contactType,
|
|
148
|
+
availableLanguage: opts.contactPoint.availableLanguage,
|
|
149
|
+
}
|
|
150
|
+
: undefined,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 2. LocalBusiness with GeoCoordinates & Address
|
|
156
|
+
*/
|
|
157
|
+
static buildLocalBusiness(opts: SchemaOrgLocalBusinessOpts): Record<string, unknown> {
|
|
158
|
+
const businessType = opts.schemaType || this.resolveBusinessType(opts.name);
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
"@context": "https://schema.org",
|
|
162
|
+
"@type": businessType,
|
|
163
|
+
name: opts.name,
|
|
164
|
+
url: opts.url,
|
|
165
|
+
telephone: opts.telephone,
|
|
166
|
+
email: opts.email,
|
|
167
|
+
priceRange: opts.priceRange || "$$",
|
|
168
|
+
address: {
|
|
169
|
+
"@type": "PostalAddress",
|
|
170
|
+
streetAddress: opts.streetAddress,
|
|
171
|
+
addressLocality: opts.city,
|
|
172
|
+
addressRegion: opts.region,
|
|
173
|
+
postalCode: opts.postalCode,
|
|
174
|
+
addressCountry: opts.country,
|
|
175
|
+
},
|
|
176
|
+
geo:
|
|
177
|
+
opts.latitude && opts.longitude
|
|
178
|
+
? {
|
|
179
|
+
"@type": "GeoCoordinates",
|
|
180
|
+
latitude: opts.latitude,
|
|
181
|
+
longitude: opts.longitude,
|
|
182
|
+
}
|
|
183
|
+
: undefined,
|
|
184
|
+
openingHoursSpecification: opts.openingHours
|
|
185
|
+
? opts.openingHours.map((hours) => ({
|
|
186
|
+
"@type": "OpeningHoursSpecification",
|
|
187
|
+
dayOfWeek: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
|
|
188
|
+
opens: hours.split("-")[0] || "09:00",
|
|
189
|
+
closes: hours.split("-")[1] || "18:00",
|
|
190
|
+
}))
|
|
191
|
+
: undefined,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 3. SoftwareApplication & WebApplication
|
|
197
|
+
*/
|
|
198
|
+
static buildSoftwareApplication(opts: {
|
|
199
|
+
name: string;
|
|
200
|
+
description: string;
|
|
201
|
+
url: string;
|
|
202
|
+
priceMonthly?: number;
|
|
203
|
+
currency?: string;
|
|
204
|
+
operatingSystem?: string;
|
|
205
|
+
category?: string;
|
|
206
|
+
}): Record<string, unknown> {
|
|
207
|
+
return {
|
|
208
|
+
"@context": "https://schema.org",
|
|
209
|
+
"@type": "SoftwareApplication",
|
|
210
|
+
name: opts.name,
|
|
211
|
+
description: opts.description,
|
|
212
|
+
url: opts.url,
|
|
213
|
+
applicationCategory: opts.category || "BusinessApplication",
|
|
214
|
+
operatingSystem: opts.operatingSystem || "Web, Cloud, iOS, Android",
|
|
215
|
+
offers: {
|
|
216
|
+
"@type": "Offer",
|
|
217
|
+
price: opts.priceMonthly !== undefined ? String(opts.priceMonthly) : "0",
|
|
218
|
+
priceCurrency: opts.currency || "USD",
|
|
219
|
+
description: "14-day free trial, no credit card required",
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* 4. FAQPage
|
|
226
|
+
*/
|
|
227
|
+
static buildFAQPage(faqs: { question: string; answer: string }[]): Record<string, unknown> {
|
|
228
|
+
return {
|
|
229
|
+
"@context": "https://schema.org",
|
|
230
|
+
"@type": "FAQPage",
|
|
231
|
+
mainEntity: faqs.map((faq) => ({
|
|
232
|
+
"@type": "Question",
|
|
233
|
+
name: faq.question,
|
|
234
|
+
acceptedAnswer: {
|
|
235
|
+
"@type": "Answer",
|
|
236
|
+
text: faq.answer,
|
|
237
|
+
},
|
|
238
|
+
})),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* 5. HowTo & HowToStep
|
|
244
|
+
*/
|
|
245
|
+
static buildHowTo(opts: {
|
|
246
|
+
name: string;
|
|
247
|
+
description: string;
|
|
248
|
+
steps: string[];
|
|
249
|
+
}): Record<string, unknown> {
|
|
250
|
+
return {
|
|
251
|
+
"@context": "https://schema.org",
|
|
252
|
+
"@type": "HowTo",
|
|
253
|
+
name: opts.name,
|
|
254
|
+
description: opts.description,
|
|
255
|
+
step: opts.steps.map((text, idx) => ({
|
|
256
|
+
"@type": "HowToStep",
|
|
257
|
+
position: idx + 1,
|
|
258
|
+
name: `Step ${idx + 1}`,
|
|
259
|
+
text,
|
|
260
|
+
})),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* 6. BreadcrumbList
|
|
266
|
+
*/
|
|
267
|
+
static buildBreadcrumbs(items: { name: string; url: string }[]): Record<string, unknown> {
|
|
268
|
+
return {
|
|
269
|
+
"@context": "https://schema.org",
|
|
270
|
+
"@type": "BreadcrumbList",
|
|
271
|
+
itemListElement: items.map((item, idx) => ({
|
|
272
|
+
"@type": "ListItem",
|
|
273
|
+
position: idx + 1,
|
|
274
|
+
name: item.name,
|
|
275
|
+
item: item.url,
|
|
276
|
+
})),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🗺️ Geographic Mesh Linking Engine
|
|
3
|
+
*
|
|
4
|
+
* Implements geodesic distance calculation (Haversine formula) and
|
|
5
|
+
* administrative zone clustering to connect neighboring cities and propagate PageRank.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface GeoCoordinate {
|
|
9
|
+
latitude: number;
|
|
10
|
+
longitude: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface MeshCity {
|
|
14
|
+
slug: string;
|
|
15
|
+
name: string;
|
|
16
|
+
departmentCode?: string;
|
|
17
|
+
region?: string;
|
|
18
|
+
country: string;
|
|
19
|
+
coordinates?: GeoCoordinate;
|
|
20
|
+
population?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class GeoMeshLinkingEngine {
|
|
24
|
+
/**
|
|
25
|
+
* Computes the great-circle distance between two points in kilometers (Haversine formula).
|
|
26
|
+
*/
|
|
27
|
+
static computeDistanceKm(coord1: GeoCoordinate, coord2: GeoCoordinate): number {
|
|
28
|
+
const R = 6371; // Earth radius in KM
|
|
29
|
+
const dLat = this.deg2rad(coord2.latitude - coord1.latitude);
|
|
30
|
+
const dLon = this.deg2rad(coord2.longitude - coord1.longitude);
|
|
31
|
+
|
|
32
|
+
const a =
|
|
33
|
+
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
34
|
+
Math.cos(this.deg2rad(coord1.latitude)) *
|
|
35
|
+
Math.cos(this.deg2rad(coord2.latitude)) *
|
|
36
|
+
Math.sin(dLon / 2) *
|
|
37
|
+
Math.sin(dLon / 2);
|
|
38
|
+
|
|
39
|
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
40
|
+
return R * c;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private static deg2rad(deg: number): number {
|
|
44
|
+
return deg * (Math.PI / 180);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Finds the N closest neighbor cities within the same country / department / distance.
|
|
49
|
+
*/
|
|
50
|
+
static findClosestNeighbors(
|
|
51
|
+
targetCity: MeshCity,
|
|
52
|
+
allCities: MeshCity[],
|
|
53
|
+
maxNeighbors = 4,
|
|
54
|
+
maxDistanceKm = 50,
|
|
55
|
+
): { city: MeshCity; distanceKm?: number }[] {
|
|
56
|
+
const candidateCities = allCities.filter(
|
|
57
|
+
(c) => c.slug !== targetCity.slug && c.country === targetCity.country,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// If coordinates are available, calculate exact geodesic proximity
|
|
61
|
+
if (targetCity.coordinates) {
|
|
62
|
+
const withDistance = candidateCities
|
|
63
|
+
.filter((c) => Boolean(c.coordinates))
|
|
64
|
+
.map((c) => ({
|
|
65
|
+
city: c,
|
|
66
|
+
distanceKm: this.computeDistanceKm(targetCity.coordinates!, c.coordinates!),
|
|
67
|
+
}))
|
|
68
|
+
.filter((item) => item.distanceKm <= maxDistanceKm)
|
|
69
|
+
.sort((a, b) => a.distanceKm - b.distanceKm);
|
|
70
|
+
|
|
71
|
+
if (withDistance.length > 0) {
|
|
72
|
+
return withDistance.slice(0, maxNeighbors);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Fallback: match by same department or region
|
|
77
|
+
const sameDepartment = candidateCities.filter(
|
|
78
|
+
(c) =>
|
|
79
|
+
targetCity.departmentCode &&
|
|
80
|
+
c.departmentCode === targetCity.departmentCode,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
if (sameDepartment.length > 0) {
|
|
84
|
+
return sameDepartment.slice(0, maxNeighbors).map((c) => ({ city: c }));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const sameRegion = candidateCities.filter(
|
|
88
|
+
(c) => targetCity.region && c.region === targetCity.region,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
return sameRegion.slice(0, maxNeighbors).map((c) => ({ city: c }));
|
|
92
|
+
}
|
|
93
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
export * from "./engine";
|
|
14
|
+
export * from "./slug-engine";
|
|
15
|
+
export * from "./legal-disclaimers";
|
|
16
|
+
export * from "./matrix-engine";
|
|
17
|
+
export * from "./llm-prompt";
|
|
14
18
|
export * from "./auth-key";
|
|
15
19
|
export * from "./token-quota-manager";
|
|
16
20
|
export * from "./lago-token-meter";
|
|
@@ -18,11 +22,39 @@ export * from "./indexnow-client";
|
|
|
18
22
|
export * from "./site-auditor";
|
|
19
23
|
export * from "./site-crawler";
|
|
20
24
|
export * from "./schema-builder";
|
|
25
|
+
export * from "./extended-schemas";
|
|
21
26
|
export * from "./analytics-client";
|
|
22
27
|
export * from "./serp-client";
|
|
23
28
|
export * from "./backlinks-client";
|
|
24
29
|
export * from "./ai-copilot-client";
|
|
25
30
|
export * from "./types";
|
|
31
|
+
export * from "./geo-mesh-linking";
|
|
32
|
+
export * from "./brand-dna-calendar";
|
|
33
|
+
export * from "./technical-rules-auditor";
|
|
34
|
+
export * from "./ai-bots-log-analyzer";
|
|
35
|
+
export * from "./serp-history-alerts";
|
|
36
|
+
export * from "./seo-opportunities-decay";
|
|
37
|
+
export * from "./knowledge-graph-linker";
|
|
38
|
+
export * from "./cro-copywriting-engine";
|
|
39
|
+
export * from "./mcp-seo-server";
|
|
40
|
+
export * from "./master-marketing-engine";
|
|
41
|
+
export * from "./social-video-seo";
|
|
42
|
+
export * from "./social-trend-seo";
|
|
43
|
+
export * from "./crosslink-scorer";
|
|
44
|
+
export * from "./instant-matrix-search";
|
|
45
|
+
export * from "./rss-syndication-feed";
|
|
46
|
+
export * from "./embeddable-seo-widget";
|
|
47
|
+
export * from "./ngram-density-analyzer";
|
|
48
|
+
export * from "./isr-cache-manager";
|
|
49
|
+
export * from "./llm-content-cleaner";
|
|
50
|
+
export * from "./copy-frameworks-master";
|
|
51
|
+
export * from "./social-ads-seo";
|
|
52
|
+
export * from "./ad-intelligence-cro";
|
|
53
|
+
export * from "./rank-math-parity";
|
|
54
|
+
export * from "./yoast-parity";
|
|
55
|
+
export * from "./social-growth-suite";
|
|
56
|
+
export * from "./team-rbac";
|
|
57
|
+
export * from "./real-reviews-sync";
|
|
26
58
|
|
|
27
59
|
import { LynxSeoEngine, type EngineConfig } from "./engine";
|
|
28
60
|
import { ApiKeyGuardian } from "./auth-key";
|
|
@@ -32,6 +64,35 @@ import { IndexNowClient } from "./indexnow-client";
|
|
|
32
64
|
import { SiteAuditor } from "./site-auditor";
|
|
33
65
|
import { DeepCrawlerAuditor } from "./site-crawler";
|
|
34
66
|
import { SchemaGraphBuilder } from "./schema-builder";
|
|
67
|
+
import { ExtendedSchemaGraphBuilder } from "./extended-schemas";
|
|
68
|
+
|
|
69
|
+
import { GeoMeshLinkingEngine } from "./geo-mesh-linking";
|
|
70
|
+
import { BrandDnaCalendarEngine } from "./brand-dna-calendar";
|
|
71
|
+
import { TechnicalRulesAuditor } from "./technical-rules-auditor";
|
|
72
|
+
import { AiBotsLogAnalyzer } from "./ai-bots-log-analyzer";
|
|
73
|
+
import { SerpRankHistoryEngine } from "./serp-history-alerts";
|
|
74
|
+
import { SeoOpportunitiesDecayDetector } from "./seo-opportunities-decay";
|
|
75
|
+
import { KnowledgeGraphLinker } from "./knowledge-graph-linker";
|
|
76
|
+
import { CroCopywritingEngine } from "./cro-copywriting-engine";
|
|
77
|
+
import { McpSeoServerHub } from "./mcp-seo-server";
|
|
78
|
+
import { MasterMarketingEngine } from "./master-marketing-engine";
|
|
79
|
+
import { SocialVideoSeoAnalyticsEngine } from "./social-video-seo";
|
|
80
|
+
import { SocialTrendSeoGenerator } from "./social-trend-seo";
|
|
81
|
+
import { CrosslinkScorerEngine } from "./crosslink-scorer";
|
|
82
|
+
import { InstantMatrixSearchEngine } from "./instant-matrix-search";
|
|
83
|
+
import { RssSyndicationFeedGenerator } from "./rss-syndication-feed";
|
|
84
|
+
import { EmbeddableSeoWidgetGenerator } from "./embeddable-seo-widget";
|
|
85
|
+
import { NGramDensityAnalyzer } from "./ngram-density-analyzer";
|
|
86
|
+
import { IsrCacheManager } from "./isr-cache-manager";
|
|
87
|
+
import { LlmContentCleaner } from "./llm-content-cleaner";
|
|
88
|
+
import { CopywritingFrameworksMaster } from "./copy-frameworks-master";
|
|
89
|
+
import { SocialAdsSocialSeoEngine } from "./social-ads-seo";
|
|
90
|
+
import { AdIntelligenceCroEngine } from "./ad-intelligence-cro";
|
|
91
|
+
import { RankMathParityEngine } from "./rank-math-parity";
|
|
92
|
+
import { YoastParityEngine } from "./yoast-parity";
|
|
93
|
+
import { SocialGrowthSuite } from "./social-growth-suite";
|
|
94
|
+
import { TeamRbacEngine } from "./team-rbac";
|
|
95
|
+
import { RealReviewsSyncEngine } from "./real-reviews-sync";
|
|
35
96
|
|
|
36
97
|
export function createLynxSeoEngine(config: EngineConfig) {
|
|
37
98
|
return new LynxSeoEngine(config);
|
|
@@ -48,6 +109,34 @@ export const LynxSeo = {
|
|
|
48
109
|
crawlDomain: DeepCrawlerAuditor.crawlAndAuditDomain,
|
|
49
110
|
inspectHtmlSnapshot: DeepCrawlerAuditor.inspectHtmlSnapshot,
|
|
50
111
|
buildSchemaGraph: SchemaGraphBuilder.buildGraph,
|
|
112
|
+
extendedSchemas: ExtendedSchemaGraphBuilder,
|
|
113
|
+
geoMesh: GeoMeshLinkingEngine,
|
|
114
|
+
brandDna: BrandDnaCalendarEngine,
|
|
115
|
+
technicalAuditor: TechnicalRulesAuditor,
|
|
116
|
+
aiBotsLogger: AiBotsLogAnalyzer,
|
|
117
|
+
serpRankHistory: SerpRankHistoryEngine,
|
|
118
|
+
opportunitiesDecay: SeoOpportunitiesDecayDetector,
|
|
119
|
+
knowledgeGraph: KnowledgeGraphLinker,
|
|
120
|
+
croCopywriting: CroCopywritingEngine,
|
|
121
|
+
mcpServer: McpSeoServerHub,
|
|
122
|
+
marketing: MasterMarketingEngine,
|
|
123
|
+
socialVideo: SocialVideoSeoAnalyticsEngine,
|
|
124
|
+
socialTrends: SocialTrendSeoGenerator,
|
|
125
|
+
crosslinks: CrosslinkScorerEngine,
|
|
126
|
+
instantSearch: InstantMatrixSearchEngine,
|
|
127
|
+
rssFeed: RssSyndicationFeedGenerator,
|
|
128
|
+
embeddableWidget: EmbeddableSeoWidgetGenerator,
|
|
129
|
+
ngramDensity: NGramDensityAnalyzer,
|
|
130
|
+
isrCache: IsrCacheManager,
|
|
131
|
+
llmCleaner: LlmContentCleaner,
|
|
132
|
+
copyFrameworks: CopywritingFrameworksMaster,
|
|
133
|
+
socialAds: SocialAdsSocialSeoEngine,
|
|
134
|
+
adIntelligence: AdIntelligenceCroEngine,
|
|
135
|
+
rankMathScore: RankMathParityEngine,
|
|
136
|
+
yoastReadability: YoastParityEngine,
|
|
137
|
+
socialGrowth: SocialGrowthSuite,
|
|
138
|
+
rbac: TeamRbacEngine,
|
|
139
|
+
realReviews: RealReviewsSyncEngine,
|
|
51
140
|
};
|
|
52
141
|
|
|
53
142
|
export default LynxSeo;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🔎 Instant In-Memory Matrix Search & Autocomplete ("Buscador") (Inspiration: santifer-irepair)
|
|
3
|
+
*
|
|
4
|
+
* Provides sub-millisecond client-side live filtering and autocomplete across all 8 programmatic matrices.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface SearchableMatrixPage {
|
|
8
|
+
url: string;
|
|
9
|
+
title: string;
|
|
10
|
+
serviceName: string;
|
|
11
|
+
matrixType: string;
|
|
12
|
+
modifierName: string; // City, Competitor, Industry, Software, Persona...
|
|
13
|
+
keywords: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class InstantMatrixSearchEngine {
|
|
17
|
+
/**
|
|
18
|
+
* Fast in-memory search over programmatic pages.
|
|
19
|
+
*/
|
|
20
|
+
static search(query: string, pages: SearchableMatrixPage[], limit = 10): SearchableMatrixPage[] {
|
|
21
|
+
const cleanQuery = query.trim().toLowerCase();
|
|
22
|
+
if (!cleanQuery) return [];
|
|
23
|
+
|
|
24
|
+
const tokens = cleanQuery.split(/\s+/).filter(Boolean);
|
|
25
|
+
|
|
26
|
+
return pages
|
|
27
|
+
.map((p) => {
|
|
28
|
+
const textTarget = `${p.title} ${p.serviceName} ${p.modifierName} ${p.keywords.join(" ")}`.toLowerCase();
|
|
29
|
+
let matchScore = 0;
|
|
30
|
+
|
|
31
|
+
for (const token of tokens) {
|
|
32
|
+
if (textTarget.includes(token)) {
|
|
33
|
+
matchScore += 1;
|
|
34
|
+
// Exact prefix match bonus
|
|
35
|
+
if (p.modifierName.toLowerCase().startsWith(token) || p.serviceName.toLowerCase().startsWith(token)) {
|
|
36
|
+
matchScore += 2;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { page: p, score: matchScore };
|
|
42
|
+
})
|
|
43
|
+
.filter((item) => item.score > 0)
|
|
44
|
+
.sort((a, b) => b.score - a.score)
|
|
45
|
+
.slice(0, limit)
|
|
46
|
+
.map((item) => item.page);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ⚡ Incremental Static Regeneration (ISR) & Edge Invalidation Cache Manager
|
|
3
|
+
*
|
|
4
|
+
* Implements high-throughput in-memory LRU caching with stale-while-revalidate semantics
|
|
5
|
+
* and selective tag-based invalidation for programmatic pages.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface CacheEntry<T> {
|
|
9
|
+
data: T;
|
|
10
|
+
timestamp: number;
|
|
11
|
+
tags: string[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class IsrCacheManager {
|
|
15
|
+
private static cache = new Map<string, CacheEntry<any>>();
|
|
16
|
+
private static defaultTtlMs = 24 * 3600 * 1000; // 24 hours
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Retrieves an item from cache, or executes the resolver and caches the result.
|
|
20
|
+
*/
|
|
21
|
+
static async getOrSet<T>(
|
|
22
|
+
key: string,
|
|
23
|
+
resolver: () => Promise<T> | T,
|
|
24
|
+
tags: string[] = [],
|
|
25
|
+
ttlMs = this.defaultTtlMs,
|
|
26
|
+
): Promise<T> {
|
|
27
|
+
const cached = this.cache.get(key);
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
|
|
30
|
+
if (cached && now - cached.timestamp < ttlMs) {
|
|
31
|
+
return cached.data;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const data = await resolver();
|
|
35
|
+
this.cache.set(key, {
|
|
36
|
+
data,
|
|
37
|
+
timestamp: now,
|
|
38
|
+
tags,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return data;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Invalidates a specific cache key.
|
|
46
|
+
*/
|
|
47
|
+
static invalidateKey(key: string): boolean {
|
|
48
|
+
return this.cache.delete(key);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Invalidates all cache entries matching a tag (e.g. "service:crm-pipeline" or "city:lyon").
|
|
53
|
+
*/
|
|
54
|
+
static invalidateTag(tag: string): number {
|
|
55
|
+
let count = 0;
|
|
56
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
57
|
+
if (entry.tags.includes(tag)) {
|
|
58
|
+
this.cache.delete(key);
|
|
59
|
+
count++;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return count;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Clears the entire cache.
|
|
67
|
+
*/
|
|
68
|
+
static clear(): void {
|
|
69
|
+
this.cache.clear();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Returns cache size.
|
|
74
|
+
*/
|
|
75
|
+
static size(): number {
|
|
76
|
+
return this.cache.size;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🧠 Google Knowledge Graph Entity Linker (Inspiration: advertools/knowledge_graph.py)
|
|
3
|
+
*
|
|
4
|
+
* Resolves official Google Entity IDs (`kgmid`), entity types, and Wikidata URIs
|
|
5
|
+
* to maximize semantic authority and Knowledge Panel recognition (Entity SEO).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface KnowledgeGraphEntity {
|
|
9
|
+
name: string;
|
|
10
|
+
kgmid: string; // e.g. "/m/02zsn" or "kg:/g/11b6t4q7k"
|
|
11
|
+
types: string[]; // e.g. ["Organization", "Corporation", "SoftwareApplication"]
|
|
12
|
+
description?: string;
|
|
13
|
+
detailedDescriptionUrl?: string;
|
|
14
|
+
wikidataUri?: string;
|
|
15
|
+
resultScore: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class KnowledgeGraphLinker {
|
|
19
|
+
/**
|
|
20
|
+
* Constructs search query parameters for the Google Knowledge Graph Search API.
|
|
21
|
+
*/
|
|
22
|
+
static buildSearchUrl(query: string, apiKey: string, limit = 5, languages = ["fr", "en"]): string {
|
|
23
|
+
const params = new URLSearchParams({
|
|
24
|
+
query,
|
|
25
|
+
key: apiKey,
|
|
26
|
+
limit: String(limit),
|
|
27
|
+
indent: "true",
|
|
28
|
+
languages: languages.join(","),
|
|
29
|
+
});
|
|
30
|
+
return `https://kgsearch.googleapis.com/v1/entities:search?${params.toString()}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parses Google Knowledge Graph JSON-LD response items into normalized entities.
|
|
35
|
+
*/
|
|
36
|
+
static parseApiResponse(jsonResponse: any): KnowledgeGraphEntity[] {
|
|
37
|
+
if (!jsonResponse || !Array.isArray(jsonResponse.itemListElement)) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return jsonResponse.itemListElement.map((item: any) => {
|
|
42
|
+
const result = item.result || {};
|
|
43
|
+
const kgmid = result["@id"] || "";
|
|
44
|
+
const types = Array.isArray(result["@type"]) ? result["@type"] : [result["@type"] || "Thing"];
|
|
45
|
+
const detailed = result.detailedDescription || {};
|
|
46
|
+
|
|
47
|
+
let wikidataUri: string | undefined;
|
|
48
|
+
if (detailed.url && detailed.url.includes("wikipedia.org")) {
|
|
49
|
+
wikidataUri = detailed.url;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
name: result.name || "",
|
|
54
|
+
kgmid,
|
|
55
|
+
types,
|
|
56
|
+
description: detailed.articleBody || result.description,
|
|
57
|
+
detailedDescriptionUrl: detailed.url,
|
|
58
|
+
wikidataUri,
|
|
59
|
+
resultScore: item.resultScore || 0,
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Generates Schema.org `sameAs` entity triples combining Google kgmid and Wikipedia/Wikidata.
|
|
66
|
+
*/
|
|
67
|
+
static generateEntitySameAsLinks(entity: KnowledgeGraphEntity, customUrls: string[] = []): string[] {
|
|
68
|
+
const links: string[] = [...customUrls];
|
|
69
|
+
|
|
70
|
+
if (entity.kgmid) {
|
|
71
|
+
const cleanMid = entity.kgmid.replace(/^kg:/, "");
|
|
72
|
+
links.push(`https://www.google.com/search?kgmid=${encodeURIComponent(cleanMid)}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (entity.wikidataUri) {
|
|
76
|
+
links.push(entity.wikidataUri);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return Array.from(new Set(links));
|
|
80
|
+
}
|
|
81
|
+
}
|