@rankcli/agent-runtime 0.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 (178) hide show
  1. package/README.md +242 -0
  2. package/dist/analyzer-2CSWIQGD.mjs +6 -0
  3. package/dist/chunk-YNZYHEYM.mjs +774 -0
  4. package/dist/index.d.mts +4012 -0
  5. package/dist/index.d.ts +4012 -0
  6. package/dist/index.js +29672 -0
  7. package/dist/index.mjs +28602 -0
  8. package/package.json +53 -0
  9. package/scripts/build-deno.ts +134 -0
  10. package/src/audit/ai/analyzer.ts +347 -0
  11. package/src/audit/ai/index.ts +29 -0
  12. package/src/audit/ai/prompts/content-analysis.ts +271 -0
  13. package/src/audit/ai/types.ts +179 -0
  14. package/src/audit/checks/additional-checks.ts +439 -0
  15. package/src/audit/checks/ai-citation-worthiness.ts +399 -0
  16. package/src/audit/checks/ai-content-structure.ts +325 -0
  17. package/src/audit/checks/ai-readiness.ts +339 -0
  18. package/src/audit/checks/anchor-text.ts +179 -0
  19. package/src/audit/checks/answer-conciseness.ts +322 -0
  20. package/src/audit/checks/asset-minification.ts +270 -0
  21. package/src/audit/checks/bing-optimization.ts +206 -0
  22. package/src/audit/checks/brand-mention-optimization.ts +349 -0
  23. package/src/audit/checks/caching-headers.ts +305 -0
  24. package/src/audit/checks/canonical-advanced.ts +150 -0
  25. package/src/audit/checks/canonical-domain.ts +196 -0
  26. package/src/audit/checks/citation-quality.ts +358 -0
  27. package/src/audit/checks/client-rendering.ts +542 -0
  28. package/src/audit/checks/color-contrast.ts +342 -0
  29. package/src/audit/checks/content-freshness.ts +170 -0
  30. package/src/audit/checks/content-science.ts +589 -0
  31. package/src/audit/checks/conversion-elements.ts +526 -0
  32. package/src/audit/checks/crawlability.ts +220 -0
  33. package/src/audit/checks/directory-listing.ts +172 -0
  34. package/src/audit/checks/dom-analysis.ts +191 -0
  35. package/src/audit/checks/dom-size.ts +246 -0
  36. package/src/audit/checks/duplicate-content.ts +194 -0
  37. package/src/audit/checks/eeat-signals.ts +990 -0
  38. package/src/audit/checks/entity-seo.ts +396 -0
  39. package/src/audit/checks/featured-snippet.ts +473 -0
  40. package/src/audit/checks/freshness-signals.ts +443 -0
  41. package/src/audit/checks/funnel-intent.ts +463 -0
  42. package/src/audit/checks/hreflang.ts +174 -0
  43. package/src/audit/checks/html-compliance.ts +302 -0
  44. package/src/audit/checks/image-dimensions.ts +167 -0
  45. package/src/audit/checks/images.ts +160 -0
  46. package/src/audit/checks/indexnow.ts +275 -0
  47. package/src/audit/checks/interactive-tools.ts +475 -0
  48. package/src/audit/checks/internal-link-graph.ts +436 -0
  49. package/src/audit/checks/keyword-analysis.ts +239 -0
  50. package/src/audit/checks/keyword-cannibalization.ts +385 -0
  51. package/src/audit/checks/keyword-placement.ts +471 -0
  52. package/src/audit/checks/links.ts +203 -0
  53. package/src/audit/checks/llms-txt.ts +224 -0
  54. package/src/audit/checks/local-seo.ts +296 -0
  55. package/src/audit/checks/mobile.ts +167 -0
  56. package/src/audit/checks/modern-images.ts +226 -0
  57. package/src/audit/checks/navboost-signals.ts +395 -0
  58. package/src/audit/checks/on-page.ts +209 -0
  59. package/src/audit/checks/page-resources.ts +285 -0
  60. package/src/audit/checks/pagination.ts +180 -0
  61. package/src/audit/checks/performance.ts +153 -0
  62. package/src/audit/checks/platform-presence.ts +580 -0
  63. package/src/audit/checks/redirect-analysis.ts +153 -0
  64. package/src/audit/checks/redirect-chain.ts +389 -0
  65. package/src/audit/checks/resource-hints.ts +420 -0
  66. package/src/audit/checks/responsive-css.ts +247 -0
  67. package/src/audit/checks/responsive-images.ts +396 -0
  68. package/src/audit/checks/review-ecosystem.ts +415 -0
  69. package/src/audit/checks/robots-validation.ts +373 -0
  70. package/src/audit/checks/security-headers.ts +172 -0
  71. package/src/audit/checks/security.ts +144 -0
  72. package/src/audit/checks/serp-preview.ts +251 -0
  73. package/src/audit/checks/site-maturity.ts +444 -0
  74. package/src/audit/checks/social-meta.test.ts +275 -0
  75. package/src/audit/checks/social-meta.ts +134 -0
  76. package/src/audit/checks/soft-404.ts +151 -0
  77. package/src/audit/checks/structured-data.ts +238 -0
  78. package/src/audit/checks/tech-detection.ts +496 -0
  79. package/src/audit/checks/topical-clusters.ts +435 -0
  80. package/src/audit/checks/tracker-bloat.ts +462 -0
  81. package/src/audit/checks/tracking-verification.test.ts +371 -0
  82. package/src/audit/checks/tracking-verification.ts +636 -0
  83. package/src/audit/checks/url-safety.ts +682 -0
  84. package/src/audit/deno-entry.ts +66 -0
  85. package/src/audit/discovery/index.ts +15 -0
  86. package/src/audit/discovery/link-crawler.ts +232 -0
  87. package/src/audit/discovery/repo-routes.ts +347 -0
  88. package/src/audit/engine.ts +620 -0
  89. package/src/audit/fixes/index.ts +209 -0
  90. package/src/audit/fixes/social-meta-fixes.test.ts +329 -0
  91. package/src/audit/fixes/social-meta-fixes.ts +463 -0
  92. package/src/audit/index.ts +74 -0
  93. package/src/audit/runner.test.ts +299 -0
  94. package/src/audit/runner.ts +130 -0
  95. package/src/audit/types.ts +1953 -0
  96. package/src/content/featured-snippet.ts +367 -0
  97. package/src/content/generator.test.ts +534 -0
  98. package/src/content/generator.ts +501 -0
  99. package/src/content/headline.ts +317 -0
  100. package/src/content/index.ts +62 -0
  101. package/src/content/intent.ts +258 -0
  102. package/src/content/keyword-density.ts +349 -0
  103. package/src/content/readability.ts +262 -0
  104. package/src/executor.ts +336 -0
  105. package/src/fixer.ts +416 -0
  106. package/src/frameworks/detector.test.ts +248 -0
  107. package/src/frameworks/detector.ts +371 -0
  108. package/src/frameworks/index.ts +68 -0
  109. package/src/frameworks/recipes/angular.yaml +171 -0
  110. package/src/frameworks/recipes/astro.yaml +206 -0
  111. package/src/frameworks/recipes/django.yaml +180 -0
  112. package/src/frameworks/recipes/laravel.yaml +137 -0
  113. package/src/frameworks/recipes/nextjs.yaml +268 -0
  114. package/src/frameworks/recipes/nuxt.yaml +175 -0
  115. package/src/frameworks/recipes/rails.yaml +188 -0
  116. package/src/frameworks/recipes/react.yaml +202 -0
  117. package/src/frameworks/recipes/sveltekit.yaml +154 -0
  118. package/src/frameworks/recipes/vue.yaml +137 -0
  119. package/src/frameworks/recipes/wordpress.yaml +209 -0
  120. package/src/frameworks/suggestion-engine.ts +320 -0
  121. package/src/geo/geo-content.test.ts +305 -0
  122. package/src/geo/geo-content.ts +266 -0
  123. package/src/geo/geo-history.test.ts +473 -0
  124. package/src/geo/geo-history.ts +433 -0
  125. package/src/geo/geo-tracker.test.ts +359 -0
  126. package/src/geo/geo-tracker.ts +411 -0
  127. package/src/geo/index.ts +10 -0
  128. package/src/git/commit-helper.test.ts +261 -0
  129. package/src/git/commit-helper.ts +329 -0
  130. package/src/git/index.ts +12 -0
  131. package/src/git/pr-helper.test.ts +284 -0
  132. package/src/git/pr-helper.ts +307 -0
  133. package/src/index.ts +66 -0
  134. package/src/keywords/ai-keyword-engine.ts +1062 -0
  135. package/src/keywords/ai-summarizer.ts +387 -0
  136. package/src/keywords/ci-mode.ts +555 -0
  137. package/src/keywords/engine.ts +359 -0
  138. package/src/keywords/index.ts +151 -0
  139. package/src/keywords/llm-judge.ts +357 -0
  140. package/src/keywords/nlp-analysis.ts +706 -0
  141. package/src/keywords/prioritizer.ts +295 -0
  142. package/src/keywords/site-crawler.ts +342 -0
  143. package/src/keywords/sources/autocomplete.ts +139 -0
  144. package/src/keywords/sources/competitive-search.ts +450 -0
  145. package/src/keywords/sources/competitor-analysis.ts +374 -0
  146. package/src/keywords/sources/dataforseo.ts +206 -0
  147. package/src/keywords/sources/free-sources.ts +294 -0
  148. package/src/keywords/sources/gsc.ts +123 -0
  149. package/src/keywords/topic-grouping.ts +327 -0
  150. package/src/keywords/types.ts +144 -0
  151. package/src/keywords/wizard.ts +457 -0
  152. package/src/loader.ts +40 -0
  153. package/src/reports/index.ts +7 -0
  154. package/src/reports/report-generator.test.ts +293 -0
  155. package/src/reports/report-generator.ts +713 -0
  156. package/src/scheduler/alerts.test.ts +458 -0
  157. package/src/scheduler/alerts.ts +328 -0
  158. package/src/scheduler/index.ts +8 -0
  159. package/src/scheduler/scheduled-audit.test.ts +377 -0
  160. package/src/scheduler/scheduled-audit.ts +149 -0
  161. package/src/test/integration-test.ts +325 -0
  162. package/src/tools/analyzer.ts +373 -0
  163. package/src/tools/crawl.ts +293 -0
  164. package/src/tools/files.ts +301 -0
  165. package/src/tools/h1-fixer.ts +249 -0
  166. package/src/tools/index.ts +67 -0
  167. package/src/tracking/github-action.ts +326 -0
  168. package/src/tracking/google-analytics.ts +265 -0
  169. package/src/tracking/index.ts +45 -0
  170. package/src/tracking/report-generator.ts +386 -0
  171. package/src/tracking/search-console.ts +335 -0
  172. package/src/types.ts +134 -0
  173. package/src/utils/http.ts +302 -0
  174. package/src/wasm-adapter.ts +297 -0
  175. package/src/wasm-entry.ts +14 -0
  176. package/tsconfig.json +17 -0
  177. package/tsup.wasm.config.ts +26 -0
  178. package/vitest.config.ts +15 -0
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@rankcli/agent-runtime",
3
+ "version": "0.0.1",
4
+ "description": "RankCLI agent runtime - executes SEO audits and fixes with AI",
5
+ "homepage": "https://rankcli.dev",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.mjs",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.js"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "dev": "pnpm build:deno && tsup src/index.ts --watch --onSuccess 'pnpm build:deno'",
18
+ "dev:node": "tsup src/index.ts --watch",
19
+ "dev:deno": "tsx scripts/build-deno.ts --watch",
20
+ "build": "tsup src/index.ts --format cjs,esm --dts && pnpm build:deno",
21
+ "build:deno": "tsx scripts/build-deno.ts",
22
+ "build:wasm": "tsup --config tsup.wasm.config.ts",
23
+ "lint": "eslint src/",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest",
26
+ "test:coverage": "vitest run --coverage"
27
+ },
28
+ "dependencies": {
29
+ "@anthropic-ai/sdk": "^0.52.0",
30
+ "cheerio": "^1.0.0",
31
+ "openai": "^6.16.0",
32
+ "yaml": "^2.3.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^20.0.0",
36
+ "esbuild": "^0.20.0",
37
+ "tsup": "^8.0.0",
38
+ "tsx": "^4.0.0",
39
+ "typescript": "^5.3.0",
40
+ "vitest": "^1.0.0"
41
+ },
42
+ "keywords": [
43
+ "seo",
44
+ "agent",
45
+ "claude",
46
+ "ai"
47
+ ],
48
+ "author": "Brian Sam-Bodden",
49
+ "license": "MIT",
50
+ "engines": {
51
+ "node": ">=18.0.0"
52
+ }
53
+ }
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Build script to create a Deno-compatible bundle of the audit engine.
4
+ * This bundle can be used by Supabase Edge Functions.
5
+ *
6
+ * Usage:
7
+ * pnpm build:deno # One-time build
8
+ * pnpm build:deno --watch # Watch mode for development
9
+ */
10
+
11
+ import { build, context } from 'esbuild';
12
+ import { writeFileSync, readFileSync, mkdirSync } from 'fs';
13
+ import { join } from 'path';
14
+
15
+ const OUTPUT_DIR = join(__dirname, '../../saas/supabase/functions/_shared/audit');
16
+ const WATCH_MODE = process.argv.includes('--watch');
17
+
18
+ const buildOptions = {
19
+ entryPoints: [join(__dirname, '../src/audit/deno-entry.ts')],
20
+ bundle: true,
21
+ format: 'esm' as const,
22
+ platform: 'neutral' as const,
23
+ target: 'es2022',
24
+ outfile: join(OUTPUT_DIR, 'engine.bundle.js'),
25
+ external: ['cheerio'], // Will be imported from esm.sh in Deno
26
+ minify: false,
27
+ sourcemap: false,
28
+ define: {
29
+ 'process.env.NODE_ENV': '"production"',
30
+ },
31
+ banner: {
32
+ js: `// Auto-generated Deno-compatible audit bundle
33
+ // Do not edit manually - regenerate with: pnpm build:deno
34
+ // Generated: ${new Date().toISOString()}
35
+
36
+ // Polyfill for Node's Buffer if needed
37
+ if (typeof globalThis.Buffer === 'undefined') {
38
+ globalThis.Buffer = {
39
+ from: (str, encoding) => new TextEncoder().encode(str),
40
+ isBuffer: () => false
41
+ };
42
+ }
43
+ `,
44
+ },
45
+ };
46
+
47
+ function postProcessBundle() {
48
+ // Replace bare cheerio imports with esm.sh URL
49
+ const bundlePath = join(OUTPUT_DIR, 'engine.bundle.js');
50
+ let content = readFileSync(bundlePath, 'utf-8');
51
+
52
+ // Replace various import patterns for cheerio
53
+ content = content
54
+ .replace(/from\s*["']cheerio["']/g, 'from "https://esm.sh/cheerio@1.0.0"')
55
+ .replace(/import\s*\*\s*as\s*cheerio\s*from\s*["']cheerio["']/g,
56
+ 'import * as cheerio from "https://esm.sh/cheerio@1.0.0"')
57
+ .replace(/require\s*\(\s*["']cheerio["']\s*\)/g,
58
+ 'await import("https://esm.sh/cheerio@1.0.0")');
59
+
60
+ writeFileSync(bundlePath, content);
61
+ }
62
+
63
+ function writeWrapper() {
64
+ const wrapper = `// Deno wrapper for audit engine
65
+ // Re-export everything from the bundle
66
+ export * from './engine.bundle.js';
67
+ `;
68
+ writeFileSync(join(OUTPUT_DIR, 'index.ts'), wrapper);
69
+ }
70
+
71
+ async function buildDenoBundle() {
72
+ console.log('Building Deno-compatible audit bundle...');
73
+
74
+ // Ensure output directory exists
75
+ mkdirSync(OUTPUT_DIR, { recursive: true });
76
+
77
+ try {
78
+ if (WATCH_MODE) {
79
+ // Watch mode using esbuild context
80
+ const ctx = await context({
81
+ ...buildOptions,
82
+ plugins: [{
83
+ name: 'rebuild-notify',
84
+ setup(build) {
85
+ build.onEnd(result => {
86
+ if (result.errors.length > 0) {
87
+ console.error(`[${new Date().toLocaleTimeString()}] Deno build failed:`, result.errors);
88
+ } else {
89
+ postProcessBundle();
90
+ writeWrapper();
91
+ console.log(`[${new Date().toLocaleTimeString()}] ✓ Deno bundle rebuilt`);
92
+ }
93
+ });
94
+ },
95
+ }],
96
+ });
97
+
98
+ await ctx.watch();
99
+ console.log('Watching for changes in audit module...');
100
+ console.log('Press Ctrl+C to stop.\n');
101
+
102
+ // Keep the process running
103
+ process.on('SIGINT', async () => {
104
+ await ctx.dispose();
105
+ console.log('\nStopped watching.');
106
+ process.exit(0);
107
+ });
108
+ } else {
109
+ // One-time build
110
+ const result = await build(buildOptions);
111
+
112
+ if (result.errors.length > 0) {
113
+ console.error('Build errors:', result.errors);
114
+ process.exit(1);
115
+ }
116
+
117
+ console.log(`✓ Bundle created at ${OUTPUT_DIR}/engine.bundle.js`);
118
+
119
+ postProcessBundle();
120
+ console.log(`✓ Cheerio imports replaced with esm.sh URLs`);
121
+
122
+ writeWrapper();
123
+ console.log(`✓ Wrapper created at ${OUTPUT_DIR}/index.ts`);
124
+
125
+ console.log('\nDeno bundle ready for use in edge functions!');
126
+ console.log('Import with: import { runFullAudit } from "../_shared/audit/index.ts"');
127
+ }
128
+ } catch (error) {
129
+ console.error('Build failed:', error);
130
+ process.exit(1);
131
+ }
132
+ }
133
+
134
+ buildDenoBundle();
@@ -0,0 +1,347 @@
1
+ // AI-Powered SEO Analyzer
2
+ // Uses LLMs to provide contextual, intelligent SEO analysis
3
+
4
+ import type {
5
+ AIConfig,
6
+ AIContentAnalysis,
7
+ AIMetaAnalysis,
8
+ AISchemaAnalysis,
9
+ AIVisibilityAnalysis,
10
+ AIFixPrioritization,
11
+ AIAnalysisResult,
12
+ PageContext,
13
+ } from './types.js';
14
+
15
+ import {
16
+ CONTENT_ANALYSIS_SYSTEM,
17
+ CONTENT_ANALYSIS_USER,
18
+ META_ANALYSIS_SYSTEM,
19
+ META_ANALYSIS_USER,
20
+ SCHEMA_ANALYSIS_SYSTEM,
21
+ SCHEMA_ANALYSIS_USER,
22
+ AI_VISIBILITY_SYSTEM,
23
+ AI_VISIBILITY_USER,
24
+ FIX_PRIORITIZATION_SYSTEM,
25
+ FIX_PRIORITIZATION_USER,
26
+ } from './prompts/content-analysis.js';
27
+
28
+ export class AIAnalyzer {
29
+ private config: AIConfig;
30
+ private cache: Map<string, { result: unknown; timestamp: number }> = new Map();
31
+ private cacheTimeout = 3600000; // 1 hour
32
+
33
+ constructor(config: AIConfig) {
34
+ this.config = {
35
+ model: 'gpt-4o',
36
+ maxTokens: 4096,
37
+ temperature: 0.3,
38
+ ...config,
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Call OpenAI API with structured JSON response
44
+ */
45
+ private async callOpenAI<T>(
46
+ systemPrompt: string,
47
+ userPrompt: string,
48
+ cacheKey?: string
49
+ ): Promise<{ result: T; tokensUsed: number; cached: boolean }> {
50
+ // Check cache
51
+ if (cacheKey) {
52
+ const cached = this.cache.get(cacheKey);
53
+ if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
54
+ return { result: cached.result as T, tokensUsed: 0, cached: true };
55
+ }
56
+ }
57
+
58
+ const response = await fetch('https://api.openai.com/v1/chat/completions', {
59
+ method: 'POST',
60
+ headers: {
61
+ 'Content-Type': 'application/json',
62
+ Authorization: `Bearer ${this.config.openaiApiKey}`,
63
+ },
64
+ body: JSON.stringify({
65
+ model: this.config.model,
66
+ messages: [
67
+ { role: 'system', content: systemPrompt },
68
+ { role: 'user', content: userPrompt },
69
+ ],
70
+ max_tokens: this.config.maxTokens,
71
+ temperature: this.config.temperature,
72
+ response_format: { type: 'json_object' },
73
+ }),
74
+ });
75
+
76
+ if (!response.ok) {
77
+ const error = await response.text();
78
+ throw new Error(`OpenAI API error: ${response.status} - ${error}`);
79
+ }
80
+
81
+ const data = await response.json();
82
+ const content = data.choices?.[0]?.message?.content;
83
+ const tokensUsed = data.usage?.total_tokens || 0;
84
+
85
+ if (!content) {
86
+ throw new Error('No content in OpenAI response');
87
+ }
88
+
89
+ let result: T;
90
+ try {
91
+ result = JSON.parse(content) as T;
92
+ } catch {
93
+ throw new Error(`Failed to parse OpenAI JSON response: ${content}`);
94
+ }
95
+
96
+ // Cache result
97
+ if (cacheKey) {
98
+ this.cache.set(cacheKey, { result, timestamp: Date.now() });
99
+ }
100
+
101
+ return { result, tokensUsed, cached: false };
102
+ }
103
+
104
+ /**
105
+ * Analyze content quality using AI
106
+ */
107
+ async analyzeContent(context: PageContext): Promise<{
108
+ analysis: AIContentAnalysis;
109
+ tokensUsed: number;
110
+ cached: boolean;
111
+ }> {
112
+ const cacheKey = `content:${context.url}:${context.textContent.length}`;
113
+
114
+ const { result, tokensUsed, cached } = await this.callOpenAI<AIContentAnalysis>(
115
+ CONTENT_ANALYSIS_SYSTEM,
116
+ CONTENT_ANALYSIS_USER({
117
+ url: context.url,
118
+ title: context.title,
119
+ h1: context.h1,
120
+ metaDescription: context.metaDescription,
121
+ textContent: context.textContent,
122
+ wordCount: context.wordCount,
123
+ headings: context.headings,
124
+ }),
125
+ cacheKey
126
+ );
127
+
128
+ return { analysis: result, tokensUsed, cached };
129
+ }
130
+
131
+ /**
132
+ * Analyze and optimize meta tags using AI
133
+ */
134
+ async analyzeMeta(context: PageContext): Promise<{
135
+ analysis: AIMetaAnalysis;
136
+ tokensUsed: number;
137
+ cached: boolean;
138
+ }> {
139
+ const cacheKey = `meta:${context.url}:${context.title}:${context.metaDescription}`;
140
+
141
+ const { result, tokensUsed, cached } = await this.callOpenAI<AIMetaAnalysis>(
142
+ META_ANALYSIS_SYSTEM,
143
+ META_ANALYSIS_USER({
144
+ url: context.url,
145
+ title: context.title,
146
+ metaDescription: context.metaDescription,
147
+ h1: context.h1,
148
+ pageType: context.industry,
149
+ industry: context.industry,
150
+ }),
151
+ cacheKey
152
+ );
153
+
154
+ return { analysis: result, tokensUsed, cached };
155
+ }
156
+
157
+ /**
158
+ * Analyze and generate structured data using AI
159
+ */
160
+ async analyzeSchema(context: PageContext): Promise<{
161
+ analysis: AISchemaAnalysis;
162
+ tokensUsed: number;
163
+ cached: boolean;
164
+ }> {
165
+ const cacheKey = `schema:${context.url}`;
166
+
167
+ // Detect page type from URL and content
168
+ let pageType = 'other';
169
+ const urlLower = context.url.toLowerCase();
170
+ const contentLower = context.textContent.toLowerCase();
171
+
172
+ if (urlLower.includes('/blog/') || urlLower.includes('/article/') || urlLower.includes('/post/')) {
173
+ pageType = 'article';
174
+ } else if (urlLower.includes('/product/') || contentLower.includes('add to cart') || contentLower.includes('buy now')) {
175
+ pageType = 'product';
176
+ } else if (urlLower.includes('/faq') || contentLower.includes('frequently asked')) {
177
+ pageType = 'faq';
178
+ } else if (urlLower.includes('/about') || urlLower.includes('/team')) {
179
+ pageType = 'organization';
180
+ } else if (urlLower.includes('/contact')) {
181
+ pageType = 'local-business';
182
+ }
183
+
184
+ const { result, tokensUsed, cached } = await this.callOpenAI<AISchemaAnalysis>(
185
+ SCHEMA_ANALYSIS_SYSTEM,
186
+ SCHEMA_ANALYSIS_USER({
187
+ url: context.url,
188
+ title: context.title,
189
+ textContent: context.textContent,
190
+ existingSchema: context.schema,
191
+ pageType,
192
+ }),
193
+ cacheKey
194
+ );
195
+
196
+ return { analysis: result, tokensUsed, cached };
197
+ }
198
+
199
+ /**
200
+ * Analyze AI/LLM visibility and optimization
201
+ */
202
+ async analyzeAIVisibility(
203
+ context: PageContext,
204
+ botAccess: { hasLlmsTxt: boolean; llmsTxtContent?: string; blockedBots: string[] }
205
+ ): Promise<{
206
+ analysis: AIVisibilityAnalysis;
207
+ tokensUsed: number;
208
+ cached: boolean;
209
+ }> {
210
+ const cacheKey = `aivis:${context.url}`;
211
+
212
+ const { result, tokensUsed, cached } = await this.callOpenAI<AIVisibilityAnalysis>(
213
+ AI_VISIBILITY_SYSTEM,
214
+ AI_VISIBILITY_USER({
215
+ url: context.url,
216
+ title: context.title,
217
+ textContent: context.textContent,
218
+ headings: context.headings,
219
+ hasLlmsTxt: botAccess.hasLlmsTxt,
220
+ llmsTxtContent: botAccess.llmsTxtContent,
221
+ blockedBots: botAccess.blockedBots,
222
+ }),
223
+ cacheKey
224
+ );
225
+
226
+ return { analysis: result, tokensUsed, cached };
227
+ }
228
+
229
+ /**
230
+ * Prioritize fixes using AI
231
+ */
232
+ async prioritizeFixes(
233
+ issues: { code: string; severity: string; title: string; category: string }[],
234
+ context: { url: string; framework?: string }
235
+ ): Promise<{
236
+ analysis: AIFixPrioritization;
237
+ tokensUsed: number;
238
+ cached: boolean;
239
+ }> {
240
+ // Don't cache fix prioritization - issues change each audit
241
+ const { result, tokensUsed, cached } = await this.callOpenAI<AIFixPrioritization>(
242
+ FIX_PRIORITIZATION_SYSTEM,
243
+ FIX_PRIORITIZATION_USER({
244
+ issues,
245
+ url: context.url,
246
+ framework: context.framework,
247
+ })
248
+ );
249
+
250
+ return { analysis: result, tokensUsed, cached };
251
+ }
252
+
253
+ /**
254
+ * Run full AI analysis on a page
255
+ */
256
+ async runFullAnalysis(
257
+ context: PageContext,
258
+ options: {
259
+ analyzeContent?: boolean;
260
+ analyzeMeta?: boolean;
261
+ analyzeSchema?: boolean;
262
+ analyzeAIVisibility?: boolean;
263
+ prioritizeFixes?: boolean;
264
+ issues?: { code: string; severity: string; title: string; category: string }[];
265
+ botAccess?: { hasLlmsTxt: boolean; llmsTxtContent?: string; blockedBots: string[] };
266
+ } = {}
267
+ ): Promise<AIAnalysisResult> {
268
+ const startTime = Date.now();
269
+ let totalTokens = 0;
270
+ let anyCached = false;
271
+
272
+ const result: AIAnalysisResult = {
273
+ analysisTime: 0,
274
+ tokensUsed: 0,
275
+ modelUsed: this.config.model || 'gpt-4o',
276
+ cached: false,
277
+ };
278
+
279
+ // Run analyses in parallel where possible
280
+ const analyses = await Promise.allSettled([
281
+ options.analyzeContent !== false
282
+ ? this.analyzeContent(context)
283
+ : Promise.resolve(null),
284
+ options.analyzeMeta !== false
285
+ ? this.analyzeMeta(context)
286
+ : Promise.resolve(null),
287
+ options.analyzeSchema !== false
288
+ ? this.analyzeSchema(context)
289
+ : Promise.resolve(null),
290
+ options.analyzeAIVisibility !== false && options.botAccess
291
+ ? this.analyzeAIVisibility(context, options.botAccess)
292
+ : Promise.resolve(null),
293
+ options.prioritizeFixes && options.issues
294
+ ? this.prioritizeFixes(options.issues, { url: context.url, framework: context.framework })
295
+ : Promise.resolve(null),
296
+ ]);
297
+
298
+ // Process results
299
+ if (analyses[0].status === 'fulfilled' && analyses[0].value) {
300
+ result.content = analyses[0].value.analysis;
301
+ totalTokens += analyses[0].value.tokensUsed;
302
+ if (analyses[0].value.cached) anyCached = true;
303
+ }
304
+
305
+ if (analyses[1].status === 'fulfilled' && analyses[1].value) {
306
+ result.meta = analyses[1].value.analysis;
307
+ totalTokens += analyses[1].value.tokensUsed;
308
+ if (analyses[1].value.cached) anyCached = true;
309
+ }
310
+
311
+ if (analyses[2].status === 'fulfilled' && analyses[2].value) {
312
+ result.schema = analyses[2].value.analysis;
313
+ totalTokens += analyses[2].value.tokensUsed;
314
+ if (analyses[2].value.cached) anyCached = true;
315
+ }
316
+
317
+ if (analyses[3].status === 'fulfilled' && analyses[3].value) {
318
+ result.aiVisibility = analyses[3].value.analysis;
319
+ totalTokens += analyses[3].value.tokensUsed;
320
+ if (analyses[3].value.cached) anyCached = true;
321
+ }
322
+
323
+ if (analyses[4].status === 'fulfilled' && analyses[4].value) {
324
+ result.fixPrioritization = analyses[4].value.analysis;
325
+ totalTokens += analyses[4].value.tokensUsed;
326
+ if (analyses[4].value.cached) anyCached = true;
327
+ }
328
+
329
+ result.analysisTime = Date.now() - startTime;
330
+ result.tokensUsed = totalTokens;
331
+ result.cached = anyCached;
332
+
333
+ return result;
334
+ }
335
+
336
+ /**
337
+ * Clear the cache
338
+ */
339
+ clearCache(): void {
340
+ this.cache.clear();
341
+ }
342
+ }
343
+
344
+ // Export a factory function for creating analyzers
345
+ export function createAIAnalyzer(config: AIConfig): AIAnalyzer {
346
+ return new AIAnalyzer(config);
347
+ }
@@ -0,0 +1,29 @@
1
+ // AI-Powered SEO Analysis Module
2
+ // Exports for use in audits and Edge Functions
3
+
4
+ export { AIAnalyzer, createAIAnalyzer } from './analyzer.js';
5
+
6
+ export type {
7
+ AIConfig,
8
+ AIContentAnalysis,
9
+ AIMetaAnalysis,
10
+ AISchemaAnalysis,
11
+ AILinkAnalysis,
12
+ AIVisibilityAnalysis,
13
+ AIFixPrioritization,
14
+ AIAnalysisResult,
15
+ PageContext,
16
+ } from './types.js';
17
+
18
+ export {
19
+ CONTENT_ANALYSIS_SYSTEM,
20
+ CONTENT_ANALYSIS_USER,
21
+ META_ANALYSIS_SYSTEM,
22
+ META_ANALYSIS_USER,
23
+ SCHEMA_ANALYSIS_SYSTEM,
24
+ SCHEMA_ANALYSIS_USER,
25
+ AI_VISIBILITY_SYSTEM,
26
+ AI_VISIBILITY_USER,
27
+ FIX_PRIORITIZATION_SYSTEM,
28
+ FIX_PRIORITIZATION_USER,
29
+ } from './prompts/content-analysis.js';