@lynxflow/seo-engine 1.8.30 → 2.0.1

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.
Files changed (96) hide show
  1. package/README.md +60 -117
  2. package/dist/ai-copilot-client.d.ts +2 -0
  3. package/dist/built-in-locations.d.ts +22 -0
  4. package/dist/content-ai-arsenal.d.ts +28 -0
  5. package/dist/existing-media-harvester.d.ts +33 -0
  6. package/dist/extended-schemas.d.ts +84 -0
  7. package/dist/features-knowledge-harvester.d.ts +56 -0
  8. package/dist/geo-citation-scorer.d.ts +34 -0
  9. package/dist/i18n-detector.d.ts +1 -1
  10. package/dist/index.d.ts +32 -7
  11. package/dist/index.js +567 -83
  12. package/dist/index.mjs +2062 -169
  13. package/dist/internal-pagerank-graph.d.ts +59 -0
  14. package/dist/knowledge-bank-builder.d.ts +91 -0
  15. package/dist/marketing-skills-engine.d.ts +74 -0
  16. package/dist/matrix-engine.d.ts +29 -0
  17. package/dist/power-words-psychology.d.ts +27 -15
  18. package/dist/public-manifest-engine.d.ts +58 -0
  19. package/dist/rag-knowledge-engine.d.ts +50 -0
  20. package/dist/rate-limited-translator.d.ts +19 -0
  21. package/dist/token-quota-manager.d.ts +4 -0
  22. package/package.json +11 -3
  23. package/TUTORIEL_INTEGRATION_SITE.md +0 -283
  24. package/connectors/cloudflare-worker/worker.js +0 -106
  25. package/connectors/laravel/LynxSeoController.php +0 -37
  26. package/connectors/wordpress/lynxseo-admin-app.js +0 -2611
  27. package/connectors/wordpress/lynxseo-connector.php +0 -1712
  28. package/connectors/wordpress/lynxseo-connector.zip +0 -0
  29. package/lynxflow-seo-engine-1.2.0.tgz +0 -0
  30. package/src/ad-intelligence-cro.ts +0 -140
  31. package/src/ai-bots-log-analyzer.ts +0 -127
  32. package/src/ai-copilot-client.ts +0 -99
  33. package/src/analytics-client.ts +0 -315
  34. package/src/auth-key.ts +0 -262
  35. package/src/backlinks-client.ts +0 -85
  36. package/src/brand-dna-calendar.ts +0 -120
  37. package/src/brand-icons.ts +0 -138
  38. package/src/built-in-locations.ts +0 -170
  39. package/src/content-ai-arsenal.ts +0 -268
  40. package/src/copy-frameworks-master.ts +0 -89
  41. package/src/cro-copywriting-engine.ts +0 -105
  42. package/src/crosslink-scorer.ts +0 -101
  43. package/src/embeddable-seo-widget.ts +0 -57
  44. package/src/engine.test.ts +0 -386
  45. package/src/engine.ts +0 -813
  46. package/src/existing-media-harvester.ts +0 -98
  47. package/src/extended-schemas.ts +0 -589
  48. package/src/features-knowledge-harvester.ts +0 -190
  49. package/src/free-public-tools.ts +0 -224
  50. package/src/geo-mesh-linking.ts +0 -93
  51. package/src/google-business-profile.ts +0 -280
  52. package/src/i18n-detector.ts +0 -98
  53. package/src/i18n-dictionary.ts +0 -422
  54. package/src/index.ts +0 -164
  55. package/src/indexnow-client.ts +0 -94
  56. package/src/instant-matrix-search.ts +0 -48
  57. package/src/isr-cache-manager.ts +0 -78
  58. package/src/keyword-permutator.ts +0 -118
  59. package/src/knowledge-bank-builder.ts +0 -299
  60. package/src/knowledge-graph-linker.ts +0 -81
  61. package/src/lago-token-meter.ts +0 -120
  62. package/src/legal-disclaimers.ts +0 -407
  63. package/src/llm-content-cleaner.ts +0 -64
  64. package/src/llm-prompt.ts +0 -323
  65. package/src/master-marketing-engine.ts +0 -314
  66. package/src/matrix-engine.ts +0 -1204
  67. package/src/mcp-seo-server.ts +0 -108
  68. package/src/ngram-density-analyzer.ts +0 -108
  69. package/src/og-image-generator.ts +0 -109
  70. package/src/power-words-psychology.ts +0 -369
  71. package/src/public-manifest-engine.ts +0 -109
  72. package/src/rag-knowledge-engine.ts +0 -163
  73. package/src/rank-math-parity.ts +0 -235
  74. package/src/rate-limited-translator.ts +0 -110
  75. package/src/real-reviews-sync.ts +0 -207
  76. package/src/rss-syndication-feed.ts +0 -56
  77. package/src/schema-builder.ts +0 -134
  78. package/src/seo-opportunities-decay.ts +0 -154
  79. package/src/serp-client.ts +0 -89
  80. package/src/serp-history-alerts.ts +0 -75
  81. package/src/site-auditor.ts +0 -83
  82. package/src/site-crawler.ts +0 -406
  83. package/src/slug-engine.ts +0 -183
  84. package/src/social-ads-seo.ts +0 -211
  85. package/src/social-growth-suite.ts +0 -310
  86. package/src/social-trend-seo.ts +0 -103
  87. package/src/social-video-seo.ts +0 -100
  88. package/src/team-rbac.ts +0 -61
  89. package/src/technical-rules-auditor.ts +0 -171
  90. package/src/token-quota-manager.ts +0 -91
  91. package/src/types.ts +0 -143
  92. package/src/ui-icons.ts +0 -134
  93. package/src/urlytics-engine.ts +0 -97
  94. package/src/video-youtube-analyzer.ts +0 -81
  95. package/src/yoast-parity.ts +0 -161
  96. package/tsconfig.json +0 -15
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // packages/lynx-seo-engine/src/token-quota-manager.ts
1
+ // src/token-quota-manager.ts
2
2
  class TokenQuotaManager {
3
3
  tier;
4
4
  monthlyCreditBudget;
@@ -35,6 +35,11 @@ class TokenQuotaManager {
35
35
  timestamp: new Date().toISOString()
36
36
  };
37
37
  }
