@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.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, { decodeEntities: false });
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,