@rankcli/agent-runtime 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +50 -1
- package/dist/index.d.ts +50 -1
- package/dist/index.js +406 -1
- package/dist/index.mjs +402 -1
- package/package.json +1 -1
- package/src/audit/checks/color-contrast.ts +1 -1
- package/src/audit/checks/dom-size.ts +1 -1
- package/src/audit/checks/html-compliance.ts +1 -1
- package/src/audit/checks/image-dimensions.ts +1 -1
- package/src/audit/checks/links.ts +70 -0
- package/src/audit/checks/site-maturity.ts +9 -0
- package/src/audit/runner.test.ts +7 -7
- package/src/audit/types.ts +9 -0
- package/src/fixer.ts +250 -0
- package/src/geo/index.ts +1 -0
- package/src/geo/llm-citation-checker.ts +188 -0
- package/src/keywords/sources/free-sources.ts +1 -1
- package/src/scheduler/scheduled-audit.test.ts +6 -6
package/dist/index.js
CHANGED
|
@@ -853,6 +853,7 @@ __export(index_exports, {
|
|
|
853
853
|
applyFixes: () => applyFixes,
|
|
854
854
|
buildGSCApiRequest: () => buildGSCApiRequest,
|
|
855
855
|
buildGSCRequest: () => buildGSCRequest,
|
|
856
|
+
calculateAIVisibilityScore: () => calculateAIVisibilityScore,
|
|
856
857
|
calculateBM25: () => calculateBM252,
|
|
857
858
|
calculateKeywordTFIDF: () => calculateTFIDF,
|
|
858
859
|
calculateNextRun: () => calculateNextRun,
|
|
@@ -867,6 +868,7 @@ __export(index_exports, {
|
|
|
867
868
|
checkGitHubCLI: () => checkGitHubCLI,
|
|
868
869
|
checkInternalRedirects: () => checkInternalRedirects,
|
|
869
870
|
checkJSRenderingRatio: () => checkJSRenderingRatio,
|
|
871
|
+
checkLLMCitations: () => checkLLMCitations,
|
|
870
872
|
checkLlmsTxt: () => checkLlmsTxt,
|
|
871
873
|
checkMobileResources: () => checkMobileResources,
|
|
872
874
|
checkPlaintextEmails: () => checkPlaintextEmails,
|
|
@@ -970,6 +972,7 @@ __export(index_exports, {
|
|
|
970
972
|
generatePDFReport: () => generatePDFReport,
|
|
971
973
|
generatePRDescription: () => generatePRDescription,
|
|
972
974
|
generateReactHelmetSocialMeta: () => generateReactHelmetSocialMeta,
|
|
975
|
+
generateRecommendationQueries: () => generateRecommendationQueries,
|
|
973
976
|
generateRemixMeta: () => generateRemixMeta,
|
|
974
977
|
generateSecretsDoc: () => generateSecretsDoc,
|
|
975
978
|
generateSocialMetaFix: () => generateSocialMetaFix,
|
|
@@ -977,6 +980,7 @@ __export(index_exports, {
|
|
|
977
980
|
generateUncertaintyQuestions: () => generateUncertaintyQuestions,
|
|
978
981
|
generateWizardQuestions: () => generateWizardQuestions,
|
|
979
982
|
generateWorkflow: () => generateWorkflow,
|
|
983
|
+
getAIVisibilitySummary: () => getAIVisibilitySummary,
|
|
980
984
|
getAutocompleteSuggestions: () => getAutocompleteSuggestions,
|
|
981
985
|
getDateRange: () => getDateRange,
|
|
982
986
|
getExpandedSuggestions: () => getExpandedSuggestions,
|
|
@@ -1321,6 +1325,15 @@ var ISSUE_DEFINITIONS = {
|
|
|
1321
1325
|
impact: "Poor user experience and potential trust issues.",
|
|
1322
1326
|
howToFix: "Update or remove the broken external link."
|
|
1323
1327
|
},
|
|
1328
|
+
JS_ONLY_NAVIGATION: {
|
|
1329
|
+
code: "JS_ONLY_NAVIGATION",
|
|
1330
|
+
severity: "warning",
|
|
1331
|
+
category: "links",
|
|
1332
|
+
title: "JavaScript-only navigation detected",
|
|
1333
|
+
description: "Navigation elements use onClick handlers without proper <a href> links.",
|
|
1334
|
+
impact: "Search engine crawlers cannot follow JavaScript-only navigation. These links are invisible to crawlers, preventing page discovery and indexing.",
|
|
1335
|
+
howToFix: "Replace onClick navigation with proper <a href> or <Link> components. In React, use react-router Link instead of navigate() in onClick handlers. Ensure all navigation renders as real anchor tags with href attributes."
|
|
1336
|
+
},
|
|
1324
1337
|
TOO_MANY_LINKS: {
|
|
1325
1338
|
code: "TOO_MANY_LINKS",
|
|
1326
1339
|
severity: "notice",
|
|
@@ -3183,6 +3196,7 @@ async function analyzeLinks(html, baseUrl, checkBroken = false) {
|
|
|
3183
3196
|
const external = [];
|
|
3184
3197
|
const brokenInternal = [];
|
|
3185
3198
|
const brokenExternal = [];
|
|
3199
|
+
const jsOnlyNavigation = [];
|
|
3186
3200
|
$("a[href]").each((_, el) => {
|
|
3187
3201
|
const href = $(el).attr("href") || "";
|
|
3188
3202
|
const text = $(el).text().trim();
|
|
@@ -3211,6 +3225,56 @@ async function analyzeLinks(html, baseUrl, checkBroken = false) {
|
|
|
3211
3225
|
}
|
|
3212
3226
|
});
|
|
3213
3227
|
const totalLinks = internal.length + external.length;
|
|
3228
|
+
const navPatterns = /navigate|router|history\.push|location\.href|window\.location|goto|redirect/i;
|
|
3229
|
+
const navTextPatterns = /^(home|about|contact|pricing|blog|docs|features|products?|services?|sign\s*up|log\s*in|register|dashboard|account|settings|help|faq|support|menu|nav)$/i;
|
|
3230
|
+
$("button[onclick], div[onclick], span[onclick], li[onclick]").each((_, el) => {
|
|
3231
|
+
const $el = $(el);
|
|
3232
|
+
const onclick = $el.attr("onclick") || "";
|
|
3233
|
+
const text = $el.text().trim().substring(0, 50);
|
|
3234
|
+
if (navPatterns.test(onclick) || navTextPatterns.test(text)) {
|
|
3235
|
+
jsOnlyNavigation.push({
|
|
3236
|
+
element: el.tagName.toLowerCase(),
|
|
3237
|
+
text: text || "(no text)",
|
|
3238
|
+
reason: "Element uses onClick for navigation instead of <a href>"
|
|
3239
|
+
});
|
|
3240
|
+
}
|
|
3241
|
+
});
|
|
3242
|
+
$("a[onclick]").each((_, el) => {
|
|
3243
|
+
const $el = $(el);
|
|
3244
|
+
const href = $el.attr("href") || "";
|
|
3245
|
+
const onclick = $el.attr("onclick") || "";
|
|
3246
|
+
const text = $el.text().trim().substring(0, 50);
|
|
3247
|
+
if ((href === "#" || href === "" || href.startsWith("javascript:")) && onclick) {
|
|
3248
|
+
jsOnlyNavigation.push({
|
|
3249
|
+
element: "a",
|
|
3250
|
+
text: text || "(no text)",
|
|
3251
|
+
reason: `Link has href="${href}" with onClick - crawlers cannot follow this`
|
|
3252
|
+
});
|
|
3253
|
+
}
|
|
3254
|
+
});
|
|
3255
|
+
$("[data-href], [data-to], [data-link], [data-route]").each((_, el) => {
|
|
3256
|
+
const $el = $(el);
|
|
3257
|
+
const tagName = el.tagName.toLowerCase();
|
|
3258
|
+
const text = $el.text().trim().substring(0, 50);
|
|
3259
|
+
if (tagName !== "a" || !$el.attr("href") || $el.attr("href") === "#") {
|
|
3260
|
+
jsOnlyNavigation.push({
|
|
3261
|
+
element: tagName,
|
|
3262
|
+
text: text || "(no text)",
|
|
3263
|
+
reason: "Uses data attribute for routing instead of real href"
|
|
3264
|
+
});
|
|
3265
|
+
}
|
|
3266
|
+
});
|
|
3267
|
+
if (jsOnlyNavigation.length > 0) {
|
|
3268
|
+
const uniqueNav = jsOnlyNavigation.slice(0, 10);
|
|
3269
|
+
issues.push({
|
|
3270
|
+
...ISSUE_DEFINITIONS.JS_ONLY_NAVIGATION,
|
|
3271
|
+
affectedUrls: [baseUrl],
|
|
3272
|
+
details: {
|
|
3273
|
+
count: jsOnlyNavigation.length,
|
|
3274
|
+
examples: uniqueNav
|
|
3275
|
+
}
|
|
3276
|
+
});
|
|
3277
|
+
}
|
|
3214
3278
|
const internalCount = internal.length;
|
|
3215
3279
|
const externalCount = external.length;
|
|
3216
3280
|
const internalToExternalRatio = externalCount > 0 ? internalCount / externalCount : null;
|
|
@@ -3318,6 +3382,7 @@ async function analyzeLinks(html, baseUrl, checkBroken = false) {
|
|
|
3318
3382
|
totalLinks,
|
|
3319
3383
|
brokenInternal,
|
|
3320
3384
|
brokenExternal,
|
|
3385
|
+
jsOnlyNavigation,
|
|
3321
3386
|
ratio: {
|
|
3322
3387
|
internal: internalCount,
|
|
3323
3388
|
external: externalCount,
|
|
@@ -8693,6 +8758,12 @@ function analyzeContentScience(html, url, targetKeywords = []) {
|
|
|
8693
8758
|
init_http();
|
|
8694
8759
|
var cheerio28 = __toESM(require("cheerio"));
|
|
8695
8760
|
async function getSSLCertificateAge(url) {
|
|
8761
|
+
let https;
|
|
8762
|
+
try {
|
|
8763
|
+
https = await import("https");
|
|
8764
|
+
} catch {
|
|
8765
|
+
return null;
|
|
8766
|
+
}
|
|
8696
8767
|
return new Promise((resolve) => {
|
|
8697
8768
|
try {
|
|
8698
8769
|
const parsedUrl = new URL(url);
|
|
@@ -15210,7 +15281,7 @@ var cheerio49 = __toESM(require("cheerio"));
|
|
|
15210
15281
|
init_http();
|
|
15211
15282
|
async function analyzeHtmlCompliance(html, url, headers) {
|
|
15212
15283
|
const issues = [];
|
|
15213
|
-
const $ = cheerio49.load(html
|
|
15284
|
+
const $ = cheerio49.load(html);
|
|
15214
15285
|
const parsedUrl = new URL(url);
|
|
15215
15286
|
const doctypeMatch = html.match(/<!DOCTYPE\s+([^>]+)>/i);
|
|
15216
15287
|
const hasDoctype = doctypeMatch !== null;
|
|
@@ -26679,26 +26750,104 @@ async function generateFixForIssue(issue, context) {
|
|
|
26679
26750
|
const fullUrl = url || "https://example.com";
|
|
26680
26751
|
const siteName = new URL(fullUrl).hostname.replace("www.", "");
|
|
26681
26752
|
switch (issue.code) {
|
|
26753
|
+
// Title issues
|
|
26682
26754
|
case "MISSING_TITLE":
|
|
26755
|
+
case "TITLE_MISSING":
|
|
26756
|
+
// Full audit code
|
|
26757
|
+
case "TITLE_KEYWORD_MISMATCH":
|
|
26758
|
+
case "TITLE_H1_KEYWORD_MISMATCH":
|
|
26759
|
+
case "OUTDATED_YEAR_IN_TITLE":
|
|
26683
26760
|
return generateTitleFix(context, siteName);
|
|
26761
|
+
// Meta description issues
|
|
26684
26762
|
case "MISSING_META_DESC":
|
|
26763
|
+
case "META_DESC_MISSING":
|
|
26685
26764
|
return generateMetaDescFix(context, siteName);
|
|
26765
|
+
// Canonical issues
|
|
26686
26766
|
case "MISSING_CANONICAL":
|
|
26767
|
+
case "CANONICAL_NO_HTTPS_REDIRECT":
|
|
26687
26768
|
return generateCanonicalFix(context, fullUrl);
|
|
26769
|
+
// Viewport issues
|
|
26688
26770
|
case "MISSING_VIEWPORT":
|
|
26771
|
+
case "HTML_NO_VIEWPORT":
|
|
26772
|
+
case "RESPONSIVE_NO_VIEWPORT":
|
|
26689
26773
|
return generateViewportFix(context);
|
|
26774
|
+
// Open Graph issues
|
|
26690
26775
|
case "MISSING_OG_TAGS":
|
|
26776
|
+
case "OG_TITLE_MISSING":
|
|
26777
|
+
case "OG_DESC_MISSING":
|
|
26778
|
+
case "OG_IMAGE_MISSING":
|
|
26779
|
+
case "OG_URL_MISSING":
|
|
26691
26780
|
return generateOGFix(context, siteName, fullUrl);
|
|
26781
|
+
// Twitter Card issues
|
|
26692
26782
|
case "MISSING_TWITTER_CARD":
|
|
26783
|
+
case "TWITTER_CARD_MISSING":
|
|
26784
|
+
case "TWITTER_TITLE_MISSING":
|
|
26785
|
+
case "TWITTER_DESC_MISSING":
|
|
26786
|
+
case "TWITTER_IMAGE_MISSING":
|
|
26693
26787
|
return generateTwitterFix(context, siteName);
|
|
26788
|
+
// Schema/structured data issues
|
|
26694
26789
|
case "MISSING_SCHEMA":
|
|
26790
|
+
case "SCHEMA_MISSING":
|
|
26791
|
+
// Full audit code
|
|
26792
|
+
case "SCHEMA_ORG_MISSING":
|
|
26793
|
+
case "NO_ORGANIZATION_SCHEMA":
|
|
26794
|
+
case "NO_ENTITY_SCHEMA":
|
|
26795
|
+
case "FAQ_SCHEMA_MISSING":
|
|
26695
26796
|
return generateSchemaFix(context, siteName, fullUrl);
|
|
26797
|
+
// Robots.txt issues
|
|
26696
26798
|
case "MISSING_ROBOTS":
|
|
26799
|
+
case "ROBOTS_TXT_MISSING":
|
|
26800
|
+
// Full audit code
|
|
26801
|
+
case "ROBOTS_TXT_WARNINGS":
|
|
26802
|
+
case "ROBOTS_TXT_INVALID_SYNTAX":
|
|
26697
26803
|
return generateRobotsFix(context, fullUrl);
|
|
26804
|
+
// Sitemap issues
|
|
26698
26805
|
case "MISSING_SITEMAP":
|
|
26806
|
+
case "SITEMAP_MISSING":
|
|
26807
|
+
// Full audit code
|
|
26808
|
+
case "BING_SITEMAP_MISSING":
|
|
26699
26809
|
return generateSitemapFix(context, fullUrl);
|
|
26810
|
+
// H1 issues
|
|
26700
26811
|
case "MISSING_H1":
|
|
26812
|
+
case "NO_VISIBLE_HEADLINE":
|
|
26813
|
+
case "H1_MISSING_KEYWORD":
|
|
26701
26814
|
return await generateH1Fix({ cwd });
|
|
26815
|
+
// SPA-specific: add meta management library recommendation
|
|
26816
|
+
case "SPA_NO_META_MANAGEMENT":
|
|
26817
|
+
case "SPA_WITHOUT_SSR":
|
|
26818
|
+
case "CLIENT_SIDE_RENDERING":
|
|
26819
|
+
return generateSPAMetaFix(context, framework);
|
|
26820
|
+
// Internal links - suggest adding links
|
|
26821
|
+
case "NO_INTERNAL_LINKS":
|
|
26822
|
+
case "LINKS_NO_INTERNAL":
|
|
26823
|
+
case "NO_CONTENT_INTERNAL_LINKS":
|
|
26824
|
+
return {
|
|
26825
|
+
issue: { code: issue.code, message: "No internal links found", severity: "warning" },
|
|
26826
|
+
file: "src/components/Footer.tsx",
|
|
26827
|
+
before: null,
|
|
26828
|
+
after: `// Add internal navigation links to your footer or content
|
|
26829
|
+
// Example: <Link to="/about">About</Link>`,
|
|
26830
|
+
explanation: "Add internal links to help search engines discover your content",
|
|
26831
|
+
skipped: true,
|
|
26832
|
+
skipReason: "Requires manual content updates - add links to your navigation and content"
|
|
26833
|
+
};
|
|
26834
|
+
// Preconnect issues
|
|
26835
|
+
case "GOOGLE_FONTS_NO_PRECONNECT":
|
|
26836
|
+
case "MISSING_PRECONNECT":
|
|
26837
|
+
return generatePreconnectFix(context);
|
|
26838
|
+
// Favicon/icons
|
|
26839
|
+
case "HTML_NO_FAVICON":
|
|
26840
|
+
case "HTML_NO_APPLE_TOUCH_ICON":
|
|
26841
|
+
return generateFaviconFix(context);
|
|
26842
|
+
// Charset/lang
|
|
26843
|
+
case "HTML_NO_CHARSET":
|
|
26844
|
+
case "HTML_NOT_UTF8":
|
|
26845
|
+
return generateCharsetFix(context);
|
|
26846
|
+
case "HTML_NO_LANG":
|
|
26847
|
+
return generateLangFix(context);
|
|
26848
|
+
// AI/LLMs.txt
|
|
26849
|
+
case "AI_NO_LLMS_TXT":
|
|
26850
|
+
return generateLlmsTxtFix(context, siteName, fullUrl);
|
|
26702
26851
|
default:
|
|
26703
26852
|
return null;
|
|
26704
26853
|
}
|
|
@@ -26927,6 +27076,159 @@ function generateSitemapFix(context, url) {
|
|
|
26927
27076
|
explanation: "Created sitemap.xml to help search engines discover all pages"
|
|
26928
27077
|
};
|
|
26929
27078
|
}
|
|
27079
|
+
function generateSPAMetaFix(context, framework) {
|
|
27080
|
+
const { htmlPath } = context;
|
|
27081
|
+
if (framework.name.toLowerCase().includes("react") || framework.name === "Unknown") {
|
|
27082
|
+
return {
|
|
27083
|
+
issue: { code: "SPA_NO_META_MANAGEMENT", message: "SPA without dynamic meta tag management", severity: "warning" },
|
|
27084
|
+
file: "src/components/SEOHead.tsx",
|
|
27085
|
+
before: null,
|
|
27086
|
+
after: `import { Helmet } from 'react-helmet-async';
|
|
27087
|
+
|
|
27088
|
+
interface SEOHeadProps {
|
|
27089
|
+
title?: string;
|
|
27090
|
+
description?: string;
|
|
27091
|
+
image?: string;
|
|
27092
|
+
url?: string;
|
|
27093
|
+
}
|
|
27094
|
+
|
|
27095
|
+
export function SEOHead({
|
|
27096
|
+
title = 'Your Site Name',
|
|
27097
|
+
description = 'Your site description',
|
|
27098
|
+
image = '/og-image.png',
|
|
27099
|
+
url = window.location.href,
|
|
27100
|
+
}: SEOHeadProps) {
|
|
27101
|
+
return (
|
|
27102
|
+
<Helmet>
|
|
27103
|
+
<title>{title}</title>
|
|
27104
|
+
<meta name="description" content={description} />
|
|
27105
|
+
<link rel="canonical" href={url} />
|
|
27106
|
+
|
|
27107
|
+
{/* Open Graph */}
|
|
27108
|
+
<meta property="og:title" content={title} />
|
|
27109
|
+
<meta property="og:description" content={description} />
|
|
27110
|
+
<meta property="og:image" content={image} />
|
|
27111
|
+
<meta property="og:url" content={url} />
|
|
27112
|
+
<meta property="og:type" content="website" />
|
|
27113
|
+
|
|
27114
|
+
{/* Twitter */}
|
|
27115
|
+
<meta name="twitter:card" content="summary_large_image" />
|
|
27116
|
+
<meta name="twitter:title" content={title} />
|
|
27117
|
+
<meta name="twitter:description" content={description} />
|
|
27118
|
+
<meta name="twitter:image" content={image} />
|
|
27119
|
+
</Helmet>
|
|
27120
|
+
);
|
|
27121
|
+
}`,
|
|
27122
|
+
explanation: "Created SEOHead component using react-helmet-async for dynamic meta tags. Install: npm install react-helmet-async"
|
|
27123
|
+
};
|
|
27124
|
+
}
|
|
27125
|
+
return {
|
|
27126
|
+
issue: { code: "SPA_NO_META_MANAGEMENT", message: "SPA without meta management", severity: "warning" },
|
|
27127
|
+
file: htmlPath,
|
|
27128
|
+
before: null,
|
|
27129
|
+
after: "<!-- Add a meta management library for your framework -->",
|
|
27130
|
+
explanation: `Add dynamic meta tag management for ${framework.name}`,
|
|
27131
|
+
skipped: true,
|
|
27132
|
+
skipReason: `Framework-specific solution needed for ${framework.name}`
|
|
27133
|
+
};
|
|
27134
|
+
}
|
|
27135
|
+
function generatePreconnectFix(context) {
|
|
27136
|
+
const { htmlPath, htmlContent } = context;
|
|
27137
|
+
const preconnects = `<!-- Preconnect to external origins -->
|
|
27138
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
27139
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />`;
|
|
27140
|
+
return {
|
|
27141
|
+
issue: { code: "MISSING_PRECONNECT", message: "Missing preconnect hints", severity: "info" },
|
|
27142
|
+
file: htmlPath,
|
|
27143
|
+
before: "<head>",
|
|
27144
|
+
after: `<head>
|
|
27145
|
+
${preconnects}`,
|
|
27146
|
+
explanation: "Added preconnect hints to speed up loading of external resources"
|
|
27147
|
+
};
|
|
27148
|
+
}
|
|
27149
|
+
function generateFaviconFix(context) {
|
|
27150
|
+
const { htmlPath } = context;
|
|
27151
|
+
const faviconTags = `<!-- Favicons -->
|
|
27152
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
27153
|
+
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
|
27154
|
+
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />`;
|
|
27155
|
+
return {
|
|
27156
|
+
issue: { code: "HTML_NO_FAVICON", message: "Missing favicon", severity: "info" },
|
|
27157
|
+
file: htmlPath,
|
|
27158
|
+
before: "<head>",
|
|
27159
|
+
after: `<head>
|
|
27160
|
+
${faviconTags}`,
|
|
27161
|
+
explanation: "Added favicon links. Create favicon files in public/ directory."
|
|
27162
|
+
};
|
|
27163
|
+
}
|
|
27164
|
+
function generateCharsetFix(context) {
|
|
27165
|
+
const { htmlPath, htmlContent } = context;
|
|
27166
|
+
if (htmlContent.includes("charset")) {
|
|
27167
|
+
return {
|
|
27168
|
+
issue: { code: "HTML_NO_CHARSET", message: "Missing charset", severity: "warning" },
|
|
27169
|
+
file: htmlPath,
|
|
27170
|
+
before: null,
|
|
27171
|
+
after: '<meta charset="UTF-8" />',
|
|
27172
|
+
skipped: true,
|
|
27173
|
+
skipReason: "Charset already defined",
|
|
27174
|
+
explanation: "Charset is already present"
|
|
27175
|
+
};
|
|
27176
|
+
}
|
|
27177
|
+
return {
|
|
27178
|
+
issue: { code: "HTML_NO_CHARSET", message: "Missing charset declaration", severity: "warning" },
|
|
27179
|
+
file: htmlPath,
|
|
27180
|
+
before: "<head>",
|
|
27181
|
+
after: '<head>\n <meta charset="UTF-8" />',
|
|
27182
|
+
explanation: "Added UTF-8 charset declaration as first element in head"
|
|
27183
|
+
};
|
|
27184
|
+
}
|
|
27185
|
+
function generateLangFix(context) {
|
|
27186
|
+
const { htmlPath, htmlContent } = context;
|
|
27187
|
+
if (htmlContent.includes("<html") && !htmlContent.includes("lang=")) {
|
|
27188
|
+
return {
|
|
27189
|
+
issue: { code: "HTML_NO_LANG", message: "Missing lang attribute", severity: "warning" },
|
|
27190
|
+
file: htmlPath,
|
|
27191
|
+
before: "<html>",
|
|
27192
|
+
after: '<html lang="en">',
|
|
27193
|
+
explanation: "Added lang attribute for accessibility and SEO"
|
|
27194
|
+
};
|
|
27195
|
+
}
|
|
27196
|
+
return {
|
|
27197
|
+
issue: { code: "HTML_NO_LANG", message: "Missing lang attribute", severity: "warning" },
|
|
27198
|
+
file: htmlPath,
|
|
27199
|
+
before: "<html",
|
|
27200
|
+
after: '<html lang="en"',
|
|
27201
|
+
explanation: "Added lang attribute for accessibility and SEO"
|
|
27202
|
+
};
|
|
27203
|
+
}
|
|
27204
|
+
function generateLlmsTxtFix(context, siteName, url) {
|
|
27205
|
+
const llmsTxt = `# ${siteName}
|
|
27206
|
+
> ${siteName} - A brief description of your product/service
|
|
27207
|
+
|
|
27208
|
+
## About
|
|
27209
|
+
${siteName} is... [Add your description here]
|
|
27210
|
+
|
|
27211
|
+
## Features
|
|
27212
|
+
- Feature 1
|
|
27213
|
+
- Feature 2
|
|
27214
|
+
- Feature 3
|
|
27215
|
+
|
|
27216
|
+
## Links
|
|
27217
|
+
- Homepage: ${url}
|
|
27218
|
+
- Documentation: ${url}/docs
|
|
27219
|
+
- API: ${url}/api
|
|
27220
|
+
|
|
27221
|
+
## Contact
|
|
27222
|
+
- Email: hello@${new URL(url).hostname}
|
|
27223
|
+
`;
|
|
27224
|
+
return {
|
|
27225
|
+
issue: { code: "AI_NO_LLMS_TXT", message: "No llms.txt file for AI crawlers", severity: "info" },
|
|
27226
|
+
file: "public/llms.txt",
|
|
27227
|
+
before: null,
|
|
27228
|
+
after: llmsTxt,
|
|
27229
|
+
explanation: "Created llms.txt to help AI systems understand your site. Customize the content."
|
|
27230
|
+
};
|
|
27231
|
+
}
|
|
26930
27232
|
async function applyFixes(fixes, options) {
|
|
26931
27233
|
const { cwd, dryRun = false } = options;
|
|
26932
27234
|
const applied = [];
|
|
@@ -28725,6 +29027,105 @@ function escapeRegex2(str) {
|
|
|
28725
29027
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
28726
29028
|
}
|
|
28727
29029
|
|
|
29030
|
+
// src/geo/llm-citation-checker.ts
|
|
29031
|
+
var PROVIDER_NAMES = {
|
|
29032
|
+
openai: "ChatGPT",
|
|
29033
|
+
anthropic: "Claude",
|
|
29034
|
+
google: "Gemini",
|
|
29035
|
+
perplexity: "Perplexity"
|
|
29036
|
+
};
|
|
29037
|
+
var DEFAULT_PROVIDERS = ["openai", "anthropic", "google", "perplexity"];
|
|
29038
|
+
function generateRecommendationQueries(brand, industry) {
|
|
29039
|
+
const queries = [
|
|
29040
|
+
`What are the best ${brand.toLowerCase()} alternatives?`,
|
|
29041
|
+
`Can you recommend tools similar to ${brand}?`,
|
|
29042
|
+
`What do you think about ${brand}?`
|
|
29043
|
+
];
|
|
29044
|
+
if (industry) {
|
|
29045
|
+
queries.push(`What are the best ${industry} tools?`);
|
|
29046
|
+
queries.push(`Recommend a good ${industry} solution`);
|
|
29047
|
+
}
|
|
29048
|
+
return queries;
|
|
29049
|
+
}
|
|
29050
|
+
async function checkLLMCitations(brand, domain, options = {}) {
|
|
29051
|
+
const {
|
|
29052
|
+
providers = DEFAULT_PROVIDERS,
|
|
29053
|
+
queries = generateRecommendationQueries(brand),
|
|
29054
|
+
...trackingOptions
|
|
29055
|
+
} = options;
|
|
29056
|
+
const brandConfig = {
|
|
29057
|
+
brandName: brand,
|
|
29058
|
+
domains: [domain],
|
|
29059
|
+
alternativeNames: [brand.toLowerCase(), brand.toUpperCase()]
|
|
29060
|
+
};
|
|
29061
|
+
const query = queries[0];
|
|
29062
|
+
const results = await trackLLMVisibility(
|
|
29063
|
+
{
|
|
29064
|
+
keyword: query,
|
|
29065
|
+
brand: brandConfig,
|
|
29066
|
+
providers
|
|
29067
|
+
},
|
|
29068
|
+
trackingOptions
|
|
29069
|
+
);
|
|
29070
|
+
return results.map((result) => ({
|
|
29071
|
+
provider: result.provider,
|
|
29072
|
+
providerName: PROVIDER_NAMES[result.provider],
|
|
29073
|
+
mentioned: result.mentioned,
|
|
29074
|
+
position: result.position,
|
|
29075
|
+
sentiment: result.sentiment,
|
|
29076
|
+
context: result.contextSnippet || null,
|
|
29077
|
+
score: result.score,
|
|
29078
|
+
error: result.error
|
|
29079
|
+
}));
|
|
29080
|
+
}
|
|
29081
|
+
function calculateAIVisibilityScore(results) {
|
|
29082
|
+
if (results.length === 0) return 0;
|
|
29083
|
+
const mentionedCount = results.filter((r) => r.mentioned && !r.error).length;
|
|
29084
|
+
const validResults = results.filter((r) => !r.error);
|
|
29085
|
+
if (validResults.length === 0) return 0;
|
|
29086
|
+
let score = mentionedCount / validResults.length * 100;
|
|
29087
|
+
const topPositions = results.filter(
|
|
29088
|
+
(r) => r.mentioned && r.position !== null && r.position <= 3
|
|
29089
|
+
);
|
|
29090
|
+
if (topPositions.length > 0) {
|
|
29091
|
+
score = Math.min(100, score + 10);
|
|
29092
|
+
}
|
|
29093
|
+
const positiveResults = results.filter((r) => r.sentiment === "positive");
|
|
29094
|
+
if (positiveResults.length > mentionedCount / 2) {
|
|
29095
|
+
score = Math.min(100, score + 5);
|
|
29096
|
+
}
|
|
29097
|
+
return Math.round(score);
|
|
29098
|
+
}
|
|
29099
|
+
function getAIVisibilitySummary(results) {
|
|
29100
|
+
const score = calculateAIVisibilityScore(results);
|
|
29101
|
+
const mentionedIn = results.filter((r) => r.mentioned).map((r) => r.providerName);
|
|
29102
|
+
const notMentionedIn = results.filter((r) => !r.mentioned && !r.error).map((r) => r.providerName);
|
|
29103
|
+
const positions = results.filter((r) => r.position !== null).map((r) => r.position);
|
|
29104
|
+
const bestPosition = positions.length > 0 ? Math.min(...positions) : null;
|
|
29105
|
+
const sentiments = results.filter((r) => r.sentiment !== null).map((r) => r.sentiment);
|
|
29106
|
+
let overallSentiment = null;
|
|
29107
|
+
if (sentiments.length > 0) {
|
|
29108
|
+
const positiveCount = sentiments.filter((s) => s === "positive").length;
|
|
29109
|
+
const negativeCount = sentiments.filter((s) => s === "negative").length;
|
|
29110
|
+
if (positiveCount > 0 && negativeCount > 0) {
|
|
29111
|
+
overallSentiment = "mixed";
|
|
29112
|
+
} else if (positiveCount > negativeCount) {
|
|
29113
|
+
overallSentiment = "positive";
|
|
29114
|
+
} else if (negativeCount > positiveCount) {
|
|
29115
|
+
overallSentiment = "negative";
|
|
29116
|
+
} else {
|
|
29117
|
+
overallSentiment = "neutral";
|
|
29118
|
+
}
|
|
29119
|
+
}
|
|
29120
|
+
return {
|
|
29121
|
+
score,
|
|
29122
|
+
mentionedIn,
|
|
29123
|
+
notMentionedIn,
|
|
29124
|
+
bestPosition,
|
|
29125
|
+
overallSentiment
|
|
29126
|
+
};
|
|
29127
|
+
}
|
|
29128
|
+
|
|
28728
29129
|
// src/frameworks/index.ts
|
|
28729
29130
|
var frameworks_exports = {};
|
|
28730
29131
|
__export(frameworks_exports, {
|
|
@@ -29481,6 +29882,7 @@ if (typeof globalThis !== "undefined") {
|
|
|
29481
29882
|
applyFixes,
|
|
29482
29883
|
buildGSCApiRequest,
|
|
29483
29884
|
buildGSCRequest,
|
|
29885
|
+
calculateAIVisibilityScore,
|
|
29484
29886
|
calculateBM25,
|
|
29485
29887
|
calculateKeywordTFIDF,
|
|
29486
29888
|
calculateNextRun,
|
|
@@ -29495,6 +29897,7 @@ if (typeof globalThis !== "undefined") {
|
|
|
29495
29897
|
checkGitHubCLI,
|
|
29496
29898
|
checkInternalRedirects,
|
|
29497
29899
|
checkJSRenderingRatio,
|
|
29900
|
+
checkLLMCitations,
|
|
29498
29901
|
checkLlmsTxt,
|
|
29499
29902
|
checkMobileResources,
|
|
29500
29903
|
checkPlaintextEmails,
|
|
@@ -29598,6 +30001,7 @@ if (typeof globalThis !== "undefined") {
|
|
|
29598
30001
|
generatePDFReport,
|
|
29599
30002
|
generatePRDescription,
|
|
29600
30003
|
generateReactHelmetSocialMeta,
|
|
30004
|
+
generateRecommendationQueries,
|
|
29601
30005
|
generateRemixMeta,
|
|
29602
30006
|
generateSecretsDoc,
|
|
29603
30007
|
generateSocialMetaFix,
|
|
@@ -29605,6 +30009,7 @@ if (typeof globalThis !== "undefined") {
|
|
|
29605
30009
|
generateUncertaintyQuestions,
|
|
29606
30010
|
generateWizardQuestions,
|
|
29607
30011
|
generateWorkflow,
|
|
30012
|
+
getAIVisibilitySummary,
|
|
29608
30013
|
getAutocompleteSuggestions,
|
|
29609
30014
|
getDateRange,
|
|
29610
30015
|
getExpandedSuggestions,
|