@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.
- package/README.md +60 -117
- package/dist/ai-copilot-client.d.ts +2 -0
- package/dist/built-in-locations.d.ts +22 -0
- package/dist/content-ai-arsenal.d.ts +28 -0
- package/dist/existing-media-harvester.d.ts +33 -0
- package/dist/extended-schemas.d.ts +84 -0
- package/dist/features-knowledge-harvester.d.ts +56 -0
- package/dist/geo-citation-scorer.d.ts +34 -0
- package/dist/i18n-detector.d.ts +1 -1
- package/dist/index.d.ts +32 -7
- package/dist/index.js +567 -83
- package/dist/index.mjs +2062 -169
- package/dist/internal-pagerank-graph.d.ts +59 -0
- package/dist/knowledge-bank-builder.d.ts +91 -0
- package/dist/marketing-skills-engine.d.ts +74 -0
- package/dist/matrix-engine.d.ts +29 -0
- package/dist/power-words-psychology.d.ts +27 -15
- package/dist/public-manifest-engine.d.ts +58 -0
- package/dist/rag-knowledge-engine.d.ts +50 -0
- package/dist/rate-limited-translator.d.ts +19 -0
- package/dist/token-quota-manager.d.ts +4 -0
- package/package.json +11 -3
- package/TUTORIEL_INTEGRATION_SITE.md +0 -283
- package/connectors/cloudflare-worker/worker.js +0 -106
- package/connectors/laravel/LynxSeoController.php +0 -37
- package/connectors/wordpress/lynxseo-admin-app.js +0 -2611
- package/connectors/wordpress/lynxseo-connector.php +0 -1712
- package/connectors/wordpress/lynxseo-connector.zip +0 -0
- package/lynxflow-seo-engine-1.2.0.tgz +0 -0
- package/src/ad-intelligence-cro.ts +0 -140
- package/src/ai-bots-log-analyzer.ts +0 -127
- package/src/ai-copilot-client.ts +0 -99
- package/src/analytics-client.ts +0 -315
- package/src/auth-key.ts +0 -262
- package/src/backlinks-client.ts +0 -85
- package/src/brand-dna-calendar.ts +0 -120
- package/src/brand-icons.ts +0 -138
- package/src/built-in-locations.ts +0 -170
- package/src/content-ai-arsenal.ts +0 -268
- package/src/copy-frameworks-master.ts +0 -89
- package/src/cro-copywriting-engine.ts +0 -105
- package/src/crosslink-scorer.ts +0 -101
- package/src/embeddable-seo-widget.ts +0 -57
- package/src/engine.test.ts +0 -386
- package/src/engine.ts +0 -813
- package/src/existing-media-harvester.ts +0 -98
- package/src/extended-schemas.ts +0 -589
- package/src/features-knowledge-harvester.ts +0 -190
- package/src/free-public-tools.ts +0 -224
- package/src/geo-mesh-linking.ts +0 -93
- package/src/google-business-profile.ts +0 -280
- package/src/i18n-detector.ts +0 -98
- package/src/i18n-dictionary.ts +0 -422
- package/src/index.ts +0 -164
- package/src/indexnow-client.ts +0 -94
- package/src/instant-matrix-search.ts +0 -48
- package/src/isr-cache-manager.ts +0 -78
- package/src/keyword-permutator.ts +0 -118
- package/src/knowledge-bank-builder.ts +0 -299
- package/src/knowledge-graph-linker.ts +0 -81
- package/src/lago-token-meter.ts +0 -120
- package/src/legal-disclaimers.ts +0 -407
- package/src/llm-content-cleaner.ts +0 -64
- package/src/llm-prompt.ts +0 -323
- package/src/master-marketing-engine.ts +0 -314
- package/src/matrix-engine.ts +0 -1204
- package/src/mcp-seo-server.ts +0 -108
- package/src/ngram-density-analyzer.ts +0 -108
- package/src/og-image-generator.ts +0 -109
- package/src/power-words-psychology.ts +0 -369
- package/src/public-manifest-engine.ts +0 -109
- package/src/rag-knowledge-engine.ts +0 -163
- package/src/rank-math-parity.ts +0 -235
- package/src/rate-limited-translator.ts +0 -110
- package/src/real-reviews-sync.ts +0 -207
- package/src/rss-syndication-feed.ts +0 -56
- package/src/schema-builder.ts +0 -134
- package/src/seo-opportunities-decay.ts +0 -154
- package/src/serp-client.ts +0 -89
- package/src/serp-history-alerts.ts +0 -75
- package/src/site-auditor.ts +0 -83
- package/src/site-crawler.ts +0 -406
- package/src/slug-engine.ts +0 -183
- package/src/social-ads-seo.ts +0 -211
- package/src/social-growth-suite.ts +0 -310
- package/src/social-trend-seo.ts +0 -103
- package/src/social-video-seo.ts +0 -100
- package/src/team-rbac.ts +0 -61
- package/src/technical-rules-auditor.ts +0 -171
- package/src/token-quota-manager.ts +0 -91
- package/src/types.ts +0 -143
- package/src/ui-icons.ts +0 -134
- package/src/urlytics-engine.ts +0 -97
- package/src/video-youtube-analyzer.ts +0 -81
- package/src/yoast-parity.ts +0 -161
- package/tsconfig.json +0 -15
package/src/social-video-seo.ts
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🎬 Video & Social SEO Analytics Engine (YouTube, TikTok, Instagram Reels)
|
|
3
|
-
*
|
|
4
|
-
* Tracks video rankings in Google Video Carousels and YouTube/TikTok Search,
|
|
5
|
-
* measures search attribution, and generates Google-compliant Schema.org `VideoObject` graphs.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export interface TrackedVideoRecord {
|
|
9
|
-
id: string;
|
|
10
|
-
platform: "youtube" | "tiktok" | "instagram-reels";
|
|
11
|
-
title: string;
|
|
12
|
-
videoUrl: string;
|
|
13
|
-
thumbnailUrl: string;
|
|
14
|
-
views: number;
|
|
15
|
-
likes: number;
|
|
16
|
-
shares: number;
|
|
17
|
-
commentsCount: number;
|
|
18
|
-
durationSec: number;
|
|
19
|
-
publishedAt: string;
|
|
20
|
-
targetKeywords: string[];
|
|
21
|
-
googleVideoCarouselRank?: number; // e.g. #1 in Google Video Carousel
|
|
22
|
-
youtubeSearchRank?: number;
|
|
23
|
-
tiktokSearchRank?: number;
|
|
24
|
-
associatedLandingPageUrl?: string;
|
|
25
|
-
estimatedSearchVisitsGenerated: number;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface VideoSchemaOpts {
|
|
29
|
-
name: string;
|
|
30
|
-
description: string;
|
|
31
|
-
thumbnailUrl: string;
|
|
32
|
-
uploadDate: string;
|
|
33
|
-
contentUrl: string;
|
|
34
|
-
embedUrl: string;
|
|
35
|
-
durationIso: string; // e.g. "PT1M45S"
|
|
36
|
-
transcript?: string;
|
|
37
|
-
chapters?: { name: string; startOffsetSeconds: number }[];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export class SocialVideoSeoAnalyticsEngine {
|
|
41
|
-
/**
|
|
42
|
-
* Generates a Google-verified Schema.org `VideoObject` with chapters (Clip / SeekToAction).
|
|
43
|
-
*/
|
|
44
|
-
static buildVideoObjectSchema(opts: VideoSchemaOpts): Record<string, unknown> {
|
|
45
|
-
const schema: Record<string, unknown> = {
|
|
46
|
-
"@context": "https://schema.org",
|
|
47
|
-
"@type": "VideoObject",
|
|
48
|
-
name: opts.name,
|
|
49
|
-
description: opts.description,
|
|
50
|
-
thumbnailUrl: [opts.thumbnailUrl],
|
|
51
|
-
uploadDate: opts.uploadDate,
|
|
52
|
-
contentUrl: opts.contentUrl,
|
|
53
|
-
embedUrl: opts.embedUrl,
|
|
54
|
-
duration: opts.durationIso,
|
|
55
|
-
transcript: opts.transcript,
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
if (opts.chapters && opts.chapters.length > 0) {
|
|
59
|
-
schema.hasPart = opts.chapters.map((ch, idx) => ({
|
|
60
|
-
"@type": "Clip",
|
|
61
|
-
name: ch.name,
|
|
62
|
-
startOffset: ch.startOffsetSeconds,
|
|
63
|
-
endOffset:
|
|
64
|
-
idx < opts.chapters!.length - 1
|
|
65
|
-
? opts.chapters![idx + 1].startOffsetSeconds
|
|
66
|
-
: ch.startOffsetSeconds + 30,
|
|
67
|
-
url: `${opts.contentUrl}?t=${ch.startOffsetSeconds}`,
|
|
68
|
-
}));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return schema;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Analyzes an array of videos to calculate total social reach and search traffic attribution.
|
|
76
|
-
*/
|
|
77
|
-
static calculateVideoPortfolioStats(videos: TrackedVideoRecord[]) {
|
|
78
|
-
const totalViews = videos.reduce((acc, v) => acc + v.views, 0);
|
|
79
|
-
const totalEngagement = videos.reduce((acc, v) => acc + (v.likes + v.shares + v.commentsCount), 0);
|
|
80
|
-
const inGoogleCarouselCount = videos.filter((v) => Boolean(v.googleVideoCarouselRank)).length;
|
|
81
|
-
const totalSearchVisits = videos.reduce((acc, v) => acc + v.estimatedSearchVisitsGenerated, 0);
|
|
82
|
-
|
|
83
|
-
const platformBreakdown = {
|
|
84
|
-
youtube: videos.filter((v) => v.platform === "youtube").length,
|
|
85
|
-
tiktok: videos.filter((v) => v.platform === "tiktok").length,
|
|
86
|
-
instagram: videos.filter((v) => v.platform === "instagram-reels").length,
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
totalVideosTracked: videos.length,
|
|
91
|
-
totalViews,
|
|
92
|
-
totalEngagement,
|
|
93
|
-
avgEngagementRate: totalViews > 0 ? Math.round((totalEngagement / totalViews) * 1000) / 10 : 0,
|
|
94
|
-
inGoogleCarouselCount,
|
|
95
|
-
totalSearchVisits,
|
|
96
|
-
platformBreakdown,
|
|
97
|
-
topPerformingVideos: [...videos].sort((a, b) => b.views - a.views).slice(0, 5),
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
}
|
package/src/team-rbac.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 👥 Team IAM & Multi-User RBAC Engine (Issue #87)
|
|
3
|
-
*
|
|
4
|
-
* Provides granular permissions per team member across SEO projects:
|
|
5
|
-
* - `owner`: Full project control, billing, domain deletion.
|
|
6
|
-
* - `admin`: Add/remove tracked keywords, run crawls, edit AI prompts.
|
|
7
|
-
* - `editor`: View rankings, generate pSEO pages, run audits.
|
|
8
|
-
* - `viewer`: Read-only access to dashboards and CSV exports.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export type TeamRole = "owner" | "admin" | "editor" | "viewer";
|
|
12
|
-
|
|
13
|
-
export type PermissionAction =
|
|
14
|
-
| "project:delete"
|
|
15
|
-
| "project:update"
|
|
16
|
-
| "keywords:add"
|
|
17
|
-
| "keywords:delete"
|
|
18
|
-
| "keywords:export"
|
|
19
|
-
| "audit:run"
|
|
20
|
-
| "pseo:generate"
|
|
21
|
-
| "ai_copilot:execute";
|
|
22
|
-
|
|
23
|
-
const ROLE_PERMISSIONS: Record<TeamRole, PermissionAction[]> = {
|
|
24
|
-
owner: [
|
|
25
|
-
"project:delete",
|
|
26
|
-
"project:update",
|
|
27
|
-
"keywords:add",
|
|
28
|
-
"keywords:delete",
|
|
29
|
-
"keywords:export",
|
|
30
|
-
"audit:run",
|
|
31
|
-
"pseo:generate",
|
|
32
|
-
"ai_copilot:execute",
|
|
33
|
-
],
|
|
34
|
-
admin: [
|
|
35
|
-
"project:update",
|
|
36
|
-
"keywords:add",
|
|
37
|
-
"keywords:delete",
|
|
38
|
-
"keywords:export",
|
|
39
|
-
"audit:run",
|
|
40
|
-
"pseo:generate",
|
|
41
|
-
"ai_copilot:execute",
|
|
42
|
-
],
|
|
43
|
-
editor: [
|
|
44
|
-
"keywords:add",
|
|
45
|
-
"keywords:export",
|
|
46
|
-
"audit:run",
|
|
47
|
-
"pseo:generate",
|
|
48
|
-
"ai_copilot:execute",
|
|
49
|
-
],
|
|
50
|
-
viewer: ["keywords:export"],
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export class TeamRbacEngine {
|
|
54
|
-
/**
|
|
55
|
-
* Checks if a user role has permission to perform an action.
|
|
56
|
-
*/
|
|
57
|
-
static can(role: TeamRole, action: PermissionAction): boolean {
|
|
58
|
-
const permissions = ROLE_PERMISSIONS[role] || [];
|
|
59
|
-
return permissions.includes(action);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🔬 Technical SEO Rules Auditor (Inspiration: seonaut/internal/issues/)
|
|
3
|
-
*
|
|
4
|
-
* Implements strict, production-grade automated checks:
|
|
5
|
-
* 1. Heading Hierarchy Integrity (Missing H1, Multiple H1s, Hierarchy Gaps like H1->H3)
|
|
6
|
-
* 2. Redirect Chain & Loop Detection (301/302 > 1 hop)
|
|
7
|
-
* 3. Thin Content Detection (< 300 words)
|
|
8
|
-
* 4. Canonical URL Auditing (Missing, relative, mismatched)
|
|
9
|
-
* 5. Hreflang Symmetrical Validation (Missing return tags)
|
|
10
|
-
* 6. Image Optimization (Missing Alt text, heavy assets > 100KB)
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export interface PageAuditInput {
|
|
14
|
-
url: string;
|
|
15
|
-
statusCode: number;
|
|
16
|
-
headings: { tag: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; text: string }[];
|
|
17
|
-
wordCount: number;
|
|
18
|
-
canonicalUrl?: string;
|
|
19
|
-
hreflangs?: { lang: string; url: string }[];
|
|
20
|
-
images?: { src: string; alt?: string; sizeBytes?: number }[];
|
|
21
|
-
redirectChain?: { url: string; statusCode: number }[];
|
|
22
|
-
timeToFirstByteMs?: number;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export type IssueSeverity = "critical" | "warning" | "info";
|
|
26
|
-
|
|
27
|
-
export interface TechnicalIssue {
|
|
28
|
-
code: string;
|
|
29
|
-
category: "headings" | "redirects" | "content" | "canonical" | "hreflang" | "images" | "performance";
|
|
30
|
-
severity: IssueSeverity;
|
|
31
|
-
title: string;
|
|
32
|
-
description: string;
|
|
33
|
-
recommendation: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export class TechnicalRulesAuditor {
|
|
37
|
-
/**
|
|
38
|
-
* Audits a single page payload across all technical rules.
|
|
39
|
-
*/
|
|
40
|
-
static auditPage(page: PageAuditInput): TechnicalIssue[] {
|
|
41
|
-
const issues: TechnicalIssue[] = [];
|
|
42
|
-
|
|
43
|
-
// 1. Heading Hierarchy Audit
|
|
44
|
-
const h1s = page.headings.filter((h) => h.tag === "h1");
|
|
45
|
-
if (h1s.length === 0) {
|
|
46
|
-
issues.push({
|
|
47
|
-
code: "HEADING_H1_MISSING",
|
|
48
|
-
category: "headings",
|
|
49
|
-
severity: "critical",
|
|
50
|
-
title: "Balise <h1> manquante",
|
|
51
|
-
description: `Aucune balise <h1> trouvée sur ${page.url}.`,
|
|
52
|
-
recommendation: "Ajoutez exactement une balise <h1> contenant votre mot-clé principal.",
|
|
53
|
-
});
|
|
54
|
-
} else if (h1s.length > 1) {
|
|
55
|
-
issues.push({
|
|
56
|
-
code: "HEADING_H1_MULTIPLE",
|
|
57
|
-
category: "headings",
|
|
58
|
-
severity: "warning",
|
|
59
|
-
title: "Multiples balises <h1> détectées",
|
|
60
|
-
description: `La page contient ${h1s.length} balises <h1>.`,
|
|
61
|
-
recommendation: "Conservez une seule balise <h1> principale et convertissez les autres en <h2>.",
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Check heading hierarchy gaps (e.g. H1 followed immediately by H3 without H2)
|
|
66
|
-
for (let i = 0; i < page.headings.length - 1; i++) {
|
|
67
|
-
const currentLevel = parseInt(page.headings[i].tag.replace("h", ""), 10);
|
|
68
|
-
const nextLevel = parseInt(page.headings[i + 1].tag.replace("h", ""), 10);
|
|
69
|
-
if (nextLevel - currentLevel > 1) {
|
|
70
|
-
issues.push({
|
|
71
|
-
code: "HEADING_HIERARCHY_GAP",
|
|
72
|
-
category: "headings",
|
|
73
|
-
severity: "warning",
|
|
74
|
-
title: "Saut dans la hiérarchie des titres",
|
|
75
|
-
description: `Passage direct de <${page.headings[i].tag}> à <${page.headings[i + 1].tag}> sans niveau intermédiaire.`,
|
|
76
|
-
recommendation: "Respectez l'arborescence logique des titres (H1 ➔ H2 ➔ H3).",
|
|
77
|
-
});
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// 2. Redirect Chain & Loop Audit
|
|
83
|
-
if (page.redirectChain && page.redirectChain.length > 1) {
|
|
84
|
-
const isLoop = new Set(page.redirectChain.map((r) => r.url)).size < page.redirectChain.length;
|
|
85
|
-
if (isLoop) {
|
|
86
|
-
issues.push({
|
|
87
|
-
code: "REDIRECT_LOOP",
|
|
88
|
-
category: "redirects",
|
|
89
|
-
severity: "critical",
|
|
90
|
-
title: "Boucle de redirection infinie",
|
|
91
|
-
description: `Boucle de redirection détectée sur ${page.url}.`,
|
|
92
|
-
recommendation: "Corrigez la configuration de redirection pour pointer directement vers l'URL finale.",
|
|
93
|
-
});
|
|
94
|
-
} else {
|
|
95
|
-
issues.push({
|
|
96
|
-
code: "REDIRECT_CHAIN",
|
|
97
|
-
category: "redirects",
|
|
98
|
-
severity: "warning",
|
|
99
|
-
title: "Chaîne de redirection (> 1 saut)",
|
|
100
|
-
description: `La redirection traverse ${page.redirectChain.length} étapes successives.`,
|
|
101
|
-
recommendation: "Redirigez directement de l'URL initiale vers la destination finale en 1 seul saut 301.",
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// 3. Thin Content Detection (< 300 words)
|
|
107
|
-
if (page.wordCount < 300 && page.statusCode === 200) {
|
|
108
|
-
issues.push({
|
|
109
|
-
code: "CONTENT_THIN",
|
|
110
|
-
category: "content",
|
|
111
|
-
severity: "critical",
|
|
112
|
-
title: "Contenu trop faible (Thin Content)",
|
|
113
|
-
description: `La page ne contient que ${page.wordCount} mots (seuil minimum recommandé : 300 mots).`,
|
|
114
|
-
recommendation: "Enrichissez la page avec du contenu textuel informatif, FAQs et preuves sociales.",
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// 4. Canonical Auditing
|
|
119
|
-
if (!page.canonicalUrl && page.statusCode === 200) {
|
|
120
|
-
issues.push({
|
|
121
|
-
code: "CANONICAL_MISSING",
|
|
122
|
-
category: "canonical",
|
|
123
|
-
severity: "warning",
|
|
124
|
-
title: "Balise canonique manquante",
|
|
125
|
-
description: `Aucune balise <link rel="canonical"> n'est déclarée.`,
|
|
126
|
-
recommendation: "Ajoutez une URL canonique absolue pointant vers la version officielle de la page.",
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// 5. Image Optimization & Alt Attributes
|
|
131
|
-
if (page.images) {
|
|
132
|
-
const missingAlts = page.images.filter((img) => !img.alt || img.alt.trim() === "");
|
|
133
|
-
if (missingAlts.length > 0) {
|
|
134
|
-
issues.push({
|
|
135
|
-
code: "IMAGES_ALT_MISSING",
|
|
136
|
-
category: "images",
|
|
137
|
-
severity: "warning",
|
|
138
|
-
title: "Images sans texte alternatif (alt)",
|
|
139
|
-
description: `${missingAlts.length} image(s) n'ont pas d'attribut alt renseigné.`,
|
|
140
|
-
recommendation: "Renseignez des descriptions textuelles précises pour l'accessibilité et Google Images.",
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const heavyImages = page.images.filter((img) => (img.sizeBytes || 0) > 100_000);
|
|
145
|
-
if (heavyImages.length > 0) {
|
|
146
|
-
issues.push({
|
|
147
|
-
code: "IMAGES_HEAVY",
|
|
148
|
-
category: "images",
|
|
149
|
-
severity: "info",
|
|
150
|
-
title: "Images trop lourdes (> 100 KB)",
|
|
151
|
-
description: `${heavyImages.length} image(s) dépassent 100 KB.`,
|
|
152
|
-
recommendation: "Convertissez vos images au format moderne WebP / AVIF et compressez-les.",
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// 6. Time To First Byte (TTFB)
|
|
158
|
-
if (page.timeToFirstByteMs && page.timeToFirstByteMs > 500) {
|
|
159
|
-
issues.push({
|
|
160
|
-
code: "PERF_HIGH_TTFB",
|
|
161
|
-
category: "performance",
|
|
162
|
-
severity: "warning",
|
|
163
|
-
title: "Temps de réponse serveur élevé (TTFB > 500ms)",
|
|
164
|
-
description: `Le TTFB mesuré est de ${page.timeToFirstByteMs}ms.`,
|
|
165
|
-
recommendation: "Activez la mise en cache Edge CDN (Cloudflare) ou l'ISR Next.js pour passer sous 50ms.",
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return issues;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🪙 Token Quota & Credit Billing Bridge
|
|
3
|
-
*
|
|
4
|
-
* Synchronizes with LynxFlow's official TokenBillingService & CreditService (1$ = 100 credits).
|
|
5
|
-
* Manages tenant monthly quotas, OpenRouter raw provider costs, and credit balances.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export interface TokenUsageRecord {
|
|
9
|
-
promptTokens: number;
|
|
10
|
-
completionTokens: number;
|
|
11
|
-
totalTokens: number;
|
|
12
|
-
creditsCharged: number;
|
|
13
|
-
model: string;
|
|
14
|
-
timestamp: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface CreditQuotaStatus {
|
|
18
|
-
tier: "starter" | "growth" | "enterprise";
|
|
19
|
-
monthlyCreditBudget: number; // e.g. 5,000 credits ($50) for Growth, 50,000 credits ($500) for Enterprise
|
|
20
|
-
usedCredits: number;
|
|
21
|
-
remainingCredits: number;
|
|
22
|
-
isQuotaExceeded: boolean;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export class TokenQuotaManager {
|
|
26
|
-
private tier: "starter" | "growth" | "enterprise";
|
|
27
|
-
private monthlyCreditBudget: number;
|
|
28
|
-
private usedCredits: number = 0;
|
|
29
|
-
|
|
30
|
-
constructor(tier: "starter" | "growth" | "enterprise" = "enterprise") {
|
|
31
|
-
this.tier = tier;
|
|
32
|
-
this.monthlyCreditBudget = this.resolveBudget(tier);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
private resolveBudget(tier: "starter" | "growth" | "enterprise"): number {
|
|
36
|
-
switch (tier) {
|
|
37
|
-
case "enterprise":
|
|
38
|
-
return 50_000; // 50,000 credits ($500/mo)
|
|
39
|
-
case "growth":
|
|
40
|
-
return 5_000; // 5,000 credits ($50/mo)
|
|
41
|
-
case "starter":
|
|
42
|
-
default:
|
|
43
|
-
return 500; // 500 credits ($5/mo)
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Records token usage and calculates LynxFlow Credit cost (1$ = 100 credits).
|
|
49
|
-
* Only charges credits when AI call succeeds (Issue #222 fix).
|
|
50
|
-
*/
|
|
51
|
-
consumeTokens(
|
|
52
|
-
promptTokens: number,
|
|
53
|
-
completionTokens: number,
|
|
54
|
-
model: string,
|
|
55
|
-
rawProviderCostUsd = 0.002, // Default ~$0.002 per 1k tokens
|
|
56
|
-
): TokenUsageRecord {
|
|
57
|
-
const totalTokens = promptTokens + completionTokens;
|
|
58
|
-
|
|
59
|
-
// LynxFlow standard: Provider cost + 20% margin converted to credits (1$ = 100cr)
|
|
60
|
-
const costWithMarginUsd = rawProviderCostUsd * (totalTokens / 1000) * 1.2;
|
|
61
|
-
const creditsCharged = Math.max(1, Math.ceil(costWithMarginUsd * 100));
|
|
62
|
-
|
|
63
|
-
this.usedCredits += creditsCharged;
|
|
64
|
-
|
|
65
|
-
if (this.usedCredits > this.monthlyCreditBudget) {
|
|
66
|
-
console.warn(`⚠️ [LynxFlow Credit Quota] Monthly credit budget exceeded (${this.usedCredits} / ${this.monthlyCreditBudget} cr). Switching to zero-cost algorithmic mode.`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
promptTokens,
|
|
71
|
-
completionTokens,
|
|
72
|
-
totalTokens,
|
|
73
|
-
creditsCharged,
|
|
74
|
-
model,
|
|
75
|
-
timestamp: new Date().toISOString(),
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Gets current credit quota status.
|
|
81
|
-
*/
|
|
82
|
-
getStatus(): CreditQuotaStatus {
|
|
83
|
-
return {
|
|
84
|
-
tier: this.tier,
|
|
85
|
-
monthlyCreditBudget: this.monthlyCreditBudget,
|
|
86
|
-
usedCredits: this.usedCredits,
|
|
87
|
-
remainingCredits: Math.max(0, this.monthlyCreditBudget - this.usedCredits),
|
|
88
|
-
isQuotaExceeded: this.usedCredits >= this.monthlyCreditBudget,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🌟 Types & Interfaces for @lynxflow/seo-engine Universal SDK
|
|
3
|
-
*
|
|
4
|
-
* Full Institutional pSEO Type System matching LynxFlow's 8 Programmatic Matrices:
|
|
5
|
-
* 1. 📍 Local & GEO : [Pillar/Service] × [City/Country]
|
|
6
|
-
* 2. 🥊 VS & Comparatifs : [Brand/Service] vs [Competitor]
|
|
7
|
-
* 3. 🔄 Alternatives : [Competitor] ➔ [Brand/Service]
|
|
8
|
-
* 4. 🏢 Secteurs & Métiers : [Service] × [Industry Vertical] (with localized ROI)
|
|
9
|
-
* 5. 🔌 Intégrations : [Service] × [Third-party Software]
|
|
10
|
-
* 6. 👤 Personas & Décideurs : [Service] × [Decision Maker Role]
|
|
11
|
-
* 7. 🎯 Cas d'Usage : [Problem Statement] ➔ [AI Workflow Solution]
|
|
12
|
-
* 8. 🧮 Simulateurs & Outils : Interactive ROI & Time-Savings Calculators
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
export interface LynxServiceDefinition {
|
|
16
|
-
slug: string;
|
|
17
|
-
name: string;
|
|
18
|
-
description?: string;
|
|
19
|
-
pricePerMonth: number;
|
|
20
|
-
category?: string;
|
|
21
|
-
features?: string[];
|
|
22
|
-
faqs?: { question: string; answer: string }[];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
import type {
|
|
26
|
-
PseoIndustry,
|
|
27
|
-
PseoCompetitor,
|
|
28
|
-
PseoIntegration,
|
|
29
|
-
PseoUseCase,
|
|
30
|
-
PseoPersona,
|
|
31
|
-
PseoService,
|
|
32
|
-
PseoLocation,
|
|
33
|
-
PseoCalculator,
|
|
34
|
-
} from "./matrix-engine";
|
|
35
|
-
|
|
36
|
-
export type {
|
|
37
|
-
PseoIndustry,
|
|
38
|
-
PseoCompetitor,
|
|
39
|
-
PseoIntegration,
|
|
40
|
-
PseoUseCase,
|
|
41
|
-
PseoPersona,
|
|
42
|
-
PseoService,
|
|
43
|
-
PseoLocation,
|
|
44
|
-
PseoCalculator,
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export interface PseoCity {
|
|
48
|
-
slug: string;
|
|
49
|
-
name: string;
|
|
50
|
-
region: string;
|
|
51
|
-
country: string;
|
|
52
|
-
department?: string;
|
|
53
|
-
densityTier?: "major" | "secondary" | "regional";
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface PseoCountry {
|
|
57
|
-
code: string;
|
|
58
|
-
name: string;
|
|
59
|
-
currency: string;
|
|
60
|
-
currencySymbol: string;
|
|
61
|
-
locale: string;
|
|
62
|
-
economicMultiplier: number;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface LynxSeoConfig {
|
|
66
|
-
apiKey?: string;
|
|
67
|
-
licenseKey?: string; // Deprecated: use apiKey
|
|
68
|
-
domain: string;
|
|
69
|
-
brandName: string;
|
|
70
|
-
category?: string;
|
|
71
|
-
defaultLocale?: string;
|
|
72
|
-
supportedLocales?: string[];
|
|
73
|
-
currency?: string;
|
|
74
|
-
currencySymbol?: string;
|
|
75
|
-
services: LynxServiceDefinition[];
|
|
76
|
-
industries?: PseoIndustry[];
|
|
77
|
-
competitors?: PseoCompetitor[];
|
|
78
|
-
integrations?: PseoIntegration[];
|
|
79
|
-
personas?: PseoPersona[];
|
|
80
|
-
useCases?: PseoUseCase[];
|
|
81
|
-
cities?: PseoCity[];
|
|
82
|
-
locations?: PseoLocation[];
|
|
83
|
-
phone?: string;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export interface LynxResolvedPage {
|
|
87
|
-
url: string;
|
|
88
|
-
title: string;
|
|
89
|
-
description: string;
|
|
90
|
-
h1: string;
|
|
91
|
-
directAnswer: string;
|
|
92
|
-
locale?: string;
|
|
93
|
-
hreflangs?: { lang: string; url: string }[];
|
|
94
|
-
faqs: { question: string; answer: string }[];
|
|
95
|
-
jsonLd: Record<string, unknown>;
|
|
96
|
-
htmlBody: string;
|
|
97
|
-
markdownBody: string;
|
|
98
|
-
executionTimeMs: number;
|
|
99
|
-
|
|
100
|
-
// Localized ROI data (when applicable)
|
|
101
|
-
localizedRoi?: {
|
|
102
|
-
monthlySavingsFormatted: string;
|
|
103
|
-
hoursSavedWeekly: number;
|
|
104
|
-
annualSavingsFormatted: string;
|
|
105
|
-
currencySymbol: string;
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
// Structured component data
|
|
109
|
-
service?: LynxServiceDefinition;
|
|
110
|
-
industry?: PseoIndustry;
|
|
111
|
-
competitor?: PseoCompetitor;
|
|
112
|
-
integration?: PseoIntegration;
|
|
113
|
-
|
|
114
|
-
meta?: {
|
|
115
|
-
title: string;
|
|
116
|
-
description: string;
|
|
117
|
-
h1: string;
|
|
118
|
-
canonical: string;
|
|
119
|
-
openGraphImageUrl?: string;
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
// Backward compatibility convenience fields
|
|
123
|
-
fullUrl?: string;
|
|
124
|
-
schemaJsonLd?: Record<string, unknown>;
|
|
125
|
-
content?: {
|
|
126
|
-
directAnswerGeoHtml?: string;
|
|
127
|
-
heroHeadline?: string;
|
|
128
|
-
heroSubheadline?: string;
|
|
129
|
-
markdownBody?: string;
|
|
130
|
-
faqList?: { question: string; answer: string }[];
|
|
131
|
-
};
|
|
132
|
-
pricing?: {
|
|
133
|
-
priceNumber?: number;
|
|
134
|
-
priceFormatted?: string;
|
|
135
|
-
currency?: string;
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
debug?: {
|
|
139
|
-
licenseStatus?: "active" | "trial" | "expired" | "offline_mode";
|
|
140
|
-
generationMs?: number;
|
|
141
|
-
matrixType?: string;
|
|
142
|
-
};
|
|
143
|
-
}
|