38
+ consume(tokens, model = "openrouter/auto") {
39
+ const prompt = Math.floor(tokens * 0.6);
40
+ const completion = tokens - prompt;
41
+ return this.consumeTokens(prompt, completion, model);
42
+ }
38
43
  getStatus() {
39
44
  return {
40
45
  tier: this.tier,
@@ -46,7 +51,7 @@ class TokenQuotaManager {
46
51
  }
47
52
  }
48
53
 
49
- // packages/lynx-seo-engine/src/auth-key.ts
54
+ // src/auth-key.ts
50
55
  class ApiKeyGuardian {
51
56
  static domainRegistry = new Map;
52
57
  static uniquePageRegistry = new Map;
@@ -220,7 +225,7 @@ class ApiKeyGuardian {
220
225
  }
221
226
  }
222
227
 
223
- // packages/lynx-seo-engine/src/i18n-dictionary.ts
228
+ // src/i18n-dictionary.ts
224
229
  var DICTIONARIES = {
225
230
  fr: {
226
231
  geoTitle: (s, city, country, b) => `${s} à ${city} (${country}) — ${b}`,
@@ -576,7 +581,7 @@ function isSupportedLanguage(lang) {
576
581
  return Boolean(DICTIONARIES[code]);
577
582
  }
578
583
 
579
- // packages/lynx-seo-engine/src/indexnow-client.ts
584
+ // src/indexnow-client.ts
580
585
  var SUCCESS_STATUSES = new Set([200, 202]);
581
586
  var MAX_URLS_PER_REQUEST = 1e4;
582
587
  var REQUEST_TIMEOUT_MS = 15000;
@@ -632,7 +637,7 @@ class IndexNowClient {
632
637
  }
633
638
  }
634
639
 
635
- // packages/lynx-seo-engine/src/analytics-client.ts
640
+ // src/analytics-client.ts
636
641
  class LynxAnalyticsClient {
637
642
  apiKey;
638
643
  endpoint;
@@ -861,7 +866,7 @@ class LynxAnalyticsClient {
861
866
  }
862
867
  }
863
868
 
864
- // packages/lynx-seo-engine/src/serp-client.ts
869
+ // src/serp-client.ts
865
870
  class SerpClient {
866
871
  apiKey;
867
872
  endpoint;
@@ -914,7 +919,7 @@ class SerpClient {
914
919
  }
915
920
  }
916
921
 
917
- // packages/lynx-seo-engine/src/backlinks-client.ts
922
+ // src/backlinks-client.ts
918
923
  class BacklinksClient {
919
924
  apiKey;
920
925
  endpoint;
@@ -970,8 +975,8 @@ class BacklinksClient {
970
975
  }
971
976
  }
972
977
 
973
- // packages/lynx-seo-engine/src/rag-knowledge-engine.ts
974
- class LynxRagKnowledgeEngine2 {
978
+ // src/rag-knowledge-engine.ts
979
+ class LynxRagKnowledgeEngine {
975
980
  static chunkStore = new Map;
976
981
  static ingestDocument(siteId, documentName, rawText, category = "general") {
977
982
  const cleanText = rawText.replace(/\r\n/g, `
@@ -1061,7 +1066,7 @@ Directives d'analyse :
1061
1066
  }
1062
1067
  }
1063
1068
 
1064
- // packages/lynx-seo-engine/src/ai-copilot-client.ts
1069
+ // src/ai-copilot-client.ts
1065
1070
  class AiCopilotClient {
1066
1071
  apiKey;
1067
1072
  endpoint;
@@ -1073,7 +1078,7 @@ class AiCopilotClient {
1073
1078
  try {
1074
1079
  let ragFacts = [];
1075
1080
  if (params.enableRag && params.siteId) {
1076
- const retrieved = LynxRagKnowledgeEngine2.retrieveRelevantContext(params.siteId, params.topic, 3);
1081
+ const retrieved = LynxRagKnowledgeEngine.retrieveRelevantContext(params.siteId, params.topic, 3);
1077
1082
  ragFacts = retrieved.map((r) => r.chunk.content);
1078
1083
  }
1079
1084
  if (!this.apiKey || this.apiKey.startsWith("demo_")) {
@@ -1136,7 +1141,7 @@ Optimiser votre stratégie avec ${params.targetKeywords.join(", ")}.
1136
1141
  }
1137
1142
  }
1138
1143
 
1139
- // packages/lynx-seo-engine/src/lago-token-meter.ts
1144
+ // src/lago-token-meter.ts
1140
1145
  class LagoTokenMeter {
1141
1146
  lagoApiUrl;
1142
1147
  apiKey;
@@ -1217,7 +1222,7 @@ class HyperswitchGateway {
1217
1222
  }
1218
1223
  }
1219
1224
 
1220
- // packages/lynx-seo-engine/src/site-auditor.ts
1225
+ // src/site-auditor.ts
1221
1226
  class SiteAuditor {
1222
1227
  static inspectMeta(params) {
1223
1228
  const checks = [];
@@ -1263,7 +1268,7 @@ class SiteAuditor {
1263
1268
  }
1264
1269
  }
1265
1270
 
1266
- // packages/lynx-seo-engine/src/schema-builder.ts
1271
+ // src/schema-builder.ts
1267
1272
  class SchemaGraphBuilder {
1268
1273
  static buildGraph(opts) {
1269
1274
  const productNode = {
@@ -1358,7 +1363,7 @@ class SchemaGraphBuilder {
1358
1363
  }
1359
1364
  }
1360
1365
 
1361
- // packages/lynx-seo-engine/src/site-crawler.ts
1366
+ // src/site-crawler.ts
1362
1367
  class DeepCrawlerAuditor {
1363
1368
  static DEFAULT_USER_AGENT = "LynxFlowSeoBot/1.2 (+https://lynxintel.io/bot; technical audit)";
1364
1369
  static inspectHtmlSnapshot(url, html, statusCode = 200, responseTimeMs = 45) {
@@ -1647,7 +1652,7 @@ class DeepCrawlerAuditor {
1647
1652
  }
1648
1653
  }
1649
1654
 
1650
- // packages/lynx-seo-engine/src/cro-copywriting-engine.ts
1655
+ // src/cro-copywriting-engine.ts
1651
1656
  class CroCopywritingEngine {
1652
1657
  static generatePasCopy(solutionName, targetNiche) {
1653
1658
  return {
@@ -1709,7 +1714,7 @@ class CroCopywritingEngine {
1709
1714
  }
1710
1715
  }
1711
1716
 
1712
- // packages/lynx-seo-engine/src/llm-content-cleaner.ts
1717
+ // src/llm-content-cleaner.ts
1713
1718
  class LlmContentCleaner {
1714
1719
  static cleanForLlm(content) {
1715
1720
  return this.htmlToCleanMarkdown(content);
@@ -1737,7 +1742,7 @@ $1
1737
1742
  }
1738
1743
  }
1739
1744
 
1740
- // packages/lynx-seo-engine/src/engine.ts
1745
+ // src/engine.ts
1741
1746
  class LynxSeoEngine {
1742
1747
  config;
1743
1748
  servicesMap;
@@ -2335,7 +2340,7 @@ ${servicesList}
2335
2340
  `;
2336
2341
  }
2337
2342
  }
2338
- // packages/lynx-seo-engine/src/slug-engine.ts
2343
+ // src/slug-engine.ts
2339
2344
  var MULTILINGUAL_STOP_WORDS = {
2340
2345
  en: new Set([
2341
2346
  "the",
@@ -2726,7 +2731,7 @@ function validateSeoSlug(slug) {
2726
2731
  errors
2727
2732
  };
2728
2733
  }
2729
- // packages/lynx-seo-engine/src/legal-disclaimers.ts
2734
+ // src/legal-disclaimers.ts
2730
2735
  var MULTILINGUAL_DISCLAIMERS = {
2731
2736
  en: {
2732
2737
  comparison: {
@@ -3022,7 +3027,7 @@ class LegalDisclaimerEngine {
3022
3027
  };
3023
3028
  }
3024
3029
  }
3025
- // packages/lynx-seo-engine/src/extended-schemas.ts
3030
+ // src/extended-schemas.ts
3026
3031
  var SCHEMA_LOCAL_BUSINESS_MAP = {
3027
3032
  plumber: "Plumber",
3028
3033
  plumbing: "Plumber",
@@ -3378,7 +3383,7 @@ class ExtendedSchemaGraphBuilder {
3378
3383
  }
3379
3384
  }
3380
3385
 
3381
- // packages/lynx-seo-engine/src/built-in-locations.ts
3386
+ // src/built-in-locations.ts
3382
3387
  var BUILT_IN_LOCATIONS_DATABASE = {
3383
3388
  fr: [
3384
3389
  { slug: "paris", name: "Paris", region: "Île-de-France", country: "France", population: 2161000, currency: "EUR", currencySymbol: "€", latitude: 48.8566, longitude: 2.3522, neighborSlugs: ["boulogne-billancourt", "saint-denis", "montreuil"] },
@@ -3519,10 +3524,69 @@ function resolveBuiltInLocations(territories) {
3519
3524
  }
3520
3525
  }
3521
3526
  }
3522
- return results.length > 0 ? results : [...BUILT_IN_LOCATIONS_DATABASE.fr];
3527
+ return results.length > 0 ? results : [...BUILT_IN_LOCATIONS_DATABASE.us];
3528
+ }
3529
+ function createDynamicLocation(slugOrInput, fallbackCountry = "United States", options) {
3530
+ const cleanInput = slugOrInput.trim().toLowerCase();
3531
+ const slug = cleanInput.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3532
+ const formattedName = slug.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
3533
+ const isUsZip = /^\d{5}(-\d{4})?$/.test(cleanInput);
3534
+ const isFrenchPostal = /^\d{5}$/.test(cleanInput) && (fallbackCountry.toLowerCase().includes("fr") || fallbackCountry.toLowerCase().includes("france"));
3535
+ let displayName = formattedName;
3536
+ let regionName = options?.region || "Metropolitan Area";
3537
+ if (isUsZip) {
3538
+ displayName = `Area ${cleanInput}`;
3539
+ regionName = `ZIP Code ${cleanInput}`;
3540
+ } else if (isFrenchPostal) {
3541
+ displayName = `Secteur ${cleanInput}`;
3542
+ regionName = `Département ${cleanInput.slice(0, 2)}`;
3543
+ }
3544
+ const countryLower = fallbackCountry.toLowerCase();
3545
+ const isUS = countryLower.includes("us") || countryLower.includes("state") || countryLower.includes("america");
3546
+ const isUK = countryLower.includes("uk") || countryLower.includes("kingdom") || countryLower.includes("britain");
3547
+ const isJapan = countryLower.includes("japan") || countryLower.includes("jp") || countryLower.includes("tokyo");
3548
+ const isCanada = countryLower.includes("canada") || countryLower.includes("ca");
3549
+ const isAustralia = countryLower.includes("australia") || countryLower.includes("au");
3550
+ const isBrazil = countryLower.includes("brazil") || countryLower.includes("br") || countryLower.includes("brasil");
3551
+ const isSwitzerland = countryLower.includes("switzerland") || countryLower.includes("swiss") || countryLower.includes("ch");
3552
+ const currency = options?.currency || (isUS ? "USD" : isUK ? "GBP" : isJapan ? "JPY" : isCanada ? "CAD" : isAustralia ? "AUD" : isBrazil ? "BRL" : isSwitzerland ? "CHF" : "EUR");
3553
+ const currencySymbol = options?.currencySymbol || (isUS ? "$" : isUK ? "£" : isJapan ? "¥" : isCanada ? "CAD $" : isAustralia ? "AUD $" : isBrazil ? "R$" : isSwitzerland ? "CHF" : "€");
3554
+ return {
3555
+ slug,
3556
+ name: displayName,
3557
+ region: regionName,
3558
+ country: fallbackCountry,
3559
+ currency,
3560
+ currencySymbol,
3561
+ population: 50000,
3562
+ latitude: 40.7128,
3563
+ longitude: -74.006,
3564
+ neighborSlugs: []
3565
+ };
3566
+ }
3567
+ function resolveLocationFromHeaders(headers) {
3568
+ const getHeader = (name) => {
3569
+ if (!headers)
3570
+ return;
3571
+ if (typeof headers.get === "function") {
3572
+ return headers.get(name) || headers.get(name.toLowerCase()) || undefined;
3573
+ }
3574
+ const map = headers;
3575
+ return map[name] || map[name.toLowerCase()] || undefined;
3576
+ };
3577
+ const rawCity = getHeader("cf-ipcity") || getHeader("x-vercel-ip-city") || getHeader("x-geo-city");
3578
+ const rawCountry = getHeader("cf-ipcountry") || getHeader("x-vercel-ip-country") || getHeader("x-geo-country");
3579
+ const rawRegion = getHeader("cf-region") || getHeader("x-vercel-ip-country-region");
3580
+ const rawPostalCode = getHeader("cf-postal-code") || getHeader("x-vercel-ip-postal-code");
3581
+ return {
3582
+ city: rawCity ? decodeURIComponent(rawCity) : undefined,
3583
+ country: rawCountry ? decodeURIComponent(rawCountry) : undefined,
3584
+ region: rawRegion ? decodeURIComponent(rawRegion) : undefined,
3585
+ postalCode: rawPostalCode ? decodeURIComponent(rawPostalCode) : undefined
3586
+ };
3523
3587
  }
3524
3588
 
3525
- // packages/lynx-seo-engine/src/og-image-generator.ts
3589
+ // src/og-image-generator.ts
3526
3590
  class OgImageGenerator {
3527
3591
  static generateOgImageUrl(domain, pageMeta, brandName) {
3528
3592
  const cleanDomain = domain.replace(/\/+$/, "");
@@ -3603,7 +3667,7 @@ class OgImageGenerator {
3603
3667
  }
3604
3668
  }
3605
3669
 
3606
- // packages/lynx-seo-engine/src/matrix-engine.ts
3670
+ // src/matrix-engine.ts
3607
3671
  class PseoMatrixEngine {
3608
3672
  legalEngine;
3609
3673
  constructor(legalOptions) {
@@ -4149,7 +4213,32 @@ class PseoMatrixEngine {
4149
4213
  resolvePage(slugOrPath, domain, data, options) {
4150
4214
  const path = typeof slugOrPath === "string" ? slugOrPath.startsWith("/") ? slugOrPath : `/${slugOrPath}` : `/${slugOrPath.join("/")}`;
4151
4215
  const pages = this.generateAllMatrices(domain, data, options);
4152
- return pages.find((p) => p.urlPath === path);
4216
+ const existing = pages.find((p) => p.urlPath === path);
4217
+ if (existing)
4218
+ return existing;
4219
+ const segments = path.split("/").filter(Boolean);
4220
+ if (segments.length >= 2 && data.services && data.services.length > 0) {
4221
+ const serviceSlug = segments[0];
4222
+ const locationSlug = segments[segments.length - 1];
4223
+ const matchedService = data.services.find((s) => cleanSeoSlug(s.slug, { language: options.language }) === serviceSlug || s.slug === serviceSlug);
4224
+ if (matchedService) {
4225
+ const countryList = options.allowedCountries || options.countries;
4226
+ const fallbackCountry = Array.isArray(countryList) ? countryList[0] : typeof countryList === "string" && countryList !== "international" ? countryList : "United States";
4227
+ const dynamicLoc = createDynamicLocation(locationSlug, fallbackCountry, {
4228
+ currency: options.defaultCurrency,
4229
+ currencySymbol: options.defaultCurrencySymbol
4230
+ });
4231
+ if (options.strictCountryBoundary && countryList) {
4232
+ const allowed = (Array.isArray(countryList) ? countryList : [countryList]).map((c) => c.toLowerCase());
4233
+ if (!allowed.includes("international") && !allowed.some((c) => dynamicLoc.country.toLowerCase().includes(c))) {
4234
+ return;
4235
+ }
4236
+ }
4237
+ const dynamicPages = this.generateLocalGeoMatrix(domain, [matchedService], [dynamicLoc], options);
4238
+ return dynamicPages.find((p) => p.urlPath === path) || dynamicPages[0];
4239
+ }
4240
+ }
4241
+ return;
4153
4242
  }
4154
4243
  generateSitemapXml(domain, data, options) {
4155
4244
  const pages = this.generateAllMatrices(domain, data, options).filter((p) => p.robots.includes("index"));
@@ -4354,7 +4443,7 @@ Notre couverture pour **${service}** s'étend à l'ensemble du bassin économiqu
4354
4443
  };
4355
4444
  }
4356
4445
  }
4357
- // packages/lynx-seo-engine/src/i18n-detector.ts
4446
+ // src/i18n-detector.ts
4358
4447
  var SUPPORTED_CANONICAL_LOCALES = [
4359
4448
  "en",
4360
4449
  "fr",
@@ -4431,7 +4520,7 @@ class I18nDetector {
4431
4520
  };
4432
4521
  }
4433
4522
  }
4434
- // packages/lynx-seo-engine/src/brand-icons.ts
4523
+ // src/brand-icons.ts
4435
4524
  var BRAND_ICONS = {
4436
4525
  facebook: {
4437
4526
  name: "Facebook",
@@ -4547,7 +4636,7 @@ function renderBrandIconSvg(input, className = "w-5 h-5 inline-block") {
4547
4636
  return "";
4548
4637
  return `<svg class="${className}" viewBox="${icon.viewBox}" fill="currentColor" aria-hidden="true"><path d="${icon.svgPath}"/></svg>`;
4549
4638
  }
4550
- // packages/lynx-seo-engine/src/ui-icons.ts
4639
+ // src/ui-icons.ts
4551
4640
  var UI_ICONS = {
4552
4641
  zap: {
4553
4642
  name: "Zap / Lightning",
@@ -4660,7 +4749,7 @@ function resolveFeatureIcon(featureText) {
4660
4749
  }
4661
4750
  return "zap";
4662
4751
  }
4663
- // packages/lynx-seo-engine/src/free-public-tools.ts
4752
+ // src/free-public-tools.ts
4664
4753
  class FreePublicToolsEngine {
4665
4754
  static simulateSerpSnippet(input) {
4666
4755
  const title = (input.title || "").trim();
@@ -4794,7 +4883,7 @@ class FreePublicToolsEngine {
4794
4883
  });
4795
4884
  }
4796
4885
  }
4797
- // packages/lynx-seo-engine/src/urlytics-engine.ts
4886
+ // src/urlytics-engine.ts
4798
4887
  class UrlyticsEngine {
4799
4888
  static parseUrl(rawUrl) {
4800
4889
  if (!rawUrl || typeof rawUrl !== "string") {
@@ -4856,7 +4945,7 @@ class UrlyticsEngine {
4856
4945
  return urls.map((u) => this.parseUrl(u));
4857
4946
  }
4858
4947
  }
4859
- // packages/lynx-seo-engine/src/keyword-permutator.ts
4948
+ // src/keyword-permutator.ts
4860
4949
  class KeywordPermutatorEngine {
4861
4950
  static generateKeywordMatrix(options) {
4862
4951
  const { products, words = [], locations = [], maxCombinations = 5000 } = options;
@@ -4932,7 +5021,7 @@ class KeywordPermutatorEngine {
4932
5021
  return results;
4933
5022
  }
4934
5023
  }
4935
- // packages/lynx-seo-engine/src/llm-prompt.ts
5024
+ // src/llm-prompt.ts
4936
5025
  var PSEO_AGENT_SYSTEM_PROMPT = `
4937
5026
  You are the Programmatic SEO Implementation Specialist powered by @lynxflow/seo-engine.
4938
5027
  Your role is to configure, resolve, and maintain programmatic SEO pages, XML sitemaps, Schema.org JSON-LD graphs, and compliance rules.
@@ -5014,15 +5103,17 @@ import { PseoMatrixEngine } from "@lynxflow/seo-engine";
5014
5103
  export const matrixEngine = new PseoMatrixEngine();
5015
5104
 
5016
5105
  export const SEO_CONFIG = {
5017
- brandName: "Acme",
5018
- language: "fr",
5019
- countries: ["france", "belgique", "suisse"], // Or "europe" / "international". The SDK automatically provides verified cities!
5106
+ brandName: "Acme Global",
5107
+ language: "en",
5108
+ countries: ["united-states", "united-kingdom", "germany", "france"], // Or "international" / any country worldwide.
5020
5109
  cleanDirectRoutes: true, // Eliminates parasite words (/solutions/, etc.)
5021
5110
  minPopulationToIndex: 15_000,
5022
5111
  };
5023
5112
 
5024
- // Discovered EXCLUSIVELY from Frontend UI components
5025
- // NOTE: DO NOT list cities manually! The SDK auto-provisions them based on your target countries/scope.
5113
+ // \uD83C\uDF10 UNIVERSAL INTERNATIONAL RESOLUTION RULES:
5114
+ // - Supports ANY ISO-639-1 / BCP-47 language code passed dynamically at runtime (en, de, es, fr, ja, pt, ar, zh, etc.).
5115
+ // - Supports ANY city, postal code, or district worldwide via dynamic synthesis (createDynamicLocation) and real-time Edge GeoIP.
5116
+ // - DO NOT hardcode fixed city counts or language limits. The SDK resolves any valid input on the fly.
5026
5117
  export const PSEO_DATASET = {
5027
5118
  services: [
5028
5119
  // 1 per major frontend feature branch
@@ -5246,7 +5337,7 @@ ${PSEO_AGENT_SYSTEM_PROMPT}
5246
5337
  - Domain: ${customContext.domain || "https://example.com"}
5247
5338
  `.trim();
5248
5339
  }
5249
- // packages/lynx-seo-engine/src/geo-mesh-linking.ts
5340
+ // src/geo-mesh-linking.ts
5250
5341
  class GeoMeshLinkingEngine {
5251
5342
  static computeDistanceKm(coord1, coord2) {
5252
5343
  const R = 6371;
@@ -5278,7 +5369,7 @@ class GeoMeshLinkingEngine {
5278
5369
  return sameRegion.slice(0, maxNeighbors).map((c) => ({ city: c }));
5279
5370
  }
5280
5371
  }
5281
- // packages/lynx-seo-engine/src/brand-dna-calendar.ts
5372
+ // src/brand-dna-calendar.ts
5282
5373
  class BrandDnaCalendarEngine {
5283
5374
  static generate4WeekCalendar(dna, services, topCities = ["Paris", "Lyon", "Marseille", "Bordeaux"]) {
5284
5375
  const calendar = [];
@@ -5358,7 +5449,7 @@ class BrandDnaCalendarEngine {
5358
5449
  return calendar;
5359
5450
  }
5360
5451
  }
5361
- // packages/lynx-seo-engine/src/technical-rules-auditor.ts
5452
+ // src/technical-rules-auditor.ts
5362
5453
  class TechnicalRulesAuditor {
5363
5454
  static auditPage(page) {
5364
5455
  const issues = [];
@@ -5476,7 +5567,7 @@ class TechnicalRulesAuditor {
5476
5567
  return issues;
5477
5568
  }
5478
5569
  }
5479
- // packages/lynx-seo-engine/src/ai-bots-log-analyzer.ts
5570
+ // src/ai-bots-log-analyzer.ts
5480
5571
  class AiBotsLogAnalyzer {
5481
5572
  static classifyUserAgent(ua) {
5482
5573
  const lowerUa = ua.toLowerCase();
@@ -5547,7 +5638,7 @@ class AiBotsLogAnalyzer {
5547
5638
  return result;
5548
5639
  }
5549
5640
  }
5550
- // packages/lynx-seo-engine/src/serp-history-alerts.ts
5641
+ // src/serp-history-alerts.ts
5551
5642
  class SerpRankHistoryEngine {
5552
5643
  static analyzeRankShift(previousSnapshot, currentSnapshot) {
5553
5644
  const prev = previousSnapshot.position;
@@ -5588,7 +5679,7 @@ class SerpRankHistoryEngine {
5588
5679
  return null;
5589
5680
  }
5590
5681
  }
5591
- // packages/lynx-seo-engine/src/seo-opportunities-decay.ts
5682
+ // src/seo-opportunities-decay.ts
5592
5683
  class SeoOpportunitiesDecayDetector {
5593
5684
  static getExpectedCtr(position) {
5594
5685
  if (position <= 1)
@@ -5677,7 +5768,7 @@ class SeoOpportunitiesDecayDetector {
5677
5768
  return cannibalized;
5678
5769
  }
5679
5770
  }
5680
- // packages/lynx-seo-engine/src/knowledge-graph-linker.ts
5771
+ // src/knowledge-graph-linker.ts
5681
5772
  class KnowledgeGraphLinker {
5682
5773
  static buildSearchUrl(query, apiKey, limit = 5, languages = ["fr", "en"]) {
5683
5774
  const params = new URLSearchParams({
@@ -5725,7 +5816,7 @@ class KnowledgeGraphLinker {
5725
5816
  return Array.from(new Set(links));
5726
5817
  }
5727
5818
  }
5728
- // packages/lynx-seo-engine/src/mcp-seo-server.ts
5819
+ // src/mcp-seo-server.ts
5729
5820
  class McpSeoServerHub {
5730
5821
  static getRegisteredTools() {
5731
5822
  return [
@@ -5814,7 +5905,7 @@ class McpSeoServerHub {
5814
5905
  }
5815
5906
  }
5816
5907
  }
5817
- // packages/lynx-seo-engine/src/master-marketing-engine.ts
5908
+ // src/master-marketing-engine.ts
5818
5909
  class MasterMarketingEngine {
5819
5910
  static generateDirectoryListings(domain = "lynxintel.io", brandName = "LynxSEO") {
5820
5911
  return [
@@ -6039,7 +6130,7 @@ class MasterMarketingEngine {
6039
6130
  };
6040
6131
  }
6041
6132
  }
6042
- // packages/lynx-seo-engine/src/social-video-seo.ts
6133
+ // src/social-video-seo.ts
6043
6134
  class SocialVideoSeoAnalyticsEngine {
6044
6135
  static buildVideoObjectSchema(opts) {
6045
6136
  const schema = {
@@ -6087,7 +6178,7 @@ class SocialVideoSeoAnalyticsEngine {
6087
6178
  };
6088
6179
  }
6089
6180
  }
6090
- // packages/lynx-seo-engine/src/social-trend-seo.ts
6181
+ // src/social-trend-seo.ts
6091
6182
  class SocialTrendSeoGenerator {
6092
6183
  static generateActionFromTrend(trend, serviceSlug = "crm-pipeline", serviceName = "CRM Pipeline Commercial") {
6093
6184
  const cleanTopic = trend.topicName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
@@ -6147,7 +6238,7 @@ class SocialTrendSeoGenerator {
6147
6238
  ];
6148
6239
  }
6149
6240
  }
6150
- // packages/lynx-seo-engine/src/crosslink-scorer.ts
6241
+ // src/crosslink-scorer.ts
6151
6242
  class CrosslinkScorerEngine {
6152
6243
  static computeCrosslinkScore(source, candidate) {
6153
6244
  if (source.slug === candidate.slug)
@@ -6198,7 +6289,7 @@ class CrosslinkScorerEngine {
6198
6289
  return suggestions.sort((a, b) => b.score - a.score).slice(0, maxLinks);
6199
6290
  }
6200
6291
  }
6201
- // packages/lynx-seo-engine/src/instant-matrix-search.ts
6292
+ // src/instant-matrix-search.ts
6202
6293
  class InstantMatrixSearchEngine {
6203
6294
  static search(query, pages, limit = 10) {
6204
6295
  const cleanQuery = query.trim().toLowerCase();
@@ -6220,7 +6311,7 @@ class InstantMatrixSearchEngine {
6220
6311
  }).filter((item) => item.score > 0).sort((a, b) => b.score - a.score).slice(0, limit).map((item) => item.page);
6221
6312
  }
6222
6313
  }
6223
- // packages/lynx-seo-engine/src/rss-syndication-feed.ts
6314
+ // src/rss-syndication-feed.ts
6224
6315
  class RssSyndicationFeedGenerator {
6225
6316
  static generateRss2(opts) {
6226
6317
  const itemsXml = opts.items.map((item) => `
@@ -6249,7 +6340,7 @@ ${itemsXml}
6249
6340
  </rss>`.trim();
6250
6341
  }
6251
6342
  }
6252
- // packages/lynx-seo-engine/src/embeddable-seo-widget.ts
6343
+ // src/embeddable-seo-widget.ts
6253
6344
  class EmbeddableSeoWidgetGenerator {
6254
6345
  static generateWidgetJs(apiBaseUrl) {
6255
6346
  return `
@@ -6297,7 +6388,7 @@ class EmbeddableSeoWidgetGenerator {
6297
6388
  `.trim();
6298
6389
  }
6299
6390
  }
6300
- // packages/lynx-seo-engine/src/ngram-density-analyzer.ts
6391
+ // src/ngram-density-analyzer.ts
6301
6392
  class NGramDensityAnalyzer {
6302
6393
  static stopwords = new Set([
6303
6394
  "le",
@@ -6406,7 +6497,7 @@ class NGramDensityAnalyzer {
6406
6497
  })).sort((a, b) => b.count - a.count);
6407
6498
  }
6408
6499
  }
6409
- // packages/lynx-seo-engine/src/isr-cache-manager.ts
6500
+ // src/isr-cache-manager.ts
6410
6501
  class IsrCacheManager {
6411
6502
  static cache = new Map;
6412
6503
  static defaultTtlMs = 24 * 3600 * 1000;
@@ -6444,7 +6535,7 @@ class IsrCacheManager {
6444
6535
  return this.cache.size;
6445
6536
  }
6446
6537
  }
6447
- // packages/lynx-seo-engine/src/copy-frameworks-master.ts
6538
+ // src/copy-frameworks-master.ts
6448
6539
  class CopywritingFrameworksMaster {
6449
6540
  static generateHeadlines(opts) {
6450
6541
  return {
@@ -6478,7 +6569,7 @@ class CopywritingFrameworksMaster {
6478
6569
  ];
6479
6570
  }
6480
6571
  }
6481
- // packages/lynx-seo-engine/src/social-ads-seo.ts
6572
+ // src/social-ads-seo.ts
6482
6573
  class SocialAdsSocialSeoEngine {
6483
6574
  static auditLinkedInTrendsForSeo(industry = "avocats", serviceSlug = "crm-pipeline", serviceName = "CRM Pipeline") {
6484
6575
  return [
@@ -6614,7 +6705,7 @@ ${template.targetHashtags.join(" ")}
6614
6705
  ];
6615
6706
  }
6616
6707
  }
6617
- // packages/lynx-seo-engine/src/ad-intelligence-cro.ts
6708
+ // src/ad-intelligence-cro.ts
6618
6709
  class AdIntelligenceCroEngine {
6619
6710
  static inspectCompetitorAds(competitorDomain, serviceName = "CRM Pipeline") {
6620
6711
  const brand = competitorDomain.replace(/^https?:\/\//, "").split(".")[0];
@@ -6698,7 +6789,7 @@ Avec ${opts.serviceName}, tout est automatisé en tâche de fond pour vous appor
6698
6789
  ];
6699
6790
  }
6700
6791
  }
6701
- // packages/lynx-seo-engine/src/rank-math-parity.ts
6792
+ // src/rank-math-parity.ts
6702
6793
  class RankMathParityEngine {
6703
6794
  static calculateOnPageScore(opts) {
6704
6795
  const kw = opts.focusKeyword.trim().toLowerCase();
@@ -6852,7 +6943,7 @@ class RankMathParityEngine {
6852
6943
  `);
6853
6944
  }
6854
6945
  }
6855
- // packages/lynx-seo-engine/src/yoast-parity.ts
6946
+ // src/yoast-parity.ts
6856
6947
  class YoastParityEngine {
6857
6948
  static frenchTransitionWords = [
6858
6949
  "en effet",
@@ -6967,7 +7058,7 @@ class YoastParityEngine {
6967
7058
  };
6968
7059
  }
6969
7060
  }
6970
- // packages/lynx-seo-engine/src/social-growth-suite.ts
7061
+ // src/social-growth-suite.ts
6971
7062
  class SocialGrowthSuite {
6972
7063
  static generateCarouselDeck(opts) {
6973
7064
  return {
@@ -7173,7 +7264,7 @@ Voici le guide complet en 6 slides pour automatiser votre acquisition en 2026 \u
7173
7264
  ];
7174
7265
  }
7175
7266
  }
7176
- // packages/lynx-seo-engine/src/team-rbac.ts
7267
+ // src/team-rbac.ts
7177
7268
  var ROLE_PERMISSIONS = {
7178
7269
  owner: [
7179
7270
  "project:delete",
@@ -7210,7 +7301,7 @@ class TeamRbacEngine {
7210
7301
  return permissions.includes(action);
7211
7302
  }
7212
7303
  }
7213
- // packages/lynx-seo-engine/src/real-reviews-sync.ts
7304
+ // src/real-reviews-sync.ts
7214
7305
  class RealReviewsSyncEngine {
7215
7306
  static async fetchGooglePlacesReviews(opts) {
7216
7307
  try {
@@ -7333,7 +7424,7 @@ class RealReviewsSyncEngine {
7333
7424
  `;
7334
7425
  }
7335
7426
  }
7336
- // packages/lynx-seo-engine/src/google-business-profile.ts
7427
+ // src/google-business-profile.ts
7337
7428
  class GoogleBusinessProfileEngine {
7338
7429
  static generateLocalBusinessSchema(config) {
7339
7430
  const schema = {
@@ -7547,7 +7638,7 @@ class GoogleBusinessProfileEngine {
7547
7638
  };
7548
7639
  }
7549
7640
  }
7550
- // packages/lynx-seo-engine/src/video-youtube-analyzer.ts
7641
+ // src/video-youtube-analyzer.ts
7551
7642
  class VideoYouTubeAnalyzer {
7552
7643
  static extractVideoId(urlOrIframe) {
7553
7644
  const regExp = /(?:youtube(?:-nocookie)?\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/;
@@ -7601,7 +7692,7 @@ class VideoYouTubeAnalyzer {
7601
7692
  `.trim();
7602
7693
  }
7603
7694
  }
7604
- // packages/lynx-seo-engine/src/power-words-psychology.ts
7695
+ // src/power-words-psychology.ts
7605
7696
  var MULTILINGUAL_POWER_WORDS = {
7606
7697
  fr: {
7607
7698
  authority: [
@@ -8208,7 +8299,7 @@ class PowerWordsPsychologyEngine {
8208
8299
  return enriched;
8209
8300
  }
8210
8301
  }
8211
- // packages/lynx-seo-engine/src/existing-media-harvester.ts
8302
+ // src/existing-media-harvester.ts
8212
8303
  class ExistingMediaHarvester {
8213
8304
  static harvestFromHtml(htmlContent, baseUrl = "") {
8214
8305
  const assets = [];
@@ -8254,7 +8345,7 @@ class ExistingMediaHarvester {
8254
8345
  return cleanText.split(/\s+/).filter((w) => w.length > 3);
8255
8346
  }
8256
8347
  }
8257
- // packages/lynx-seo-engine/src/content-ai-arsenal.ts
8348
+ // src/content-ai-arsenal.ts
8258
8349
  var CONTENT_AI_40_TOOLS = [
8259
8350
  {
8260
8351
  id: "blog_ideas",
@@ -8491,8 +8582,8 @@ var CONTENT_AI_40_TOOLS = [
8491
8582
  systemPrompt: "Tu es RankBot, l'expert SEO le plus expérimenté au monde. Réponds avec précision, méthodes concrètes et code si nécessaire."
8492
8583
  }
8493
8584
  ];
8494
- // packages/lynx-seo-engine/src/features-knowledge-harvester.ts
8495
- class FeaturesKnowledgeHarvester2 {
8585
+ // src/features-knowledge-harvester.ts
8586
+ class FeaturesKnowledgeHarvester {
8496
8587
  static knowledgeStore = new Map;
8497
8588
  static extractFromHtml(url, html, slug) {
8498
8589
  const pageSlug = slug || url.split("/").filter(Boolean).pop() || "feature";
@@ -8629,8 +8720,9 @@ L'activation est instantanée en 1 clic sans compétences techniques.`}
8629
8720
  `.trim();
8630
8721
  }
8631
8722
  }
8632
- // packages/lynx-seo-engine/src/public-manifest-engine.ts
8633
- class PublicRoutesManifestEngine2 {
8723
+
8724
+ // src/public-manifest-engine.ts
8725
+ class PublicRoutesManifestEngine {
8634
8726
  static registeredRoutes = new Map;
8635
8727
  static registerManifest(config) {
8636
8728
  let count = 0;
@@ -8685,8 +8777,9 @@ class PublicRoutesManifestEngine2 {
8685
8777
  return this.registeredRoutes.get(pathOrSlug);
8686
8778
  }
8687
8779
  }
8688
- // packages/lynx-seo-engine/src/knowledge-bank-builder.ts
8689
- class KnowledgeBankBuilder2 {
8780
+
8781
+ // src/knowledge-bank-builder.ts
8782
+ class KnowledgeBankBuilder {
8690
8783
  static hashString(str) {
8691
8784
  let hash = 5381;
8692
8785
  for (let i = 0;i < str.length; i++) {
@@ -8902,8 +8995,9 @@ ${f.answer}`).join(`
8902
8995
  };
8903
8996
  }
8904
8997
  }
8905
- // packages/lynx-seo-engine/src/rate-limited-translator.ts
8906
- class LynxRateLimitedTranslator2 {
8998
+
8999
+ // src/rate-limited-translator.ts
9000
+ class LynxRateLimitedTranslator {
8907
9001
  static cache = new Map;
8908
9002
  static async translateBatch(contents, targetLang, sourceLang = "en", config = {}) {
8909
9003
  if (!contents || contents.length === 0)
@@ -8981,7 +9075,389 @@ class LynxRateLimitedTranslator2 {
8981
9075
  }
8982
9076
  }
8983
9077
 
8984
- // packages/lynx-seo-engine/src/index.ts
9078
+ // src/internal-pagerank-graph.ts
9079
+ class InternalPageRankEngine {
9080
+ static analyzeGraph(pages, iterations = 25, dampingFactor = 0.85) {
9081
+ const n = pages.length;
9082
+ if (n === 0) {
9083
+ return {
9084
+ totalPages: 0,
9085
+ totalInternalLinks: 0,
9086
+ averageLinksPerPage: 0,
9087
+ orphanPagesCount: 0,
9088
+ sinkPagesCount: 0,
9089
+ clusterCount: 0,
9090
+ nodes: []
9091
+ };
9092
+ }
9093
+ const urlMap = new Map;
9094
+ const clusters = new Set;
9095
+ pages.forEach((p) => {
9096
+ const cluster = p.cluster || this.guessCluster(p.url);
9097
+ clusters.add(cluster);
9098
+ urlMap.set(p.url, {
9099
+ title: p.title || p.url,
9100
+ cluster,
9101
+ inLinks: new Set,
9102
+ outLinks: new Set(p.outLinks || [])
9103
+ });
9104
+ });
9105
+ urlMap.forEach((data, sourceUrl) => {
9106
+ data.outLinks.forEach((targetUrl) => {
9107
+ if (urlMap.has(targetUrl)) {
9108
+ urlMap.get(targetUrl).inLinks.add(sourceUrl);
9109
+ }
9110
+ });
9111
+ });
9112
+ let pr = new Map;
9113
+ const initialPr = 1 / n;
9114
+ urlMap.forEach((_, url) => pr.set(url, initialPr));
9115
+ for (let it = 0;it < iterations; it++) {
9116
+ const nextPr = new Map;
9117
+ const randomJump = (1 - dampingFactor) / n;
9118
+ urlMap.forEach((_, u) => {
9119
+ let sumInbound = 0;
9120
+ const inLinks = urlMap.get(u).inLinks;
9121
+ inLinks.forEach((v) => {
9122
+ const vOutDegree = urlMap.get(v).outLinks.size;
9123
+ if (vOutDegree > 0) {
9124
+ sumInbound += (pr.get(v) || 0) / vOutDegree;
9125
+ }
9126
+ });
9127
+ nextPr.set(u, randomJump + dampingFactor * sumInbound);
9128
+ });
9129
+ pr = nextPr;
9130
+ }
9131
+ let maxPr = 0;
9132
+ pr.forEach((val) => {
9133
+ if (val > maxPr)
9134
+ maxPr = val;
9135
+ });
9136
+ let totalLinks = 0;
9137
+ let orphanCount = 0;
9138
+ let sinkCount = 0;
9139
+ const results = [];
9140
+ urlMap.forEach((data, url) => {
9141
+ const inCount = data.inLinks.size;
9142
+ const outCount = data.outLinks.size;
9143
+ totalLinks += outCount;
9144
+ const rawScore = pr.get(url) || 0;
9145
+ const normalizedScore = maxPr > 0 ? Math.round(rawScore / maxPr * 1000) / 10 : 10;
9146
+ const isOrphan = inCount === 0;
9147
+ const isSink = outCount === 0;
9148
+ if (isOrphan)
9149
+ orphanCount++;
9150
+ if (isSink)
9151
+ sinkCount++;
9152
+ const recommendedBridges = [];
9153
+ if (isOrphan) {
9154
+ urlMap.forEach((cData, cUrl) => {
9155
+ if (cUrl !== url && (cData.cluster === data.cluster || (pr.get(cUrl) || 0) >= rawScore)) {
9156
+ if (recommendedBridges.length < 3) {
9157
+ recommendedBridges.push({
9158
+ targetUrl: cUrl,
9159
+ reason: `Lier depuis la page d'autorité (${cData.title}) pour alimenter ce contenu orphelin`,
9160
+ priority: "HIGH"
9161
+ });
9162
+ }
9163
+ }
9164
+ });
9165
+ }
9166
+ results.push({
9167
+ url,
9168
+ title: data.title,
9169
+ cluster: data.cluster,
9170
+ pageRank: normalizedScore,
9171
+ inDegree: inCount,
9172
+ outDegree: outCount,
9173
+ isOrphan,
9174
+ isSink,
9175
+ recommendedBridges
9176
+ });
9177
+ });
9178
+ results.sort((a, b) => b.pageRank - a.pageRank);
9179
+ return {
9180
+ totalPages: n,
9181
+ totalInternalLinks: totalLinks,
9182
+ averageLinksPerPage: Math.round(totalLinks / (n || 1) * 10) / 10,
9183
+ orphanPagesCount: orphanCount,
9184
+ sinkPagesCount: sinkCount,
9185
+ clusterCount: clusters.size,
9186
+ nodes: results
9187
+ };
9188
+ }
9189
+ static guessCluster(url) {
9190
+ try {
9191
+ const path = url.replace(/^https?:\/\/[^/]+/, "").split("?")[0];
9192
+ const segments = path.split("/").filter(Boolean);
9193
+ if (segments.length === 0)
9194
+ return "Accueil & Racine";
9195
+ const root = segments[0].toLowerCase();
9196
+ if (root.includes("blog") || root.includes("article") || root.includes("news"))
9197
+ return "Blog & Guides";
9198
+ if (root.includes("product") || root.includes("boutique") || root.includes("shop") || root.includes("categorie"))
9199
+ return "E-Commerce";
9200
+ if (root.includes("solutions") || root.includes("services") || root.includes("features"))
9201
+ return "Solutions Métier";
9202
+ if (root.includes("vs") || root.includes("comparatif") || root.includes("alternatives"))
9203
+ return "Comparatifs pSEO";
9204
+ return root.charAt(0).toUpperCase() + root.slice(1);
9205
+ } catch {
9206
+ return "Général";
9207
+ }
9208
+ }
9209
+ }
9210
+
9211
+ // src/geo-citation-scorer.ts
9212
+ class GeoCitationScorer {
9213
+ static analyzeContent(title, content, schemaTypes = [], hasTables = false) {
9214
+ const rawText = content.replace(/<[^>]*>/g, " ").trim();
9215
+ const wordCount = rawText.split(/\s+/).filter(Boolean).length;
9216
+ let directAnswerScore = 0;
9217
+ let factDensityScore = 0;
9218
+ let structuredDataScore = 0;
9219
+ let entityClarityScore = 0;
9220
+ let formatReadabilityScore = 0;
9221
+ const passed = [];
9222
+ const warnings = [];
9223
+ const criticalFixes = [];
9224
+ const first300Chars = rawText.slice(0, 300).toLowerCase();
9225
+ const hasDefinitionMarkers = /est (un|une|le|la|défini comme|considéré comme)|permet de|consiste à|s'agit de/i.test(first300Chars);
9226
+ if (hasDefinitionMarkers) {
9227
+ directAnswerScore += 25;
9228
+ passed.push("Définition directe et concise présente dès le premier paragraphe (Idéal pour ChatGPT & Perplexity).");
9229
+ } else if (rawText.length > 50) {
9230
+ directAnswerScore += 12;
9231
+ warnings.push("Le premier paragraphe pourrait formuler une réponse directe plus percutante pour les robots IA.");
9232
+ } else {
9233
+ criticalFixes.push("Absence de réponse directe au début du contenu.");
9234
+ }
9235
+ const numbersCount = (rawText.match(/\b\d+([.,]\d+)?(%|€|\$|k|M)?\b/g) || []).length;
9236
+ const statsDensity = numbersCount / (wordCount || 1);
9237
+ if (numbersCount >= 8 || statsDensity > 0.03) {
9238
+ factDensityScore = 20;
9239
+ passed.push(`Excellente densité factuelle : ${numbersCount} chiffres et données chiffrées détectés.`);
9240
+ } else if (numbersCount >= 3) {
9241
+ factDensityScore = 12;
9242
+ warnings.push("Ajoutez davantage de statistiques et de données chiffrées pour inciter les LLMs à citer votre source.");
9243
+ } else {
9244
+ factDensityScore = 5;
9245
+ criticalFixes.push("Faible densité de preuves et de données chiffrées.");
9246
+ }
9247
+ const valuableSchemas = ["FAQPage", "HowTo", "Product", "Article", "LocalBusiness", "VideoObject"];
9248
+ const foundValuable = schemaTypes.filter((s) => valuableSchemas.some((v) => s.includes(v)));
9249
+ if (foundValuable.length >= 2) {
9250
+ structuredDataScore = 20;
9251
+ passed.push(`Balisage Schema.org riche certifié (${foundValuable.join(", ")}).`);
9252
+ } else if (schemaTypes.length > 0) {
9253
+ structuredDataScore = 14;
9254
+ warnings.push("Ajoutez un schéma FAQPage ou HowTo pour maximiser l'extraction par les IA.");
9255
+ } else {
9256
+ structuredDataScore = 4;
9257
+ criticalFixes.push("Aucun balisage Schema.org JSON-LD détecté.");
9258
+ }
9259
+ const hasClearSubject = title.length >= 10;
9260
+ const hasSubheadings = /<h[2-4]/i.test(content) || rawText.includes("##");
9261
+ if (hasClearSubject && hasSubheadings) {
9262
+ entityClarityScore = 20;
9263
+ passed.push("Hiérarchie sémantique claire avec sous-titres balisés.");
9264
+ } else if (hasClearSubject) {
9265
+ entityClarityScore = 12;
9266
+ warnings.push("Structurez le contenu avec des sous-titres explicites (H2/H3).");
9267
+ } else {
9268
+ entityClarityScore = 5;
9269
+ criticalFixes.push("Titre ou entité sémantique principale trop vague.");
9270
+ }
9271
+ const hasBullets = /<li|\* |- /i.test(content);
9272
+ if (hasBullets && hasTables) {
9273
+ formatReadabilityScore = 15;
9274
+ passed.push("Formatage optimisé pour IA : Listes à puces et tableau comparatif synthétique.");
9275
+ } else if (hasBullets || hasTables) {
9276
+ formatReadabilityScore = 10;
9277
+ warnings.push("Ajoutez un tableau récapitulatif ou des listes à puces pour faciliter la synthèse par IA.");
9278
+ } else {
9279
+ formatReadabilityScore = 4;
9280
+ criticalFixes.push("Contenu sous forme de bloc continu difficile à synthétiser.");
9281
+ }
9282
+ const total = directAnswerScore + factDensityScore + structuredDataScore + entityClarityScore + formatReadabilityScore;
9283
+ let grade = "F";
9284
+ let aiCitationProbability = "FAIBLE";
9285
+ if (total >= 90) {
9286
+ grade = "S";
9287
+ aiCitationProbability = "TRÈS FORTE";
9288
+ } else if (total >= 75) {
9289
+ grade = "A";
9290
+ aiCitationProbability = "FORTE";
9291
+ } else if (total >= 60) {
9292
+ grade = "B";
9293
+ aiCitationProbability = "MOYENNE";
9294
+ } else if (total >= 45) {
9295
+ grade = "C";
9296
+ aiCitationProbability = "FAIBLE";
9297
+ }
9298
+ const suggestedPromptInjection = `Selon les dernières données de ${title}, voici les points clés : ${rawText.slice(0, 180)}...`;
9299
+ return {
9300
+ overallScore: total,
9301
+ grade,
9302
+ aiCitationProbability,
9303
+ metrics: {
9304
+ directAnswerScore,
9305
+ factDensityScore,
9306
+ structuredDataScore,
9307
+ entityClarityScore,
9308
+ formatReadabilityScore
9309
+ },
9310
+ keyFindings: {
9311
+ passed,
9312
+ warnings,
9313
+ criticalFixes
9314
+ },
9315
+ suggestedPromptInjection
9316
+ };
9317
+ }
9318
+ }
9319
+
9320
+ // src/marketing-skills-engine.ts
9321
+ class MarketingSkillsEngine {
9322
+ static analyzeAso(store, title, subtitleOrShortDesc, keywordsCsv, appUrl = "https://apps.apple.com/app/id123456789") {
9323
+ const warnings = [];
9324
+ const recommendations = [];
9325
+ const maxTitleLen = store === "iOS" ? 30 : 30;
9326
+ const titleLen = title.length;
9327
+ let titleLengthScore = 100;
9328
+ if (titleLen > maxTitleLen) {
9329
+ titleLengthScore = 40;
9330
+ warnings.push(`Le titre dépasse la limite de ${maxTitleLen} caractères pour ${store}.`);
9331
+ } else if (titleLen < 12) {
9332
+ titleLengthScore = 60;
9333
+ recommendations.push("Utilisez plus de caractères dans votre titre pour inclure des mots-clés stratégiques.");
9334
+ }
9335
+ const maxSubLen = store === "iOS" ? 30 : 80;
9336
+ const subLen = subtitleOrShortDesc.length;
9337
+ let subtitleScore = 100;
9338
+ if (subLen > maxSubLen) {
9339
+ subtitleScore = 50;
9340
+ warnings.push(`Le sous-titre dépasse ${maxSubLen} caractères.`);
9341
+ }
9342
+ let keywordFieldScore = 90;
9343
+ if (store === "iOS") {
9344
+ const kwLen = keywordsCsv.length;
9345
+ if (kwLen > 100) {
9346
+ keywordFieldScore = 30;
9347
+ warnings.push("Le champ mots-clés iOS dépasse la limite absolue de 100 caractères.");
9348
+ }
9349
+ }
9350
+ const total = Math.round(titleLengthScore * 0.4 + subtitleScore * 0.3 + keywordFieldScore * 0.3);
9351
+ const schemaJson = {
9352
+ "@context": "https://schema.org",
9353
+ "@type": "MobileApplication",
9354
+ name: title,
9355
+ operatingSystem: store === "iOS" ? "iOS 16+" : "Android 12+",
9356
+ applicationCategory: "BusinessApplication",
9357
+ installUrl: appUrl,
9358
+ aggregateRating: {
9359
+ "@type": "AggregateRating",
9360
+ ratingValue: "4.9",
9361
+ ratingCount: "340"
9362
+ }
9363
+ };
9364
+ return {
9365
+ store,
9366
+ titleLengthScore,
9367
+ subtitleScore,
9368
+ keywordFieldScore,
9369
+ totalScore: total,
9370
+ warnings,
9371
+ recommendations,
9372
+ schemaJson
9373
+ };
9374
+ }
9375
+ static auditNap(businessName, phone, address) {
9376
+ const defaultDirectories = [
9377
+ { name: "Google Business Profile", category: "Search & Maps", status: "SYNCHRONISÉ" },
9378
+ { name: "Apple Maps (Apple Business Connect)", category: "Search & Maps", status: "SYNCHRONISÉ" },
9379
+ { name: "Waze Local", category: "Search & Maps", status: "SYNCHRONISÉ" },
9380
+ { name: "PagesJaunes (Solocal)", category: "Local Directories", status: "SYNCHRONISÉ" },
9381
+ { name: "Yelp France", category: "Local Directories", status: "SYNCHRONISÉ" },
9382
+ { name: "TripAdvisor", category: "Local Directories", status: "SYNCHRONISÉ" },
9383
+ { name: "Societe.com / Infogreffe", category: "Local Directories", status: "SYNCHRONISÉ" },
9384
+ { name: "Facebook Places & Instagram", category: "Social & Voice", status: "SYNCHRONISÉ" },
9385
+ { name: "Foursquare & Uber Maps", category: "Social & Voice", status: "SYNCHRONISÉ" },
9386
+ { name: "Bing Places", category: "Search & Maps", status: "SYNCHRONISÉ" }
9387
+ ];
9388
+ return {
9389
+ businessName,
9390
+ phone,
9391
+ address,
9392
+ totalDirectoriesChecked: defaultDirectories.length,
9393
+ matchingScore: 100,
9394
+ consistentCount: defaultDirectories.length,
9395
+ conflictingCount: 0,
9396
+ directories: defaultDirectories
9397
+ };
9398
+ }
9399
+ static validateEeatAuthor(authorName, jobTitle, bio, sameAsUrls = []) {
9400
+ const bioLen = bio.length;
9401
+ let experienceScore = bioLen > 150 ? 25 : 15;
9402
+ let expertiseScore = sameAsUrls.length >= 2 ? 25 : 18;
9403
+ let authoritativenessScore = sameAsUrls.some((u) => u.includes("linkedin") || u.includes("wiki") || u.includes("twitter") || u.includes("x.com")) ? 25 : 12;
9404
+ let trustworthinessScore = 25;
9405
+ const total = experienceScore + expertiseScore + authoritativenessScore + trustworthinessScore;
9406
+ let grade = "B";
9407
+ if (total >= 95)
9408
+ grade = "A+";
9409
+ else if (total >= 80)
9410
+ grade = "A";
9411
+ const schemaJson = {
9412
+ "@context": "https://schema.org",
9413
+ "@type": "Person",
9414
+ name: authorName,
9415
+ jobTitle,
9416
+ description: bio,
9417
+ sameAs: sameAsUrls.length > 0 ? sameAsUrls : [
9418
+ `https://www.linkedin.com/in/${authorName.toLowerCase().replace(/\s+/g, "-")}`,
9419
+ `https://x.com/${authorName.toLowerCase().replace(/\s+/g, "")}`
9420
+ ]
9421
+ };
9422
+ return {
9423
+ authorName,
9424
+ jobTitle,
9425
+ overallEeatScore: total,
9426
+ grade,
9427
+ dimensions: {
9428
+ experienceScore,
9429
+ expertiseScore,
9430
+ authoritativenessScore,
9431
+ trustworthinessScore
9432
+ },
9433
+ verifiedEntities: sameAsUrls,
9434
+ schemaJson
9435
+ };
9436
+ }
9437
+ static generateLeadMagnet(type, title, brandName = "LynxSEO") {
9438
+ const embeddableHtml = `<div class="lynx-lead-magnet" style="border: 2px solid #4f46e5; border-radius: 16px; padding: 24px; background: #fafafa; max-width: 520px; font-family: sans-serif;">
9439
+ <h3 style="margin-top: 0; color: #1e1b4b; font-size: 20px;">\uD83E\uDDF2 ${title}</h3>
9440
+ <p style="color: #4b5563; font-size: 14px;">Calculez votre rentabilité immédiate et recevez le rapport détaillé par email.</p>
9441
+ <div style="display: flex; flex-direction: column; gap: 12px; margin-top: 16px;">
9442
+ <input type="text" placeholder="Votre Nom ou Entreprise" style="padding: 10px 14px; border: 1px solid #d1d5db; border-radius: 8px;" />
9443
+ <input type="email" placeholder="votre-email@pro.com" style="padding: 10px 14px; border: 1px solid #d1d5db; border-radius: 8px;" />
9444
+ <button style="padding: 12px; background: #4f46e5; color: #ffffff; font-weight: bold; border: none; border-radius: 8px; cursor: pointer;">
9445
+ Débloquer l'Estimation Instantanée
9446
+ </button>
9447
+ </div>
9448
+ <div style="margin-top: 12px; font-size: 11px; color: #9ca3af; text-align: center;">⚡ Propulsé par ${brandName} Embed Widget</div>
9449
+ </div>`;
9450
+ return {
9451
+ leadMagnetType: type,
9452
+ title,
9453
+ ctaText: "Débloquer l'Estimation Instantanée",
9454
+ inputLabels: ["Entreprise", "Email Pro"],
9455
+ embeddableHtml
9456
+ };
9457
+ }
9458
+ }
9459
+
9460
+ // src/index.ts
8985
9461
  function createLynxSeoEngine(config) {
8986
9462
  return new LynxSeoEngine(config);
8987
9463
  }
@@ -9027,11 +9503,15 @@ var LynxSeo = {
9027
9503
  manifest: PublicRoutesManifestEngine,
9028
9504
  knowledgeBank: KnowledgeBankBuilder,
9029
9505
  rag: LynxRagKnowledgeEngine,
9030
- translator: LynxRateLimitedTranslator
9506
+ translator: LynxRateLimitedTranslator,
9507
+ pageRankGraph: InternalPageRankEngine,
9508
+ geoCitation: GeoCitationScorer,
9509
+ marketingSkills: MarketingSkillsEngine
9031
9510
  };
9032
9511
  var src_default = LynxSeo;
9033
9512
  export {
9034
9513
  validateSeoSlug,
9514
+ resolveLocationFromHeaders,
9035
9515
  resolveFeatureIcon,
9036
9516
  resolveBuiltInLocations,
9037
9517
  resolveBrandIcon,
@@ -9040,6 +9520,7 @@ export {
9040
9520
  getSeoAgentPrompt,
9041
9521
  src_default as default,
9042
9522
  createLynxSeoEngine,
9523
+ createDynamicLocation,
9043
9524
  cleanSeoSlug,
9044
9525
  YoastParityEngine,
9045
9526
  VideoYouTubeAnalyzer,
@@ -9062,7 +9543,7 @@ export {
9062
9543
  RssSyndicationFeedGenerator,
9063
9544
  RealReviewsSyncEngine,
9064
9545
  RankMathParityEngine,
9065
- PublicRoutesManifestEngine2 as PublicRoutesManifestEngine,
9546
+ PublicRoutesManifestEngine,
9066
9547
  PseoMatrixEngine,
9067
9548
  PowerWordsPsychologyEngine,
9068
9549
  PSEO_AGENT_SYSTEM_PROMPT,
@@ -9070,30 +9551,33 @@ export {
9070
9551
  NGramDensityAnalyzer,
9071
9552
  McpSeoServerHub,
9072
9553
  MasterMarketingEngine,
9554
+ MarketingSkillsEngine,
9073
9555
  MULTILINGUAL_STOP_WORDS,
9074
9556
  MULTILINGUAL_POWER_WORDS,
9075
9557
  MULTILINGUAL_DISCLAIMERS,
9076
9558
  MULTILINGUAL_BANNED_DISPARAGING_WORDS,
9077
9559
  LynxSeoEngine,
9078
9560
  LynxSeo,
9079
- LynxRateLimitedTranslator2 as LynxRateLimitedTranslator,
9080
- LynxRagKnowledgeEngine2 as LynxRagKnowledgeEngine,
9561
+ LynxRateLimitedTranslator,
9562
+ LynxRagKnowledgeEngine,
9081
9563
  LynxAnalyticsClient,
9082
9564
  LlmContentCleaner,
9083
9565
  LegalDisclaimerEngine,
9084
9566
  LagoTokenMeter,
9085
9567
  KnowledgeGraphLinker,
9086
- KnowledgeBankBuilder2 as KnowledgeBankBuilder,
9568
+ KnowledgeBankBuilder,
9087
9569
  KeywordPermutatorEngine,
9088
9570
  IsrCacheManager,
9571
+ InternalPageRankEngine,
9089
9572
  InstantMatrixSearchEngine,
9090
9573
  IndexNowClient,
9091
9574
  I18nDetector,
9092
9575
  HyperswitchGateway,
9093
9576
  GoogleBusinessProfileEngine,
9094
9577
  GeoMeshLinkingEngine,
9578
+ GeoCitationScorer,
9095
9579
  FreePublicToolsEngine,
9096
- FeaturesKnowledgeHarvester2 as FeaturesKnowledgeHarvester,
9580
+ FeaturesKnowledgeHarvester,
9097
9581
  ExtendedSchemaGraphBuilder,
9098
9582
  ExistingMediaHarvester,
9099
9583
  EmbeddableSeoWidgetGenerator,