@ai-lighthouse/cli 1.0.1 → 1.0.2

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 (102) hide show
  1. package/dist/index.js +2573 -12
  2. package/package.json +10 -4
  3. package/.ai-lighthouse/audit_example.com_2025-12-15T12-10-43.json +0 -183
  4. package/.ai-lighthouse/audit_fayeed.dev_2026-01-07T19-32-28.html +0 -743
  5. package/.ai-lighthouse/audit_fayeed.dev_2026-01-07T19-33-02.html +0 -757
  6. package/.ai-lighthouse/audit_github.com_2025-12-15T11-53-21.json +0 -168
  7. package/.ai-lighthouse/audit_github.com_2025-12-15T12-04-06.json +0 -168
  8. package/.ai-lighthouse/audit_github.com_2025-12-15T12-05-10.json +0 -168
  9. package/.ai-lighthouse/audit_github.com_2025-12-15T12-09-45.json +0 -168
  10. package/.ai-lighthouse/audit_github.com_2025-12-15T12-11-07.json +0 -168
  11. package/.ai-lighthouse/audit_github.com_2025-12-15T12-13-28.json +0 -168
  12. package/.ai-lighthouse/audit_github.com_2025-12-15T12-14-59.json +0 -205
  13. package/.ai-lighthouse/audit_github.com_2025-12-15T12-18-07.json +0 -205
  14. package/.ai-lighthouse/audit_github.com_2025-12-15T12-18-44.json +0 -205
  15. package/.ai-lighthouse/audit_github.com_2025-12-15T12-21-38.json +0 -205
  16. package/.ai-lighthouse/audit_github.com_2025-12-15T12-22-21.json +0 -205
  17. package/.ai-lighthouse/audit_github.com_2025-12-15T12-22-46.json +0 -205
  18. package/.ai-lighthouse/audit_github.com_2025-12-15T12-23-18.json +0 -205
  19. package/.ai-lighthouse/audit_github.com_2025-12-15T12-24-43.json +0 -205
  20. package/.ai-lighthouse/audit_github.com_2025-12-17T12-15-08.json +0 -168
  21. package/.ai-lighthouse/audit_github.com_2025-12-17T12-15-57.json +0 -168
  22. package/.ai-lighthouse/audit_github.com_2025-12-17T12-17-11.json +0 -168
  23. package/.ai-lighthouse/audit_github.com_2025-12-17T12-22-17.json +0 -168
  24. package/.ai-lighthouse/audit_github.com_2025-12-17T12-22-42.json +0 -168
  25. package/.ai-lighthouse/audit_github.com_2025-12-17T12-23-56.json +0 -168
  26. package/.ai-lighthouse/audit_github.com_2025-12-17T12-25-24.json +0 -168
  27. package/.ai-lighthouse/audit_github.com_2025-12-17T12-25-40.json +0 -168
  28. package/.ai-lighthouse/audit_github.com_2025-12-17T12-27-02.json +0 -168
  29. package/.ai-lighthouse/audit_github.com_2025-12-17T12-27-20.json +0 -168
  30. package/.ai-lighthouse/audit_github.com_2025-12-17T12-29-56.json +0 -168
  31. package/.ai-lighthouse/audit_github.com_2025-12-17T12-32-27.json +0 -168
  32. package/.ai-lighthouse/audit_github.com_2025-12-17T12-33-00.json +0 -168
  33. package/.ai-lighthouse/audit_github.com_2025-12-17T12-34-49.json +0 -168
  34. package/.ai-lighthouse/audit_stripe.com_2025-12-15T12-11-31.json +0 -168
  35. package/.ai-lighthouse/audit_stripe.com_2025-12-15T12-11-45.json +0 -168
  36. package/.ai-lighthouse/audit_tailwindcss.com_2025-12-15T12-12-01.json +0 -169
  37. package/.ai-lighthouse/crawl_example.com_2025-12-15T12-03-08.json +0 -24
  38. package/.ai-lighthouse/crawl_example.com_2025-12-15T12-03-23.json +0 -24
  39. package/.ai-lighthouse/crawl_github.com_2025-12-15T11-41-34.json +0 -21
  40. package/.ai-lighthouse/crawl_github.com_2025-12-15T11-42-09.json +0 -21
  41. package/.ai-lighthouse/crawl_github.com_2025-12-15T11-42-45.json +0 -21
  42. package/.ai-lighthouse/crawl_github.com_2025-12-15T11-43-02.json +0 -21
  43. package/.ai-lighthouse/crawl_github.com_2025-12-15T11-43-26.json +0 -21
  44. package/.ai-lighthouse/crawl_github.com_2025-12-15T11-47-46.json +0 -906
  45. package/.ai-lighthouse/crawl_github.com_2025-12-15T11-50-27.json +0 -906
  46. package/.ai-lighthouse/crawl_github.com_2025-12-15T11-52-59.json +0 -906
  47. package/.ai-lighthouse/crawl_github.com_2025-12-15T12-03-33.json +0 -28
  48. package/CLI_UI_README.md +0 -211
  49. package/EXAMPLES.md +0 -87
  50. package/IMPLEMENTATION.md +0 -215
  51. package/USAGE.md +0 -264
  52. package/WIZARD_GUIDE.md +0 -340
  53. package/bin/cli.js +0 -2
  54. package/dist/commands/audit-interactive.d.ts +0 -2
  55. package/dist/commands/audit-interactive.js +0 -106
  56. package/dist/commands/audit-wizard.d.ts +0 -2
  57. package/dist/commands/audit-wizard.js +0 -110
  58. package/dist/commands/audit.d.ts +0 -2
  59. package/dist/commands/audit.js +0 -940
  60. package/dist/commands/crawl.d.ts +0 -2
  61. package/dist/commands/crawl.js +0 -267
  62. package/dist/commands/report.d.ts +0 -2
  63. package/dist/commands/report.js +0 -304
  64. package/dist/index.d.ts +0 -1
  65. package/dist/ui/AuditReportUI.d.ts +0 -10
  66. package/dist/ui/AuditReportUI.js +0 -76
  67. package/dist/ui/SetupWizard.d.ts +0 -18
  68. package/dist/ui/SetupWizard.js +0 -179
  69. package/dist/ui/components/AIUnderstandingSection.d.ts +0 -6
  70. package/dist/ui/components/AIUnderstandingSection.js +0 -87
  71. package/dist/ui/components/HallucinationSection.d.ts +0 -6
  72. package/dist/ui/components/HallucinationSection.js +0 -84
  73. package/dist/ui/components/IssuesSection.d.ts +0 -6
  74. package/dist/ui/components/IssuesSection.js +0 -84
  75. package/dist/ui/components/MessageAlignmentSection.d.ts +0 -6
  76. package/dist/ui/components/MessageAlignmentSection.js +0 -108
  77. package/dist/ui/components/OverviewSection.d.ts +0 -6
  78. package/dist/ui/components/OverviewSection.js +0 -107
  79. package/dist/ui/components/ScoreDisplay.d.ts +0 -8
  80. package/dist/ui/components/ScoreDisplay.js +0 -41
  81. package/dist/ui/components/TechnicalSection.d.ts +0 -7
  82. package/dist/ui/components/TechnicalSection.js +0 -110
  83. package/dist/utils/comprehensive-formatter.d.ts +0 -5
  84. package/dist/utils/comprehensive-formatter.js +0 -370
  85. package/src/commands/audit-interactive.ts +0 -149
  86. package/src/commands/audit-wizard.ts +0 -137
  87. package/src/commands/audit.ts +0 -1012
  88. package/src/commands/crawl.ts +0 -307
  89. package/src/commands/report.ts +0 -321
  90. package/src/index.ts +0 -22
  91. package/src/ui/AuditReportUI.tsx +0 -151
  92. package/src/ui/SetupWizard.tsx +0 -294
  93. package/src/ui/components/AIUnderstandingSection.tsx +0 -183
  94. package/src/ui/components/HallucinationSection.tsx +0 -172
  95. package/src/ui/components/IssuesSection.tsx +0 -140
  96. package/src/ui/components/MessageAlignmentSection.tsx +0 -203
  97. package/src/ui/components/OverviewSection.tsx +0 -157
  98. package/src/ui/components/ScoreDisplay.tsx +0 -58
  99. package/src/ui/components/TechnicalSection.tsx +0 -200
  100. package/src/utils/comprehensive-formatter.ts +0 -455
  101. package/test.sh +0 -31
  102. package/tsconfig.json +0 -25
package/dist/index.js CHANGED
@@ -1,15 +1,2576 @@
1
- import { Command } from 'commander';
2
- import { auditCommand } from './commands/audit.js';
3
- import { crawlCommand } from './commands/crawl.js';
4
- import { reportCommand } from './commands/report.js';
5
- import { auditWizardCommand } from './commands/audit-wizard.js';
6
- const program = new Command();
7
- program
8
- .name('ai-lighthouse')
9
- .description('AI Lighthouse - Audit websites for AI readiness and SEO optimization')
10
- .version('1.0.0');
11
- // Register commands
12
- auditWizardCommand(program); // Interactive wizard (recommended for new users)
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/audit.ts
7
+ import chalk2 from "chalk";
8
+ import ora from "ora";
9
+ import { analyzeUrlWithRules } from "@ai-lighthouse/scanner";
10
+ import { calculateAIReadiness, formatAIReadinessReport } from "@ai-lighthouse/scanner";
11
+ import { exportAuditReport, generateScoringSummary } from "@ai-lighthouse/scanner";
12
+ import { writeFile, mkdir } from "fs/promises";
13
+ import { join, resolve } from "path";
14
+ import { existsSync } from "fs";
15
+ import html_to_pdf from "html-pdf-node";
16
+
17
+ // src/utils/comprehensive-formatter.ts
18
+ import chalk from "chalk";
19
+ function formatComprehensiveReport(result, aiReadiness) {
20
+ const sections = [];
21
+ if (result.llm) {
22
+ sections.push(formatLLMSection(result.llm));
23
+ }
24
+ if (result.chunking) {
25
+ sections.push(formatChunkingSection(result.chunking));
26
+ }
27
+ if (result.extractability) {
28
+ sections.push(formatExtractabilitySection(result.extractability));
29
+ }
30
+ if (result.hallucinationReport) {
31
+ sections.push(formatHallucinationSection(result.hallucinationReport));
32
+ }
33
+ if (result.mirrorReport) {
34
+ sections.push(formatMirrorReportSection(result.mirrorReport));
35
+ }
36
+ if (aiReadiness?.dimensions) {
37
+ sections.push(formatDimensionsSection(aiReadiness.dimensions));
38
+ }
39
+ if (aiReadiness?.quickWins && aiReadiness.quickWins.length > 0) {
40
+ sections.push(formatQuickWinsSection(aiReadiness.quickWins));
41
+ }
42
+ return sections.join("\n\n");
43
+ }
44
+ function formatLLMSection(llm) {
45
+ const lines = [];
46
+ lines.push(chalk.bold.blue("\u{1F4DD} AI Understanding Analysis"));
47
+ lines.push("\u2500".repeat(70));
48
+ if (llm.summary) {
49
+ lines.push(chalk.bold("Summary:"));
50
+ lines.push(` ${llm.summary}`);
51
+ lines.push("");
52
+ }
53
+ if (llm.pageType) {
54
+ lines.push(chalk.bold("Inferred Page Type:"));
55
+ lines.push(` ${chalk.magenta.bold(llm.pageType)}`);
56
+ if (llm.pageTypeInsights && llm.pageTypeInsights.length > 0) {
57
+ lines.push("");
58
+ lines.push(chalk.bold("\u{1F4A1} AI-Generated Insights:"));
59
+ llm.pageTypeInsights.forEach((insight) => {
60
+ lines.push(` ${chalk.cyan("\u2022")} ${insight}`);
61
+ });
62
+ }
63
+ lines.push("");
64
+ }
65
+ if (llm.keyTopics && llm.keyTopics.length > 0) {
66
+ lines.push(chalk.bold("Key Topics:"));
67
+ lines.push(` ${llm.keyTopics.map((t) => chalk.blue(t)).join(", ")}`);
68
+ lines.push("");
69
+ }
70
+ const metadata = [];
71
+ if (llm.readingLevel) {
72
+ metadata.push(`Reading Level: ${llm.readingLevel.description}`);
73
+ }
74
+ if (llm.sentiment) {
75
+ metadata.push(`Sentiment: ${llm.sentiment}`);
76
+ }
77
+ if (llm.technicalDepth) {
78
+ metadata.push(`Technical Depth: ${llm.technicalDepth}`);
79
+ }
80
+ if (metadata.length > 0) {
81
+ lines.push(chalk.bold("Metadata:"));
82
+ metadata.forEach((m) => lines.push(` ${m}`));
83
+ lines.push("");
84
+ }
85
+ if (llm.topEntities && llm.topEntities.length > 0) {
86
+ lines.push(chalk.bold("\u{1F50D} Key Entities:"));
87
+ llm.topEntities.slice(0, 5).forEach((entity) => {
88
+ const relevance = entity.relevance ? ` - ${Math.round(entity.relevance * 100)}% relevance` : "";
89
+ lines.push(` ${chalk.cyan("\u2022")} ${chalk.bold(entity.name)} ${chalk.dim(`(${entity.type})${relevance}`)}`);
90
+ });
91
+ lines.push("");
92
+ }
93
+ if (llm.questions && llm.questions.length > 0) {
94
+ lines.push(chalk.bold("\u2753 Questions AI Can Answer:"));
95
+ llm.questions.slice(0, 5).forEach((q, idx) => {
96
+ const difficulty = chalk.dim(`[${q.difficulty.toUpperCase()}]`);
97
+ lines.push(` ${idx + 1}. ${difficulty} ${q.question}`);
98
+ });
99
+ lines.push("");
100
+ }
101
+ if (llm.suggestedFAQ && llm.suggestedFAQ.length > 0) {
102
+ lines.push(chalk.bold("\u{1F4A1} Suggested FAQs:"));
103
+ llm.suggestedFAQ.filter((f) => f.importance === "high").slice(0, 3).forEach((faq, idx) => {
104
+ lines.push(` ${idx + 1}. Q: ${chalk.yellow(faq.question)}`);
105
+ lines.push(` A: ${chalk.dim(faq.suggestedAnswer)}`);
106
+ });
107
+ }
108
+ return lines.join("\n");
109
+ }
110
+ function formatChunkingSection(chunking) {
111
+ const lines = [];
112
+ lines.push(chalk.bold.green("\u{1F4C4} Content Chunking Analysis"));
113
+ lines.push("\u2500".repeat(70));
114
+ const grid = [
115
+ ["Strategy", chunking.chunkingStrategy],
116
+ ["Total Chunks", chunking.totalChunks.toString()],
117
+ ["Avg Tokens/Chunk", chunking.averageTokensPerChunk.toString()],
118
+ ["Avg Noise Ratio", `${(chunking.averageNoiseRatio * 100).toFixed(1)}%`]
119
+ ];
120
+ grid.forEach(([label, value]) => {
121
+ lines.push(` ${chalk.bold(label + ":").padEnd(25)} ${chalk.cyan(value)}`);
122
+ });
123
+ if (chunking.chunkingStrategy === "heading-based") {
124
+ lines.push("");
125
+ lines.push(chalk.green(" \u2713 Heading-based chunking is ideal for AI comprehension"));
126
+ } else if (chunking.chunkingStrategy === "paragraph-based") {
127
+ lines.push("");
128
+ lines.push(chalk.yellow(" \u26A0 Consider adding headings for better semantic structure"));
129
+ }
130
+ if (chunking.chunks && chunking.chunks.length > 0) {
131
+ lines.push("");
132
+ lines.push(chalk.bold("Chunk Distribution:"));
133
+ const tokenCounts = chunking.chunks.map((c) => c.tokenCount);
134
+ const min = Math.min(...tokenCounts);
135
+ const max = Math.max(...tokenCounts);
136
+ const avg = tokenCounts.reduce((a, b) => a + b, 0) / tokenCounts.length;
137
+ lines.push(` Min Tokens: ${min}, Max Tokens: ${max}, Avg: ${avg.toFixed(0)}`);
138
+ const noiseRatios = chunking.chunks.map((c) => c.noiseRatio);
139
+ const avgNoise = noiseRatios.reduce((a, b) => a + b, 0) / noiseRatios.length;
140
+ lines.push(` Avg Noise per Chunk: ${(avgNoise * 100).toFixed(1)}%`);
141
+ }
142
+ return lines.join("\n");
143
+ }
144
+ function formatExtractabilitySection(extractability) {
145
+ const lines = [];
146
+ lines.push(chalk.bold.yellow("\u{1F504} Extractability Analysis"));
147
+ lines.push("\u2500".repeat(70));
148
+ const grid = [
149
+ ["Overall Score", `${extractability.score.extractabilityScore}/100`],
150
+ ["Server-Rendered", `${extractability.score.serverRenderedPercent}%`]
151
+ ];
152
+ grid.forEach(([label, value]) => {
153
+ lines.push(` ${chalk.bold(label + ":").padEnd(25)} ${chalk.cyan(value)}`);
154
+ });
155
+ lines.push("");
156
+ lines.push(chalk.bold("Content Type Extractability:"));
157
+ Object.entries(extractability.contentTypes).forEach(([type, data]) => {
158
+ const percentage = data.percentage;
159
+ const color = percentage >= 80 ? chalk.green : percentage >= 50 ? chalk.yellow : chalk.red;
160
+ lines.push(` ${chalk.bold(type.charAt(0).toUpperCase() + type.slice(1) + ":").padEnd(15)} ${color(`${percentage}%`)} (${data.extractable}/${data.total})`);
161
+ });
162
+ const overallScore = extractability.score.extractabilityScore;
163
+ if (overallScore >= 80) {
164
+ lines.push("");
165
+ lines.push(chalk.green(" \u2713 Good extractability - AI can easily read your content"));
166
+ } else if (overallScore < 50) {
167
+ lines.push("");
168
+ lines.push(chalk.red(" \u26A0 Low extractability - Consider server-side rendering"));
169
+ }
170
+ return lines.join("\n");
171
+ }
172
+ function formatHallucinationSection(report) {
173
+ const lines = [];
174
+ lines.push(chalk.bold.red("\u26A0\uFE0F Hallucination Risk Assessment"));
175
+ lines.push("\u2500".repeat(70));
176
+ const riskScore = report.hallucinationRiskScore;
177
+ const riskColor = riskScore >= 70 ? chalk.red : riskScore >= 40 ? chalk.yellow : chalk.green;
178
+ lines.push(` ${chalk.bold("Risk Score:")} ${riskColor.bold(`${riskScore}/100`)}`);
179
+ if (report.factCheckSummary) {
180
+ lines.push("");
181
+ lines.push(chalk.bold("Fact Check Summary:"));
182
+ const summary = report.factCheckSummary;
183
+ lines.push(` Total Facts: ${chalk.cyan(summary.totalFacts)}`);
184
+ lines.push(` Verified: ${chalk.green(summary.verifiedFacts)}`);
185
+ lines.push(` Unverified: ${chalk.yellow(summary.unverifiedFacts)}`);
186
+ lines.push(` Contradictions: ${chalk.red(summary.contradictions)}`);
187
+ if (summary.ambiguities !== void 0) {
188
+ lines.push(` Ambiguities: ${chalk.yellow(summary.ambiguities)}`);
189
+ }
190
+ }
191
+ if (report.factCheckSummary && report.factCheckSummary.unverifiedFacts > 0) {
192
+ lines.push("");
193
+ lines.push(chalk.yellow("\u{1F4A1} Tip: Add citations and links to verify claims and reduce AI hallucination risk"));
194
+ }
195
+ if (report.triggers && report.triggers.length > 0) {
196
+ const highSeverityTriggers = report.triggers.filter(
197
+ (t) => t.severity === "high" || t.severity === "critical"
198
+ );
199
+ if (highSeverityTriggers.length > 0) {
200
+ lines.push("");
201
+ lines.push(chalk.bold("\u{1F6A8} High-Risk Triggers:"));
202
+ highSeverityTriggers.slice(0, 5).forEach((trigger, idx) => {
203
+ lines.push(` ${idx + 1}. ${chalk.red(`[${trigger.severity.toUpperCase()}]`)} ${trigger.type}`);
204
+ lines.push(` ${chalk.dim(trigger.description)}`);
205
+ if (trigger.confidence) {
206
+ lines.push(` ${chalk.dim(`Confidence: ${Math.round(trigger.confidence * 100)}%`)}`);
207
+ }
208
+ });
209
+ }
210
+ }
211
+ if (report.recommendations && report.recommendations.length > 0) {
212
+ lines.push("");
213
+ lines.push(chalk.bold("\u{1F4A1} Recommendations:"));
214
+ report.recommendations.slice(0, 3).forEach((rec, idx) => {
215
+ lines.push(` ${idx + 1}. ${rec}`);
216
+ });
217
+ }
218
+ return lines.join("\n");
219
+ }
220
+ function formatMirrorReportSection(report) {
221
+ const lines = [];
222
+ lines.push(chalk.bold.magenta("\u{1F50D} AI Misunderstanding Check"));
223
+ lines.push("\u2500".repeat(70));
224
+ const alignmentColor = report.summary.alignmentScore >= 80 ? chalk.green : report.summary.alignmentScore >= 60 ? chalk.yellow : chalk.red;
225
+ const clarityColor = report.summary.clarityScore >= 80 ? chalk.green : report.summary.clarityScore >= 60 ? chalk.yellow : chalk.red;
226
+ lines.push(` ${chalk.bold("Alignment Score:").padEnd(25)} ${alignmentColor.bold(`${report.summary.alignmentScore}/100`)}`);
227
+ lines.push(` ${chalk.bold("Clarity Score:").padEnd(25)} ${clarityColor.bold(`${report.summary.clarityScore}/100`)}`);
228
+ lines.push(` ${chalk.bold("Critical Issues:").padEnd(25)} ${chalk.red(report.summary.critical)}`);
229
+ lines.push(` ${chalk.bold("Major Issues:").padEnd(25)} ${chalk.yellow(report.summary.major)}`);
230
+ if (report.llmInterpretation) {
231
+ lines.push("");
232
+ lines.push(chalk.bold.blue("\u{1F916} What AI Actually Understood"));
233
+ lines.push(chalk.dim(` (${Math.round(report.llmInterpretation.confidence * 100)}% confident)`));
234
+ if (report.llmInterpretation.productName) {
235
+ lines.push(` ${chalk.bold("Product:")} ${report.llmInterpretation.productName}`);
236
+ }
237
+ if (report.llmInterpretation.purpose) {
238
+ lines.push(` ${chalk.bold("Purpose:")} ${report.llmInterpretation.purpose}`);
239
+ }
240
+ if (report.llmInterpretation.valueProposition) {
241
+ lines.push(` ${chalk.bold.magenta("\u{1F48E} Value:")} ${report.llmInterpretation.valueProposition}`);
242
+ }
243
+ if (report.llmInterpretation.keyBenefits && report.llmInterpretation.keyBenefits.length > 0) {
244
+ lines.push(` ${chalk.bold("Benefits:")}`);
245
+ report.llmInterpretation.keyBenefits.forEach((benefit) => {
246
+ lines.push(` \u2022 ${benefit}`);
247
+ });
248
+ }
249
+ if (report.llmInterpretation.keyFeatures && report.llmInterpretation.keyFeatures.length > 0) {
250
+ lines.push(` ${chalk.bold("Features:")}`);
251
+ report.llmInterpretation.keyFeatures.slice(0, 3).forEach((feature) => {
252
+ lines.push(` \u2022 ${feature}`);
253
+ });
254
+ }
255
+ if (report.llmInterpretation.targetAudience) {
256
+ lines.push(` ${chalk.bold("Audience:")} ${report.llmInterpretation.targetAudience}`);
257
+ }
258
+ }
259
+ if (report.mismatches && report.mismatches.length > 0) {
260
+ const priorityMismatches = report.mismatches.filter(
261
+ (m) => m.severity === "critical" || m.severity === "major"
262
+ );
263
+ if (priorityMismatches.length > 0) {
264
+ lines.push("");
265
+ lines.push(chalk.bold("Priority Mismatches:"));
266
+ priorityMismatches.slice(0, 5).forEach((mismatch, idx) => {
267
+ const icon = mismatch.severity === "critical" ? "\u{1F534}" : "\u{1F7E1}";
268
+ lines.push(` ${icon} ${idx + 1}. ${chalk.bold(mismatch.field)}`);
269
+ lines.push(` ${chalk.dim(mismatch.description)}`);
270
+ lines.push(` ${chalk.cyan("\u2192")} ${mismatch.recommendation}`);
271
+ });
272
+ }
273
+ }
274
+ if (report.recommendations && report.recommendations.length > 0) {
275
+ lines.push("");
276
+ lines.push(chalk.bold("\u{1F4A1} Top Recommendations:"));
277
+ report.recommendations.slice(0, 3).forEach((rec, idx) => {
278
+ lines.push(` ${idx + 1}. ${rec}`);
279
+ });
280
+ }
281
+ return lines.join("\n");
282
+ }
283
+ function formatDimensionsSection(dimensions) {
284
+ const lines = [];
285
+ lines.push(chalk.bold.cyan("\u{1F3AF} Dimension Analysis"));
286
+ lines.push("\u2500".repeat(70));
287
+ const dimensionDescriptions = {
288
+ technical: "\u2699\uFE0F Technical",
289
+ contentQuality: "\u{1F4DD} Content Quality",
290
+ crawlability: "\u{1F577}\uFE0F Crawlability",
291
+ discoverability: "\u{1F50D} Discoverability",
292
+ knowledge: "\u{1F9E0} Knowledge",
293
+ extractability: "\u{1F504} Extractability",
294
+ comprehensibility: "\u{1F4A1} Comprehensibility",
295
+ trustworthiness: "\u2705 Trustworthiness",
296
+ accessibility: "\u267F Accessibility"
297
+ };
298
+ Object.entries(dimensions).forEach(([key, dim]) => {
299
+ const name = dimensionDescriptions[key] || key;
300
+ const scoreColor = dim.score >= 90 ? chalk.green : dim.score >= 75 ? chalk.yellow : dim.score >= 60 ? chalk.hex("#FFA500") : chalk.red;
301
+ lines.push("");
302
+ lines.push(`${name}: ${scoreColor.bold(`${Math.round(dim.score)}/100`)} ${chalk.dim(`(${dim.status})`)}`);
303
+ if (dim.strengths && dim.strengths.length > 0) {
304
+ lines.push(` ${chalk.green("Strengths:")} ${dim.strengths.join(", ")}`);
305
+ }
306
+ if (dim.weaknesses && dim.weaknesses.length > 0) {
307
+ lines.push(` ${chalk.yellow("Weaknesses:")} ${dim.weaknesses.join(", ")}`);
308
+ }
309
+ if (dim.recommendation) {
310
+ lines.push(` ${chalk.cyan("\u2192")} ${dim.recommendation}`);
311
+ }
312
+ });
313
+ return lines.join("\n");
314
+ }
315
+ function formatQuickWinsSection(quickWins) {
316
+ const lines = [];
317
+ lines.push(chalk.bold.yellow("\u26A1 Quick Wins (High Impact, Low Effort)"));
318
+ lines.push("\u2500".repeat(70));
319
+ quickWins.slice(0, 5).forEach((win, idx) => {
320
+ lines.push("");
321
+ lines.push(`${chalk.bold(`${idx + 1}.`)} ${chalk.yellow(win.issue)}`);
322
+ lines.push(` ${chalk.dim(`Impact: ${win.impact} \xB7 Effort: ${win.effort}`)}`);
323
+ lines.push(` ${chalk.cyan("\u2192")} ${win.fix}`);
324
+ });
325
+ return lines.join("\n");
326
+ }
327
+ function formatDetailedIssues(issues) {
328
+ const lines = [];
329
+ lines.push(chalk.bold("\u26A0\uFE0F All Issues"));
330
+ lines.push("\u2500".repeat(70));
331
+ const grouped = {
332
+ critical: issues.filter((i) => i.severity === "critical"),
333
+ high: issues.filter((i) => i.severity === "high"),
334
+ medium: issues.filter((i) => i.severity === "medium"),
335
+ low: issues.filter((i) => i.severity === "low")
336
+ };
337
+ lines.push("");
338
+ lines.push(chalk.bold("Issue Count by Severity:"));
339
+ lines.push(` Critical: ${chalk.red.bold(grouped.critical.length)}`);
340
+ lines.push(` High: ${chalk.yellow.bold(grouped.high.length)}`);
341
+ lines.push(` Medium: ${chalk.blue.bold(grouped.medium.length)}`);
342
+ lines.push(` Low: ${chalk.dim(grouped.low.length)}`);
343
+ for (const [severity, severityIssues] of Object.entries(grouped)) {
344
+ if (severityIssues.length === 0) continue;
345
+ lines.push("");
346
+ lines.push(chalk.bold(`${severity.toUpperCase()} Issues:`));
347
+ severityIssues.forEach((issue, idx) => {
348
+ const icon = severity === "critical" ? "\u{1F534}" : severity === "high" ? "\u{1F7E0}" : severity === "medium" ? "\u{1F7E1}" : "\u{1F535}";
349
+ lines.push("");
350
+ lines.push(`${icon} ${idx + 1}. ${chalk.bold(issue.message || issue.title)}`);
351
+ lines.push(` ${chalk.dim(`Category: ${issue.category} \xB7 Impact: ${issue.impact}`)}`);
352
+ if (issue.evidence) {
353
+ const evidenceText = typeof issue.evidence === "string" ? issue.evidence : issue.evidence.join(", ");
354
+ lines.push(` ${chalk.dim(evidenceText.substring(0, 100))}${evidenceText.length > 100 ? "..." : ""}`);
355
+ }
356
+ if (issue.element) {
357
+ lines.push(` ${chalk.dim(issue.element.substring(0, 100))}${issue.element.length > 100 ? "..." : ""}`);
358
+ }
359
+ lines.push(` ${chalk.cyan("\u{1F4A1} Fix:")} ${issue.suggested_fix || issue.remediation}`);
360
+ });
361
+ }
362
+ return lines.join("\n");
363
+ }
364
+
365
+ // src/commands/audit.ts
366
+ import { render } from "ink";
367
+ import React10 from "react";
368
+
369
+ // src/ui/AuditReportUI.tsx
370
+ import React8, { useState as useState2 } from "react";
371
+ import { Box as Box8, Text as Text8, useInput } from "ink";
372
+ import Spinner from "ink-spinner";
373
+
374
+ // src/ui/components/ScoreDisplay.tsx
375
+ import React from "react";
376
+ import { Box, Text } from "ink";
377
+ import Gradient from "ink-gradient";
378
+ import BigText from "ink-big-text";
379
+ var ScoreDisplay = ({ score, grade, url }) => {
380
+ const getGradeColor = (grade2) => {
381
+ if (grade2.startsWith("A")) return "green";
382
+ if (grade2.startsWith("B")) return "blue";
383
+ if (grade2.startsWith("C")) return "yellow";
384
+ return "red";
385
+ };
386
+ const getScoreMessage = (score2) => {
387
+ if (score2 >= 90) return { status: "Excellent", message: "Your site is AI-ready!", color: "green" };
388
+ if (score2 >= 80) return { status: "Good", message: "Your site works well with AI", color: "blue" };
389
+ if (score2 >= 70) return { status: "Fair", message: "Room for improvement", color: "yellow" };
390
+ if (score2 >= 60) return { status: "Poor", message: "Needs significant work", color: "yellow" };
391
+ return { status: "Critical", message: "Major issues detected", color: "red" };
392
+ };
393
+ const { status, message, color } = getScoreMessage(score);
394
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", paddingY: 1 }, /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Gradient, { name: "rainbow" }, /* @__PURE__ */ React.createElement(BigText, { text: `${score}`, font: "block" }))), /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: getGradeColor(grade) }, "Grade: ", grade, " \u2022 ", status)), /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, url)), /* @__PURE__ */ React.createElement(
395
+ Box,
396
+ {
397
+ borderStyle: "round",
398
+ borderColor: color,
399
+ paddingX: 2,
400
+ paddingY: 1
401
+ },
402
+ /* @__PURE__ */ React.createElement(Text, { color }, message)
403
+ ));
404
+ };
405
+
406
+ // src/ui/components/OverviewSection.tsx
407
+ import React2 from "react";
408
+ import { Box as Box2, Text as Text2 } from "ink";
409
+ var OverviewSection = ({ aiReadiness }) => {
410
+ const getStatusColor = (status) => {
411
+ if (status === "excellent") return "green";
412
+ if (status === "good") return "blue";
413
+ if (status === "needs-work") return "yellow";
414
+ return "red";
415
+ };
416
+ const renderProgressBar = (score, width = 20) => {
417
+ const filled = Math.round(score / 100 * width);
418
+ const empty = width - filled;
419
+ const color = score >= 80 ? "green" : score >= 60 ? "yellow" : "red";
420
+ return /* @__PURE__ */ React2.createElement(Text2, { color }, "\u2588".repeat(filled), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "\u2591".repeat(empty)), ` ${Math.round(score)}%`);
421
+ };
422
+ return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", paddingY: 1 }, /* @__PURE__ */ React2.createElement(
423
+ Box2,
424
+ {
425
+ flexDirection: "column",
426
+ borderStyle: "round",
427
+ borderColor: "cyan",
428
+ paddingX: 2,
429
+ paddingY: 1,
430
+ marginBottom: 1
431
+ },
432
+ /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "cyan" }, "\u{1F916} AI Agent Perspective"),
433
+ /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, null, aiReadiness.aiPerspective?.canUnderstand ? "\u2705" : "\u274C", " Can Understand", aiReadiness.aiPerspective?.canExtract ? "\u2705" : "\u274C", " Can Extract")), /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, null, aiReadiness.aiPerspective?.canIndex ? "\u2705" : "\u274C", " Can Index", aiReadiness.aiPerspective?.canAnswer ? "\u2705" : "\u274C", " Can Answer")), /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "Confidence: ", Math.round((aiReadiness.aiPerspective?.confidence || 0) * 100), "%"))),
434
+ aiReadiness.aiPerspective?.mainBlockers && aiReadiness.aiPerspective.mainBlockers.length > 0 && /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "red" }, "Main Blockers:"), aiReadiness.aiPerspective.mainBlockers.map((blocker, idx) => /* @__PURE__ */ React2.createElement(Text2, { key: idx, color: "red" }, "\u2022 ", blocker)))
435
+ ), /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, { bold: true, underline: true }, "\u{1F3AF} Dimension Scores"), Object.entries(aiReadiness.dimensions || {}).map(([key, dim]) => /* @__PURE__ */ React2.createElement(Box2, { key, flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React2.createElement(Box2, { justifyContent: "space-between" }, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: getStatusColor(dim.status) }, getDimensionIcon(key), " ", formatDimensionName(key)), /* @__PURE__ */ React2.createElement(Text2, { color: getStatusColor(dim.status) }, Math.round(dim.score), "/100")), /* @__PURE__ */ React2.createElement(Box2, { marginTop: 0.5 }, renderProgressBar(dim.score)), dim.recommendation && /* @__PURE__ */ React2.createElement(Box2, { marginTop: 0.5 }, /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "\u2192 ", dim.recommendation))))), aiReadiness.quickWins && aiReadiness.quickWins.length > 0 && /* @__PURE__ */ React2.createElement(
436
+ Box2,
437
+ {
438
+ flexDirection: "column",
439
+ borderStyle: "round",
440
+ borderColor: "yellow",
441
+ paddingX: 2,
442
+ paddingY: 1,
443
+ marginTop: 1
444
+ },
445
+ /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "yellow" }, "\u26A1 Quick Wins (High Impact, Low Effort)"),
446
+ aiReadiness.quickWins.slice(0, 5).map((win, idx) => /* @__PURE__ */ React2.createElement(Box2, { key: idx, flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, { bold: true }, idx + 1, ". ", win.issue), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "Impact: ", win.impact, " \u2022 Effort: ", win.effort), /* @__PURE__ */ React2.createElement(Text2, { color: "cyan" }, "\u2192 ", win.fix)))
447
+ ));
448
+ };
449
+ function getDimensionIcon(key) {
450
+ const icons = {
451
+ technical: "\u2699\uFE0F",
452
+ contentQuality: "\u{1F4DD}",
453
+ crawlability: "\u{1F577}\uFE0F",
454
+ discoverability: "\u{1F50D}",
455
+ knowledge: "\u{1F9E0}",
456
+ extractability: "\u{1F504}",
457
+ comprehensibility: "\u{1F4A1}",
458
+ trustworthiness: "\u2705",
459
+ accessibility: "\u267F"
460
+ };
461
+ return icons[key] || "\u{1F4CC}";
462
+ }
463
+ function formatDimensionName(key) {
464
+ const names = {
465
+ technical: "Technical",
466
+ contentQuality: "Content Quality",
467
+ crawlability: "Crawlability",
468
+ discoverability: "Discoverability",
469
+ knowledge: "Knowledge",
470
+ extractability: "Extractability",
471
+ comprehensibility: "Comprehensibility",
472
+ trustworthiness: "Trustworthiness",
473
+ accessibility: "Accessibility"
474
+ };
475
+ return names[key] || key;
476
+ }
477
+
478
+ // src/ui/components/IssuesSection.tsx
479
+ import React3, { useState } from "react";
480
+ import { Box as Box3, Text as Text3 } from "ink";
481
+ var IssuesSection = ({ issues }) => {
482
+ const [selectedSeverity, setSelectedSeverity] = useState("all");
483
+ const [expandedIssue, setExpandedIssue] = useState(null);
484
+ const getSeverityColor2 = (severity) => {
485
+ if (severity === "critical") return "red";
486
+ if (severity === "high") return "magenta";
487
+ if (severity === "medium") return "yellow";
488
+ return "blue";
489
+ };
490
+ const getSeverityIcon = (severity) => {
491
+ if (severity === "critical") return "\u{1F534}";
492
+ if (severity === "high") return "\u{1F7E0}";
493
+ if (severity === "medium") return "\u{1F7E1}";
494
+ return "\u{1F535}";
495
+ };
496
+ const grouped = {
497
+ critical: issues.filter((i) => i.severity === "critical"),
498
+ high: issues.filter((i) => i.severity === "high"),
499
+ medium: issues.filter((i) => i.severity === "medium"),
500
+ low: issues.filter((i) => i.severity === "low")
501
+ };
502
+ const filteredIssues = selectedSeverity === "all" ? issues : grouped[selectedSeverity] || [];
503
+ return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", paddingY: 1 }, /* @__PURE__ */ React3.createElement(
504
+ Box3,
505
+ {
506
+ flexDirection: "column",
507
+ borderStyle: "round",
508
+ borderColor: "blue",
509
+ paddingX: 2,
510
+ paddingY: 1,
511
+ marginBottom: 1
512
+ },
513
+ /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "blue" }, "\u26A0\uFE0F Issues Summary"),
514
+ /* @__PURE__ */ React3.createElement(Box3, { marginTop: 1 }, /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", width: "50%" }, /* @__PURE__ */ React3.createElement(Text3, null, /* @__PURE__ */ React3.createElement(Text3, { color: "red", bold: true }, "Critical:"), " ", grouped.critical.length), /* @__PURE__ */ React3.createElement(Text3, null, /* @__PURE__ */ React3.createElement(Text3, { color: "magenta", bold: true }, "High:"), " ", grouped.high.length)), /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", width: "50%" }, /* @__PURE__ */ React3.createElement(Text3, null, /* @__PURE__ */ React3.createElement(Text3, { color: "yellow", bold: true }, "Medium:"), " ", grouped.medium.length), /* @__PURE__ */ React3.createElement(Text3, null, /* @__PURE__ */ React3.createElement(Text3, { color: "blue", bold: true }, "Low:"), " ", grouped.low.length)))
515
+ ), /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, underline: true }, selectedSeverity === "all" ? "All Issues" : `${selectedSeverity.toUpperCase()} Issues`, " (", filteredIssues.length, ")"), filteredIssues.map((issue, idx) => /* @__PURE__ */ React3.createElement(
516
+ Box3,
517
+ {
518
+ key: idx,
519
+ flexDirection: "column",
520
+ borderStyle: "round",
521
+ borderColor: getSeverityColor2(issue.severity),
522
+ paddingX: 2,
523
+ paddingY: 1,
524
+ marginTop: 1
525
+ },
526
+ /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Text3, null, getSeverityIcon(issue.severity), " ", /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: getSeverityColor2(issue.severity) }, issue.message || issue.title))),
527
+ /* @__PURE__ */ React3.createElement(Box3, { marginTop: 0.5 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "Category: ", issue.category, " \u2022 Impact: ", issue.impact)),
528
+ issue.evidence && /* @__PURE__ */ React3.createElement(Box3, { marginTop: 0.5 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, typeof issue.evidence === "string" ? issue.evidence.substring(0, 100) : Array.isArray(issue.evidence) ? issue.evidence.join(", ").substring(0, 100) : "", (typeof issue.evidence === "string" ? issue.evidence : issue.evidence?.join(", ") || "").length > 100 ? "..." : "")),
529
+ /* @__PURE__ */ React3.createElement(
530
+ Box3,
531
+ {
532
+ marginTop: 1,
533
+ borderStyle: "single",
534
+ borderColor: "cyan",
535
+ paddingX: 1,
536
+ paddingY: 0.5
537
+ },
538
+ /* @__PURE__ */ React3.createElement(Text3, { color: "cyan" }, "\u{1F4A1} Fix: ", issue.suggested_fix || issue.remediation)
539
+ )
540
+ ))));
541
+ };
542
+
543
+ // src/ui/components/AIUnderstandingSection.tsx
544
+ import React4 from "react";
545
+ import { Box as Box4, Text as Text4 } from "ink";
546
+ var AIUnderstandingSection = ({ llm }) => {
547
+ if (!llm || Object.keys(llm).length === 0) {
548
+ return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", paddingY: 1 }, /* @__PURE__ */ React4.createElement(
549
+ Box4,
550
+ {
551
+ flexDirection: "column",
552
+ borderStyle: "round",
553
+ borderColor: "yellow",
554
+ paddingX: 2,
555
+ paddingY: 1
556
+ },
557
+ /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: "yellow" }, "\u{1F4A1} Enable AI Understanding Analysis"),
558
+ /* @__PURE__ */ React4.createElement(Box4, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Text4, null, "LLM analysis is not enabled. To see AI understanding insights, run:"), /* @__PURE__ */ React4.createElement(Box4, { marginTop: 1, borderStyle: "single", paddingX: 1 }, /* @__PURE__ */ React4.createElement(Text4, { color: "cyan" }, "ai-lighthouse audit [URL] --enable-llm --llm-provider openai --llm-api-key YOUR_KEY")), /* @__PURE__ */ React4.createElement(Box4, { marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "Supported providers: openai, anthropic, ollama, local")))
559
+ ));
560
+ }
561
+ return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", paddingY: 1 }, llm.pageType && /* @__PURE__ */ React4.createElement(
562
+ Box4,
563
+ {
564
+ flexDirection: "column",
565
+ borderStyle: "round",
566
+ borderColor: "magenta",
567
+ paddingX: 2,
568
+ paddingY: 1,
569
+ marginBottom: 1
570
+ },
571
+ /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: "magenta" }, "\u{1F4C4} Inferred Page Type"),
572
+ /* @__PURE__ */ React4.createElement(Text4, { color: "cyan" }, llm.pageType)
573
+ ), llm.pageTypeInsights && llm.pageTypeInsights.length > 0 && /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React4.createElement(Text4, { bold: true, underline: true, color: "blue" }, "\u{1F4A1} AI-Generated Insights"), llm.pageTypeInsights.map((insight, idx) => /* @__PURE__ */ React4.createElement(Box4, { key: idx, marginTop: 0.5 }, /* @__PURE__ */ React4.createElement(Text4, { color: "cyan" }, "\u2022 ", insight)))), llm.summary && /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React4.createElement(Text4, { bold: true, underline: true }, "Summary"), /* @__PURE__ */ React4.createElement(Box4, { marginTop: 0.5 }, /* @__PURE__ */ React4.createElement(Text4, null, llm.summary))), llm.keyTopics && llm.keyTopics.length > 0 && /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React4.createElement(Text4, { bold: true, underline: true }, "\u{1F3F7}\uFE0F Key Topics"), /* @__PURE__ */ React4.createElement(Box4, { marginTop: 0.5 }, /* @__PURE__ */ React4.createElement(Text4, null, llm.keyTopics.map((t) => `[${t}]`).join(" ")))), /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React4.createElement(Text4, { bold: true, underline: true }, "Metadata"), /* @__PURE__ */ React4.createElement(Box4, { marginTop: 0.5, flexDirection: "column" }, llm.readingLevel && /* @__PURE__ */ React4.createElement(Text4, null, /* @__PURE__ */ React4.createElement(Text4, { bold: true }, "Reading Level:"), " ", llm.readingLevel.description), llm.sentiment && /* @__PURE__ */ React4.createElement(Text4, null, /* @__PURE__ */ React4.createElement(Text4, { bold: true }, "Sentiment:"), " ", llm.sentiment), llm.technicalDepth && /* @__PURE__ */ React4.createElement(Text4, null, /* @__PURE__ */ React4.createElement(Text4, { bold: true }, "Technical Depth:"), " ", llm.technicalDepth))), llm.topEntities && llm.topEntities.length > 0 && /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React4.createElement(Text4, { bold: true, underline: true }, "\u{1F50D} Key Entities"), llm.topEntities.slice(0, 5).map((entity, idx) => /* @__PURE__ */ React4.createElement(Box4, { key: idx, marginTop: 0.5 }, /* @__PURE__ */ React4.createElement(Text4, null, "\u2022 ", /* @__PURE__ */ React4.createElement(Text4, { bold: true }, entity.name), " ", /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "(", entity.type, ") - ", Math.round((entity.relevance || 0) * 100), "% relevance"))))), llm.questions && llm.questions.length > 0 && /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React4.createElement(Text4, { bold: true, underline: true }, "\u2753 Questions AI Can Answer"), llm.questions.slice(0, 5).map((q, idx) => /* @__PURE__ */ React4.createElement(Box4, { key: idx, marginTop: 0.5 }, /* @__PURE__ */ React4.createElement(Text4, null, idx + 1, ". ", /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "[", q.difficulty.toUpperCase(), "]"), " ", q.question)))), llm.suggestedFAQ && llm.suggestedFAQ.length > 0 && /* @__PURE__ */ React4.createElement(
574
+ Box4,
575
+ {
576
+ flexDirection: "column",
577
+ borderStyle: "round",
578
+ borderColor: "yellow",
579
+ paddingX: 2,
580
+ paddingY: 1
581
+ },
582
+ /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: "yellow" }, "\u{1F4A1} Suggested FAQs"),
583
+ llm.suggestedFAQ.filter((f) => f.importance === "high").slice(0, 3).map((faq, idx) => /* @__PURE__ */ React4.createElement(Box4, { key: idx, flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, null, /* @__PURE__ */ React4.createElement(Text4, { bold: true }, "Q:"), " ", faq.question), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, /* @__PURE__ */ React4.createElement(Text4, { bold: true }, "A:"), " ", faq.suggestedAnswer)))
584
+ ));
585
+ };
586
+
587
+ // src/ui/components/HallucinationSection.tsx
588
+ import React5 from "react";
589
+ import { Box as Box5, Text as Text5 } from "ink";
590
+ var HallucinationSection = ({ hallucinationReport }) => {
591
+ if (!hallucinationReport || Object.keys(hallucinationReport).length === 0) {
592
+ return /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column", paddingY: 1 }, /* @__PURE__ */ React5.createElement(
593
+ Box5,
594
+ {
595
+ flexDirection: "column",
596
+ borderStyle: "round",
597
+ borderColor: "yellow",
598
+ paddingX: 2,
599
+ paddingY: 1
600
+ },
601
+ /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: "yellow" }, "\u{1F4A1} Enable Hallucination Detection"),
602
+ /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Text5, null, "Hallucination detection is not enabled. To see risk assessment, run:"), /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1, borderStyle: "single", paddingX: 1 }, /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, "ai-lighthouse audit [URL] --enable-hallucination --enable-llm --llm-provider openai --llm-api-key YOUR_KEY")), /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "Note: Hallucination detection requires LLM analysis to be enabled")))
603
+ ));
604
+ }
605
+ const riskScore = hallucinationReport.hallucinationRiskScore;
606
+ const getRiskColor = (score) => {
607
+ if (score >= 70) return "red";
608
+ if (score >= 40) return "yellow";
609
+ return "green";
610
+ };
611
+ return /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column", paddingY: 1 }, /* @__PURE__ */ React5.createElement(
612
+ Box5,
613
+ {
614
+ flexDirection: "column",
615
+ borderStyle: "round",
616
+ borderColor: getRiskColor(riskScore),
617
+ paddingX: 2,
618
+ paddingY: 1,
619
+ marginBottom: 1
620
+ },
621
+ /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: getRiskColor(riskScore) }, "\u26A0\uFE0F Hallucination Risk Assessment"),
622
+ /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text5, null, /* @__PURE__ */ React5.createElement(Text5, { bold: true }, "Risk Score: "), /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: getRiskColor(riskScore) }, riskScore, "/100")))
623
+ ), hallucinationReport.factCheckSummary && /* @__PURE__ */ React5.createElement(
624
+ Box5,
625
+ {
626
+ flexDirection: "column",
627
+ borderStyle: "round",
628
+ borderColor: "blue",
629
+ paddingX: 2,
630
+ paddingY: 1,
631
+ marginBottom: 1
632
+ },
633
+ /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: "blue" }, "\u{1F4CA} Fact Check Summary"),
634
+ /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Text5, null, /* @__PURE__ */ React5.createElement(Text5, { bold: true }, "Total Facts:"), " ", hallucinationReport.factCheckSummary.totalFacts), /* @__PURE__ */ React5.createElement(Text5, null, /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: "green" }, "Verified:"), " ", hallucinationReport.factCheckSummary.verifiedFacts), /* @__PURE__ */ React5.createElement(Text5, null, /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: "yellow" }, "Unverified:"), " ", hallucinationReport.factCheckSummary.unverifiedFacts), /* @__PURE__ */ React5.createElement(Text5, null, /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: "red" }, "Contradictions:"), " ", hallucinationReport.factCheckSummary.contradictions), hallucinationReport.factCheckSummary.ambiguities !== void 0 && /* @__PURE__ */ React5.createElement(Text5, null, /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: "yellow" }, "Ambiguities:"), " ", hallucinationReport.factCheckSummary.ambiguities))
635
+ ), hallucinationReport.factCheckSummary?.unverifiedFacts > 0 && /* @__PURE__ */ React5.createElement(
636
+ Box5,
637
+ {
638
+ flexDirection: "column",
639
+ borderStyle: "round",
640
+ borderColor: "yellow",
641
+ paddingX: 2,
642
+ paddingY: 1,
643
+ marginBottom: 1
644
+ },
645
+ /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, "\u{1F4A1} Tip: Add citations and links to verify claims and reduce AI hallucination risk")
646
+ ), hallucinationReport.triggers && hallucinationReport.triggers.length > 0 && /* @__PURE__ */ React5.createElement(React5.Fragment, null, hallucinationReport.triggers.filter((t) => t.severity === "high" || t.severity === "critical").length > 0 && /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React5.createElement(Text5, { bold: true, underline: true, color: "red" }, "\u{1F6A8} High-Risk Triggers"), hallucinationReport.triggers.filter((t) => t.severity === "high" || t.severity === "critical").slice(0, 5).map((trigger, idx) => /* @__PURE__ */ React5.createElement(
647
+ Box5,
648
+ {
649
+ key: idx,
650
+ flexDirection: "column",
651
+ borderStyle: "single",
652
+ borderColor: "red",
653
+ paddingX: 1,
654
+ paddingY: 0.5,
655
+ marginTop: 1
656
+ },
657
+ /* @__PURE__ */ React5.createElement(Text5, null, idx + 1, ". ", /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: "red" }, "[", trigger.severity.toUpperCase(), "]"), " ", trigger.type),
658
+ /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, trigger.description),
659
+ trigger.confidence && /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "Confidence: ", Math.round(trigger.confidence * 100), "%")
660
+ )))), hallucinationReport.recommendations && hallucinationReport.recommendations.length > 0 && /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Text5, { bold: true, underline: true, color: "cyan" }, "\u{1F4A1} Recommendations"), hallucinationReport.recommendations.slice(0, 3).map((rec, idx) => /* @__PURE__ */ React5.createElement(Box5, { key: idx, marginTop: 0.5 }, /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, idx + 1, ". ", rec)))));
661
+ };
662
+
663
+ // src/ui/components/MessageAlignmentSection.tsx
664
+ import React6 from "react";
665
+ import { Box as Box6, Text as Text6 } from "ink";
666
+ var MessageAlignmentSection = ({ mirrorReport }) => {
667
+ if (!mirrorReport || Object.keys(mirrorReport).length === 0) {
668
+ return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column", paddingY: 1 }, /* @__PURE__ */ React6.createElement(
669
+ Box6,
670
+ {
671
+ flexDirection: "column",
672
+ borderStyle: "round",
673
+ borderColor: "yellow",
674
+ paddingX: 2,
675
+ paddingY: 1
676
+ },
677
+ /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "yellow" }, "\u{1F4A1} Enable Message Alignment Analysis"),
678
+ /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Text6, null, "Message alignment analysis is not available. To see what AI understands, run:"), /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1, borderStyle: "single", paddingX: 1 }, /* @__PURE__ */ React6.createElement(Text6, { color: "cyan" }, "ai-lighthouse audit [URL] --enable-llm --llm-provider openai --llm-api-key YOUR_KEY")), /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "This requires LLM analysis to compare AI understanding with your intended message")))
679
+ ));
680
+ }
681
+ const getScoreColor = (score) => {
682
+ if (score >= 80) return "green";
683
+ if (score >= 60) return "yellow";
684
+ return "red";
685
+ };
686
+ return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column", paddingY: 1 }, /* @__PURE__ */ React6.createElement(
687
+ Box6,
688
+ {
689
+ flexDirection: "column",
690
+ borderStyle: "round",
691
+ borderColor: "magenta",
692
+ paddingX: 2,
693
+ paddingY: 1,
694
+ marginBottom: 1
695
+ },
696
+ /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "magenta" }, "\u{1F50D} AI Misunderstanding Check"),
697
+ /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Box6, null, /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Alignment Score:"), " ", /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: getScoreColor(mirrorReport.summary.alignmentScore) }, mirrorReport.summary.alignmentScore, "/100"))), /* @__PURE__ */ React6.createElement(Box6, null, /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Clarity Score:"), " ", /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: getScoreColor(mirrorReport.summary.clarityScore) }, mirrorReport.summary.clarityScore, "/100"))), /* @__PURE__ */ React6.createElement(Box6, null, /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Critical Issues:"), " ", /* @__PURE__ */ React6.createElement(Text6, { color: "red" }, mirrorReport.summary.critical))), /* @__PURE__ */ React6.createElement(Box6, null, /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Major Issues:"), " ", /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, mirrorReport.summary.major))))
698
+ ), mirrorReport.llmInterpretation && /* @__PURE__ */ React6.createElement(
699
+ Box6,
700
+ {
701
+ flexDirection: "column",
702
+ borderStyle: "round",
703
+ borderColor: "blue",
704
+ paddingX: 2,
705
+ paddingY: 1,
706
+ marginBottom: 1
707
+ },
708
+ /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "blue" }, "\u{1F916} What AI Actually Understood"),
709
+ /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "(", Math.round(mirrorReport.llmInterpretation.confidence * 100), "% confident)"),
710
+ /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1, flexDirection: "column" }, mirrorReport.llmInterpretation.productName && /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Product:"), " ", mirrorReport.llmInterpretation.productName), mirrorReport.llmInterpretation.purpose && /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Purpose:"), " ", mirrorReport.llmInterpretation.purpose), mirrorReport.llmInterpretation.valueProposition && /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "magenta" }, "\u{1F48E} Value:"), " ", mirrorReport.llmInterpretation.valueProposition)),
711
+ mirrorReport.llmInterpretation.keyBenefits && mirrorReport.llmInterpretation.keyBenefits.length > 0 && /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Benefits:"), mirrorReport.llmInterpretation.keyBenefits.map((benefit, idx) => /* @__PURE__ */ React6.createElement(Text6, { key: idx }, "\u2022 ", benefit))),
712
+ mirrorReport.llmInterpretation.keyFeatures && mirrorReport.llmInterpretation.keyFeatures.length > 0 && /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Features:"), mirrorReport.llmInterpretation.keyFeatures.slice(0, 3).map((feature, idx) => /* @__PURE__ */ React6.createElement(Text6, { key: idx }, "\u2022 ", feature))),
713
+ mirrorReport.llmInterpretation.targetAudience && /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Audience:"), " ", mirrorReport.llmInterpretation.targetAudience))
714
+ ), mirrorReport.mismatches && mirrorReport.mismatches.length > 0 && /* @__PURE__ */ React6.createElement(React6.Fragment, null, mirrorReport.mismatches.filter((m) => m.severity === "critical" || m.severity === "major").length > 0 && /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React6.createElement(Text6, { bold: true, underline: true }, "Priority Mismatches"), mirrorReport.mismatches.filter((m) => m.severity === "critical" || m.severity === "major").slice(0, 5).map((mismatch, idx) => /* @__PURE__ */ React6.createElement(
715
+ Box6,
716
+ {
717
+ key: idx,
718
+ flexDirection: "column",
719
+ borderStyle: "single",
720
+ borderColor: mismatch.severity === "critical" ? "red" : "yellow",
721
+ paddingX: 1,
722
+ paddingY: 0.5,
723
+ marginTop: 1
724
+ },
725
+ /* @__PURE__ */ React6.createElement(Text6, null, mismatch.severity === "critical" ? "\u{1F534}" : "\u{1F7E1}", " ", idx + 1, ". ", /* @__PURE__ */ React6.createElement(Text6, { bold: true }, mismatch.field)),
726
+ /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, mismatch.description),
727
+ /* @__PURE__ */ React6.createElement(Box6, { marginTop: 0.5 }, /* @__PURE__ */ React6.createElement(Text6, { color: "cyan" }, "\u2192 ", mismatch.recommendation))
728
+ )))), mirrorReport.recommendations && mirrorReport.recommendations.length > 0 && /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Text6, { bold: true, underline: true, color: "cyan" }, "\u{1F4A1} Top Recommendations"), mirrorReport.recommendations.slice(0, 3).map((rec, idx) => /* @__PURE__ */ React6.createElement(Box6, { key: idx, marginTop: 0.5 }, /* @__PURE__ */ React6.createElement(Text6, { color: "cyan" }, idx + 1, ". ", rec)))));
729
+ };
730
+
731
+ // src/ui/components/TechnicalSection.tsx
732
+ import React7 from "react";
733
+ import { Box as Box7, Text as Text7 } from "ink";
734
+ var TechnicalSection = ({ result, scoring }) => {
735
+ const renderProgressBar = (score, width = 20) => {
736
+ const filled = Math.round(score / 100 * width);
737
+ const empty = width - filled;
738
+ const color = score >= 80 ? "green" : score >= 60 ? "yellow" : "red";
739
+ return /* @__PURE__ */ React7.createElement(Text7, { color }, "\u2588".repeat(filled), /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, "\u2591".repeat(empty)), ` ${Math.round(score)}%`);
740
+ };
741
+ return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", paddingY: 1 }, /* @__PURE__ */ React7.createElement(
742
+ Box7,
743
+ {
744
+ flexDirection: "column",
745
+ borderStyle: "round",
746
+ borderColor: "blue",
747
+ paddingX: 2,
748
+ paddingY: 1,
749
+ marginBottom: 1
750
+ },
751
+ /* @__PURE__ */ React7.createElement(Text7, { bold: true, color: "blue" }, "\u{1F4CA} Category Scores"),
752
+ /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, scoring?.crawlability !== void 0 && /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", marginTop: 0.5 }, /* @__PURE__ */ React7.createElement(Box7, { justifyContent: "space-between" }, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "\u{1F577}\uFE0F Crawlability"), /* @__PURE__ */ React7.createElement(Text7, null, Math.round(scoring.crawlability), "/100")), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 0.5 }, renderProgressBar(scoring.crawlability))), scoring?.structure !== void 0 && /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React7.createElement(Box7, { justifyContent: "space-between" }, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "\u{1F4D0} Structure"), /* @__PURE__ */ React7.createElement(Text7, null, Math.round(scoring.structure), "/100")), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 0.5 }, renderProgressBar(scoring.structure))), scoring?.schema_coverage !== void 0 && /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React7.createElement(Box7, { justifyContent: "space-between" }, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "\u{1F3F7}\uFE0F Schema Coverage"), /* @__PURE__ */ React7.createElement(Text7, null, Math.round(scoring.schema_coverage), "/100")), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 0.5 }, renderProgressBar(scoring.schema_coverage))), scoring?.content_clarity !== void 0 && /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React7.createElement(Box7, { justifyContent: "space-between" }, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "\u{1F4DD} Content Clarity"), /* @__PURE__ */ React7.createElement(Text7, null, Math.round(scoring.content_clarity), "/100")), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 0.5 }, renderProgressBar(scoring.content_clarity))))
753
+ ), result.chunking && Object.keys(result.chunking).length > 0 ? /* @__PURE__ */ React7.createElement(
754
+ Box7,
755
+ {
756
+ flexDirection: "column",
757
+ borderStyle: "round",
758
+ borderColor: "green",
759
+ paddingX: 2,
760
+ paddingY: 1,
761
+ marginBottom: 1
762
+ },
763
+ /* @__PURE__ */ React7.createElement(Text7, { bold: true, color: "green" }, "\u{1F4C4} Content Chunking Analysis"),
764
+ /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, null, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Strategy:"), " ", result.chunking.chunkingStrategy), /* @__PURE__ */ React7.createElement(Text7, null, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Total Chunks:"), " ", result.chunking.totalChunks), /* @__PURE__ */ React7.createElement(Text7, null, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Avg Tokens/Chunk:"), " ", result.chunking.averageTokensPerChunk), /* @__PURE__ */ React7.createElement(Text7, null, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Avg Noise Ratio:"), " ", (result.chunking.averageNoiseRatio * 100).toFixed(1), "%")),
765
+ result.chunking.chunkingStrategy === "heading-based" && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { color: "green" }, "\u2713 Heading-based chunking is ideal for AI comprehension")),
766
+ result.chunking.chunkingStrategy === "paragraph-based" && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { color: "yellow" }, "\u26A0 Consider adding headings for better semantic structure"))
767
+ ) : /* @__PURE__ */ React7.createElement(
768
+ Box7,
769
+ {
770
+ flexDirection: "column",
771
+ borderStyle: "round",
772
+ borderColor: "gray",
773
+ paddingX: 2,
774
+ paddingY: 1,
775
+ marginBottom: 1
776
+ },
777
+ /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, "\u{1F4A1} Enable chunking analysis with --enable-chunking to see how your content is divided for AI processing")
778
+ ), result.extractability && Object.keys(result.extractability).length > 0 ? /* @__PURE__ */ React7.createElement(
779
+ Box7,
780
+ {
781
+ flexDirection: "column",
782
+ borderStyle: "round",
783
+ borderColor: "yellow",
784
+ paddingX: 2,
785
+ paddingY: 1,
786
+ marginBottom: 1
787
+ },
788
+ /* @__PURE__ */ React7.createElement(Text7, { bold: true, color: "yellow" }, "\u{1F504} Extractability Analysis"),
789
+ /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, null, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Overall Score:"), " ", result.extractability.score.extractabilityScore, "/100"), /* @__PURE__ */ React7.createElement(Text7, null, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Server-Rendered:"), " ", result.extractability.score.serverRenderedPercent, "%")),
790
+ /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, { bold: true, underline: true }, "Content Type Extractability:"), Object.entries(result.extractability.contentTypes).map(([type, data]) => {
791
+ const percentage = data.percentage;
792
+ const color = percentage >= 80 ? "green" : percentage >= 50 ? "yellow" : "red";
793
+ return /* @__PURE__ */ React7.createElement(Box7, { key: type, marginTop: 0.5 }, /* @__PURE__ */ React7.createElement(Text7, null, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, type.charAt(0).toUpperCase() + type.slice(1), ":"), " ", /* @__PURE__ */ React7.createElement(Text7, { color }, percentage, "%"), " ", /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, "(", data.extractable, "/", data.total, ")")));
794
+ })),
795
+ result.extractability.score.extractabilityScore >= 80 && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { color: "green" }, "\u2713 Good extractability - AI can easily read your content")),
796
+ result.extractability.score.extractabilityScore < 50 && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { color: "red" }, "\u26A0 Low extractability - Consider server-side rendering"))
797
+ ) : /* @__PURE__ */ React7.createElement(
798
+ Box7,
799
+ {
800
+ flexDirection: "column",
801
+ borderStyle: "round",
802
+ borderColor: "gray",
803
+ paddingX: 2,
804
+ paddingY: 1,
805
+ marginBottom: 1
806
+ },
807
+ /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, "\u{1F4A1} Enable extractability analysis with --enable-extractability to see how well AI can extract your content")
808
+ ));
809
+ };
810
+
811
+ // src/ui/AuditReportUI.tsx
812
+ var AuditReportUI = ({
813
+ url,
814
+ result,
815
+ aiReadiness,
816
+ loading = false,
817
+ currentStep = ""
818
+ }) => {
819
+ const [currentTab, setCurrentTab] = useState2("overview");
820
+ const tabs = [
821
+ { key: "overview", label: "Overview", icon: "\u{1F4CA}" },
822
+ { key: "issues", label: "Issues", icon: "\u26A0\uFE0F" },
823
+ { key: "ai-understanding", label: "AI Understanding", icon: "\u{1F9E0}" },
824
+ { key: "hallucination", label: "Hallucination Risk", icon: "\u26A0\uFE0F" },
825
+ { key: "message-alignment", label: "Message Alignment", icon: "\u{1F50D}" },
826
+ { key: "technical", label: "Technical", icon: "\u2699\uFE0F" }
827
+ ];
828
+ useInput((input, key) => {
829
+ if (loading) return;
830
+ if (key.leftArrow) {
831
+ const currentIndex = tabs.findIndex((t) => t.key === currentTab);
832
+ const prevIndex = currentIndex > 0 ? currentIndex - 1 : tabs.length - 1;
833
+ setCurrentTab(tabs[prevIndex].key);
834
+ }
835
+ if (key.rightArrow) {
836
+ const currentIndex = tabs.findIndex((t) => t.key === currentTab);
837
+ const nextIndex = currentIndex < tabs.length - 1 ? currentIndex + 1 : 0;
838
+ setCurrentTab(tabs[nextIndex].key);
839
+ }
840
+ const num = parseInt(input);
841
+ if (num >= 1 && num <= tabs.length) {
842
+ setCurrentTab(tabs[num - 1].key);
843
+ }
844
+ });
845
+ if (loading) {
846
+ return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", paddingY: 1 }, /* @__PURE__ */ React8.createElement(Box8, null, /* @__PURE__ */ React8.createElement(Text8, { color: "green" }, /* @__PURE__ */ React8.createElement(Spinner, { type: "dots" })), /* @__PURE__ */ React8.createElement(Text8, null, " ", currentStep || "Loading...")));
847
+ }
848
+ const overallScore = aiReadiness?.overall || 0;
849
+ const grade = aiReadiness?.grade || "N/A";
850
+ return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column" }, /* @__PURE__ */ React8.createElement(
851
+ Box8,
852
+ {
853
+ flexDirection: "column",
854
+ borderStyle: "round",
855
+ borderColor: "cyan",
856
+ paddingX: 2,
857
+ paddingY: 1
858
+ },
859
+ /* @__PURE__ */ React8.createElement(Text8, { bold: true, color: "cyan" }, "\u{1F6A8} AI Lighthouse Report")
860
+ ), /* @__PURE__ */ React8.createElement(ScoreDisplay, { score: overallScore, grade, url }), /* @__PURE__ */ React8.createElement(
861
+ Box8,
862
+ {
863
+ flexDirection: "row",
864
+ borderStyle: "single",
865
+ borderColor: "blue",
866
+ paddingX: 1,
867
+ marginTop: 1
868
+ },
869
+ tabs.map((tab, index) => {
870
+ const isActive = tab.key === currentTab;
871
+ return /* @__PURE__ */ React8.createElement(Box8, { key: tab.key, marginRight: 1 }, /* @__PURE__ */ React8.createElement(
872
+ Text8,
873
+ {
874
+ bold: isActive,
875
+ color: isActive ? "cyan" : "gray",
876
+ backgroundColor: isActive ? "blue" : void 0
877
+ },
878
+ ` ${index + 1}. ${tab.icon} ${tab.label} `
879
+ ));
880
+ })
881
+ ), /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1, marginBottom: 1 }, /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, "Use \u2190 \u2192 arrow keys or numbers (1-", tabs.length, ") to navigate tabs")), /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column" }, currentTab === "overview" && /* @__PURE__ */ React8.createElement(OverviewSection, { aiReadiness }), currentTab === "issues" && /* @__PURE__ */ React8.createElement(IssuesSection, { issues: result.issues || [] }), currentTab === "ai-understanding" && /* @__PURE__ */ React8.createElement(AIUnderstandingSection, { llm: result.llm }), currentTab === "hallucination" && /* @__PURE__ */ React8.createElement(HallucinationSection, { hallucinationReport: result.hallucinationReport }), currentTab === "message-alignment" && /* @__PURE__ */ React8.createElement(MessageAlignmentSection, { mirrorReport: result.mirrorReport }), currentTab === "technical" && /* @__PURE__ */ React8.createElement(TechnicalSection, { result, scoring: result.scoring })), /* @__PURE__ */ React8.createElement(
882
+ Box8,
883
+ {
884
+ marginTop: 2,
885
+ borderStyle: "round",
886
+ borderColor: "gray",
887
+ paddingX: 2,
888
+ paddingY: 1
889
+ },
890
+ /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, "Press Ctrl+C to exit \u2022 Report generated at ", (/* @__PURE__ */ new Date()).toLocaleString())
891
+ ));
892
+ };
893
+
894
+ // src/ui/SetupWizard.tsx
895
+ import React9, { useState as useState3 } from "react";
896
+ import { Box as Box9, Text as Text9, useApp } from "ink";
897
+ import TextInput from "ink-text-input";
898
+ import SelectInput from "ink-select-input";
899
+ var SetupWizard = ({ onComplete, initialUrl }) => {
900
+ const { exit } = useApp();
901
+ const [step, setStep] = useState3(initialUrl ? "features" : "url");
902
+ const [url, setUrl] = useState3(initialUrl || "");
903
+ const [selectedFeatures, setSelectedFeatures] = useState3([]);
904
+ const [llmProvider, setLlmProvider] = useState3("");
905
+ const [llmModel, setLlmModel] = useState3("");
906
+ const [llmApiKey, setLlmApiKey] = useState3("");
907
+ const [llmBaseUrl, setLlmBaseUrl] = useState3("");
908
+ const featureOptions = [
909
+ { label: "\u{1F9E0} AI Understanding (LLM Analysis)", value: "llm" },
910
+ { label: "\u{1F4C4} Content Chunking Analysis", value: "chunking" },
911
+ { label: "\u{1F504} Extractability Analysis", value: "extractability" },
912
+ { label: "\u26A0\uFE0F Hallucination Detection", value: "hallucination" },
913
+ { label: "\u2705 Continue with selected features", value: "done" }
914
+ ];
915
+ const llmProviderOptions = [
916
+ { label: "OpenAI (GPT-4, GPT-3.5)", value: "openai" },
917
+ { label: "Anthropic (Claude)", value: "anthropic" },
918
+ { label: "Ollama (Local)", value: "ollama" },
919
+ { label: "Custom/Local Provider", value: "local" }
920
+ ];
921
+ const handleUrlSubmit = (value) => {
922
+ setUrl(value);
923
+ setStep("features");
924
+ };
925
+ const handleFeatureSelect = (item) => {
926
+ if (item.value === "done") {
927
+ if (selectedFeatures.includes("llm") || selectedFeatures.includes("hallucination")) {
928
+ setStep("llm-provider");
929
+ } else {
930
+ completeSetup();
931
+ }
932
+ } else {
933
+ if (selectedFeatures.includes(item.value)) {
934
+ setSelectedFeatures(selectedFeatures.filter((f) => f !== item.value));
935
+ } else {
936
+ setSelectedFeatures([...selectedFeatures, item.value]);
937
+ }
938
+ }
939
+ };
940
+ const handleLlmProviderSelect = (item) => {
941
+ setLlmProvider(item.value);
942
+ if (item.value === "ollama") {
943
+ setLlmBaseUrl("http://localhost:11434");
944
+ }
945
+ setStep("llm-model");
946
+ };
947
+ const handleLlmModelSubmit = (value) => {
948
+ setLlmModel(value);
949
+ if (llmProvider === "ollama") {
950
+ setStep("llm-base-url");
951
+ } else {
952
+ setStep("llm-api-key");
953
+ }
954
+ };
955
+ const handleLlmApiKeySubmit = (value) => {
956
+ setLlmApiKey(value);
957
+ if (llmProvider === "local" || llmProvider === "anthropic") {
958
+ setStep("llm-base-url");
959
+ } else {
960
+ completeSetup();
961
+ }
962
+ };
963
+ const handleLlmBaseUrlSubmit = (value) => {
964
+ setLlmBaseUrl(value);
965
+ completeSetup();
966
+ };
967
+ const completeSetup = () => {
968
+ const config = {
969
+ url,
970
+ enableLlm: selectedFeatures.includes("llm") || selectedFeatures.includes("hallucination"),
971
+ enableChunking: selectedFeatures.includes("chunking"),
972
+ enableExtractability: selectedFeatures.includes("extractability"),
973
+ enableHallucination: selectedFeatures.includes("hallucination")
974
+ };
975
+ if (config.enableLlm) {
976
+ config.llmProvider = llmProvider;
977
+ config.llmModel = llmModel;
978
+ config.llmApiKey = llmApiKey || void 0;
979
+ config.llmBaseUrl = llmBaseUrl || void 0;
980
+ }
981
+ onComplete(config);
982
+ exit();
983
+ };
984
+ const getModelPlaceholder = () => {
985
+ switch (llmProvider) {
986
+ case "openai":
987
+ return "gpt-4o-mini (default)";
988
+ case "anthropic":
989
+ return "claude-3-5-sonnet-20241022 (default)";
990
+ case "ollama":
991
+ return "qwen2.5:0.5b";
992
+ default:
993
+ return "model-name";
994
+ }
995
+ };
996
+ const getApiKeyPlaceholder = () => {
997
+ switch (llmProvider) {
998
+ case "openai":
999
+ return "sk-...";
1000
+ case "anthropic":
1001
+ return "sk-ant-...";
1002
+ default:
1003
+ return "your-api-key";
1004
+ }
1005
+ };
1006
+ return /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", paddingY: 1 }, /* @__PURE__ */ React9.createElement(
1007
+ Box9,
1008
+ {
1009
+ flexDirection: "column",
1010
+ borderStyle: "round",
1011
+ borderColor: "cyan",
1012
+ paddingX: 2,
1013
+ paddingY: 1,
1014
+ marginBottom: 1
1015
+ },
1016
+ /* @__PURE__ */ React9.createElement(Text9, { bold: true, color: "cyan" }, "\u{1F6A8} AI Lighthouse Setup Wizard"),
1017
+ /* @__PURE__ */ React9.createElement(Text9, { dimColor: true }, "Configure your audit settings")
1018
+ ), step === "url" && /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Text9, { bold: true }, "Enter the URL to audit:"), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text9, { color: "cyan" }, "URL: "), /* @__PURE__ */ React9.createElement(
1019
+ TextInput,
1020
+ {
1021
+ value: url,
1022
+ onChange: setUrl,
1023
+ onSubmit: handleUrlSubmit,
1024
+ placeholder: "https://example.com"
1025
+ }
1026
+ )), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text9, { dimColor: true }, "Press Enter to continue"))), step === "features" && /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Text9, { bold: true }, "Select features to enable:"), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text9, { dimColor: true }, "Selected: ", selectedFeatures.length === 0 ? "None (basic audit only)" : selectedFeatures.map((f) => {
1027
+ const feature = featureOptions.find((opt) => opt.value === f);
1028
+ return feature?.label.split(" ")[0];
1029
+ }).join(", "))), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(
1030
+ SelectInput,
1031
+ {
1032
+ items: featureOptions.map((opt) => ({
1033
+ ...opt,
1034
+ label: selectedFeatures.includes(opt.value) && opt.value !== "done" ? `\u2713 ${opt.label}` : opt.label
1035
+ })),
1036
+ onSelect: handleFeatureSelect
1037
+ }
1038
+ )), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text9, { dimColor: true }, "Use \u2191\u2193 to navigate, Enter to toggle/continue"))), step === "llm-provider" && /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Text9, { bold: true }, "Select LLM provider:"), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(SelectInput, { items: llmProviderOptions, onSelect: handleLlmProviderSelect })), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text9, { dimColor: true }, "Use \u2191\u2193 to navigate, Enter to select"))), step === "llm-model" && /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Text9, { bold: true }, "Enter LLM model name:"), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text9, { color: "cyan" }, "Model: "), /* @__PURE__ */ React9.createElement(
1039
+ TextInput,
1040
+ {
1041
+ value: llmModel,
1042
+ onChange: setLlmModel,
1043
+ onSubmit: handleLlmModelSubmit,
1044
+ placeholder: getModelPlaceholder()
1045
+ }
1046
+ )), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text9, { dimColor: true }, "Leave empty for default, or press Enter to continue"))), step === "llm-api-key" && /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Text9, { bold: true }, "Enter API key:"), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text9, { color: "cyan" }, "API Key: "), /* @__PURE__ */ React9.createElement(
1047
+ TextInput,
1048
+ {
1049
+ value: llmApiKey,
1050
+ onChange: setLlmApiKey,
1051
+ onSubmit: handleLlmApiKeySubmit,
1052
+ placeholder: getApiKeyPlaceholder(),
1053
+ mask: "*"
1054
+ }
1055
+ )), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text9, { dimColor: true }, "Your API key will not be stored"))), step === "llm-base-url" && /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Text9, { bold: true }, "Enter API base URL (optional):"), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text9, { color: "cyan" }, "Base URL: "), /* @__PURE__ */ React9.createElement(
1056
+ TextInput,
1057
+ {
1058
+ value: llmBaseUrl,
1059
+ onChange: setLlmBaseUrl,
1060
+ onSubmit: handleLlmBaseUrlSubmit,
1061
+ placeholder: llmProvider === "ollama" ? "http://localhost:11434" : "https://api.example.com"
1062
+ }
1063
+ )), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text9, { dimColor: true }, "Press Enter to continue (leave empty for default)"))));
1064
+ };
1065
+
1066
+ // src/commands/audit.ts
1067
+ function auditCommand(program2) {
1068
+ program2.command("audit").description("Audit a website for AI readiness").argument("<url>", "URL to audit").option("-o, --output <format>", "Output format: json, html, pdf, lhr, csv, interactive", "interactive").option("-r, --rules <preset>", "Rule preset: default, strict, minimal", "default").option("-d, --depth <number>", "Crawl depth (for multi-page audits)", parseInt, 1).option("-p, --pages <urls>", "Comma-separated list of specific pages to audit").option("--cache-ttl <seconds>", "Cache TTL in seconds to avoid re-fetching", parseInt).option("--threshold <score>", "Minimum score threshold (exit 1 if below)", parseInt).option("--max-chunk-tokens <number>", "Maximum tokens per content chunk", parseInt, 1200).option("--chunking-strategy <strategy>", "Chunking strategy: auto, heading-based, paragraph-based", "auto").option("--enable-chunking", "Enable detailed content chunking analysis", false).option("--enable-extractability", "Enable extractability mapping", false).option("--enable-hallucination", "Enable hallucination detection", false).option("--enable-llm", "Enable LLM comprehension analysis", false).option("--min-impact <number>", "Minimum impact score to include", parseInt, 8).option("--min-confidence <number>", "Minimum confidence to include (0-1)", parseFloat, 0.7).option("--max-issues <number>", "Maximum issues to return", parseInt, 20).option("--llm-provider <provider>", "LLM provider: openai, anthropic, ollama, local").option("--llm-model <model>", "LLM model name").option("--llm-base-url <url>", "LLM API base URL").option("--llm-api-key <key>", "LLM API key").action(async (url, options) => {
1069
+ const hasFeatureFlags = options.enableChunking || options.enableExtractability || options.enableHallucination || options.enableLlm || options.llmProvider;
1070
+ if (options.output === "interactive" && !hasFeatureFlags) {
1071
+ const originalConsoleError = console.error;
1072
+ const originalConsoleWarn = console.warn;
1073
+ console.error = () => {
1074
+ };
1075
+ console.warn = () => {
1076
+ };
1077
+ let auditConfig = null;
1078
+ const wizardRender = render(
1079
+ React10.createElement(SetupWizard, {
1080
+ initialUrl: url,
1081
+ onComplete: (config) => {
1082
+ auditConfig = config;
1083
+ }
1084
+ })
1085
+ );
1086
+ await wizardRender.waitUntilExit();
1087
+ if (!auditConfig) {
1088
+ console.error = originalConsoleError;
1089
+ console.warn = originalConsoleWarn;
1090
+ console.log("\nAudit cancelled.");
1091
+ process.exit(0);
1092
+ }
1093
+ url = auditConfig.url;
1094
+ options.enableChunking = auditConfig.enableChunking;
1095
+ options.enableExtractability = auditConfig.enableExtractability;
1096
+ options.enableHallucination = auditConfig.enableHallucination;
1097
+ options.enableLlm = auditConfig.enableLlm;
1098
+ options.llmProvider = auditConfig.llmProvider;
1099
+ options.llmModel = auditConfig.llmModel;
1100
+ options.llmApiKey = auditConfig.llmApiKey;
1101
+ options.llmBaseUrl = auditConfig.llmBaseUrl;
1102
+ }
1103
+ if (options.output === "interactive") {
1104
+ const originalConsoleError = console.error;
1105
+ const originalConsoleWarn = console.warn;
1106
+ console.error = () => {
1107
+ };
1108
+ console.warn = () => {
1109
+ };
1110
+ const { waitUntilExit, clear, rerender } = render(
1111
+ React10.createElement(AuditReportUI, {
1112
+ url,
1113
+ result: {},
1114
+ aiReadiness: {},
1115
+ loading: true,
1116
+ currentStep: "Starting audit..."
1117
+ })
1118
+ );
1119
+ try {
1120
+ const urlObj = new URL(url);
1121
+ const scanOptions = {
1122
+ maxChunkTokens: options.maxChunkTokens,
1123
+ chunkingStrategy: options.chunkingStrategy,
1124
+ enableChunking: options.enableChunking,
1125
+ enableExtractability: options.enableExtractability,
1126
+ enableHallucinationDetection: options.enableHallucination,
1127
+ enableLLM: options.enableLlm,
1128
+ minImpactScore: options.minImpact,
1129
+ minConfidence: options.minConfidence,
1130
+ maxIssues: options.maxIssues
1131
+ };
1132
+ if (options.enableLlm && options.llmProvider) {
1133
+ scanOptions.llmConfig = {
1134
+ provider: options.llmProvider,
1135
+ model: options.llmModel,
1136
+ baseUrl: options.llmBaseUrl,
1137
+ apiKey: options.llmApiKey
1138
+ };
1139
+ }
1140
+ rerender(
1141
+ React10.createElement(AuditReportUI, {
1142
+ url: urlObj.href,
1143
+ result: {},
1144
+ aiReadiness: {},
1145
+ loading: true,
1146
+ currentStep: "Scanning page..."
1147
+ })
1148
+ );
1149
+ const result = await analyzeUrlWithRules(url, scanOptions);
1150
+ rerender(
1151
+ React10.createElement(AuditReportUI, {
1152
+ url: urlObj.href,
1153
+ result,
1154
+ aiReadiness: {},
1155
+ loading: true,
1156
+ currentStep: "Calculating AI readiness scores..."
1157
+ })
1158
+ );
1159
+ const aiReadiness = calculateAIReadiness(result);
1160
+ clear();
1161
+ const finalRender = render(
1162
+ React10.createElement(AuditReportUI, {
1163
+ url: urlObj.href,
1164
+ result,
1165
+ aiReadiness,
1166
+ loading: false
1167
+ })
1168
+ );
1169
+ await finalRender.waitUntilExit();
1170
+ console.error = originalConsoleError;
1171
+ console.warn = originalConsoleWarn;
1172
+ if (options.threshold !== void 0) {
1173
+ const overallScore = aiReadiness.overall;
1174
+ if (overallScore !== void 0 && overallScore < options.threshold) {
1175
+ process.exit(1);
1176
+ }
1177
+ }
1178
+ } catch (error) {
1179
+ console.error = originalConsoleError;
1180
+ console.warn = originalConsoleWarn;
1181
+ clear();
1182
+ console.log("\n" + chalk2.bold.red("\u274C Audit Failed"));
1183
+ console.log(chalk2.red("\u2500".repeat(70)));
1184
+ console.log(chalk2.red(error instanceof Error ? error.message : String(error)));
1185
+ console.log("\n" + chalk2.dim("Please check the URL and your configuration."));
1186
+ process.exit(1);
1187
+ }
1188
+ return;
1189
+ }
1190
+ const spinner = ora("Starting audit...").start();
1191
+ try {
1192
+ const urlObj = new URL(url);
1193
+ spinner.text = `Auditing ${chalk2.cyan(urlObj.href)}...`;
1194
+ const scanOptions = {
1195
+ maxChunkTokens: options.maxChunkTokens,
1196
+ chunkingStrategy: options.chunkingStrategy,
1197
+ enableChunking: options.enableChunking,
1198
+ enableExtractability: options.enableExtractability,
1199
+ enableHallucinationDetection: options.enableHallucination,
1200
+ enableLLM: options.enableLlm,
1201
+ minImpactScore: options.minImpact,
1202
+ minConfidence: options.minConfidence,
1203
+ maxIssues: options.maxIssues
1204
+ };
1205
+ if (options.enableLlm && options.llmProvider) {
1206
+ scanOptions.llmConfig = {
1207
+ provider: options.llmProvider,
1208
+ model: options.llmModel,
1209
+ baseUrl: options.llmBaseUrl,
1210
+ apiKey: options.llmApiKey
1211
+ };
1212
+ }
1213
+ spinner.text = "Scanning page...";
1214
+ const result = await analyzeUrlWithRules(url, scanOptions);
1215
+ spinner.text = "Calculating scores...";
1216
+ const aiReadiness = calculateAIReadiness(result);
1217
+ spinner.text = "Generating report...";
1218
+ const auditReportJson = exportAuditReport(result);
1219
+ const auditReport = JSON.parse(auditReportJson);
1220
+ const outputDir = resolve(process.cwd(), ".ai-lighthouse");
1221
+ if (!existsSync(outputDir)) {
1222
+ await mkdir(outputDir, { recursive: true });
1223
+ }
1224
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-").split(".")[0];
1225
+ const baseFilename = `audit_${new URL(url).hostname}_${timestamp}`;
1226
+ if (options.output === "json") {
1227
+ const jsonPath = join(outputDir, `${baseFilename}.json`);
1228
+ await writeFile(jsonPath, auditReportJson);
1229
+ spinner.succeed(chalk2.green("Audit complete!"));
1230
+ console.log(chalk2.dim(`Report saved to: ${jsonPath}`));
1231
+ } else if (options.output === "html") {
1232
+ const htmlPath = join(outputDir, `${baseFilename}.html`);
1233
+ const html = generateHTMLReport(auditReport, aiReadiness, result);
1234
+ await writeFile(htmlPath, html);
1235
+ spinner.succeed(chalk2.green("Audit complete!"));
1236
+ console.log(chalk2.dim(`HTML report saved to: ${htmlPath}`));
1237
+ console.log(chalk2.dim(`\u{1F4A1} Tip: Open the HTML file and use your browser's "Print > Save as PDF" to export as PDF`));
1238
+ } else if (options.output === "pdf") {
1239
+ spinner.text = "Generating PDF...";
1240
+ const pdfPath = join(outputDir, `${baseFilename}.pdf`);
1241
+ const html = generateHTMLReport(auditReport, aiReadiness, result);
1242
+ const file = { content: html };
1243
+ const pdfOptions = {
1244
+ format: "A4",
1245
+ printBackground: false
1246
+ };
1247
+ const pdfBuffer = await html_to_pdf.generatePdf(file, pdfOptions);
1248
+ await writeFile(pdfPath, pdfBuffer);
1249
+ spinner.succeed(chalk2.green("PDF generated!"));
1250
+ console.log(chalk2.dim(`PDF report saved to: ${pdfPath}`));
1251
+ } else if (options.output === "lhr") {
1252
+ const lhrPath = join(outputDir, `${baseFilename}.lhr.json`);
1253
+ const lhr = convertToLighthouseFormat(auditReport);
1254
+ await writeFile(lhrPath, JSON.stringify(lhr, null, 2));
1255
+ spinner.succeed(chalk2.green("Audit complete!"));
1256
+ console.log(chalk2.dim(`Lighthouse-compatible report saved to: ${lhrPath}`));
1257
+ } else if (options.output === "csv") {
1258
+ const csvPath = join(outputDir, `${baseFilename}.csv`);
1259
+ const csv = generateCSVReport(auditReport);
1260
+ await writeFile(csvPath, csv);
1261
+ spinner.succeed(chalk2.green("Audit complete!"));
1262
+ console.log(chalk2.dim(`CSV report saved to: ${csvPath}`));
1263
+ }
1264
+ console.log("\n" + chalk2.bold("\u{1F4CA} AI Readiness Summary"));
1265
+ console.log(formatAIReadinessReport(aiReadiness));
1266
+ console.log("\n" + chalk2.bold("\u{1F4C8} Technical Scores"));
1267
+ console.log(generateScoringSummary(result.scoring));
1268
+ console.log("\n" + chalk2.bold("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
1269
+ console.log(chalk2.bold.cyan(" COMPREHENSIVE ANALYSIS "));
1270
+ console.log(chalk2.bold("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
1271
+ console.log(formatComprehensiveReport(result, aiReadiness));
1272
+ if (result.issues && result.issues.length > 0) {
1273
+ console.log("\n" + formatDetailedIssues(result.issues));
1274
+ }
1275
+ if (options.threshold !== void 0) {
1276
+ const overallScore = auditReport?.scores?.overall;
1277
+ if (overallScore !== void 0 && overallScore < options.threshold) {
1278
+ console.log(chalk2.red(`
1279
+ \u274C Score ${overallScore} is below threshold ${options.threshold}`));
1280
+ process.exit(1);
1281
+ } else if (overallScore !== void 0) {
1282
+ console.log(chalk2.green(`
1283
+ \u2705 Score ${overallScore} meets threshold ${options.threshold}`));
1284
+ }
1285
+ }
1286
+ } catch (error) {
1287
+ spinner.fail(chalk2.red("Audit failed"));
1288
+ if (error instanceof Error) {
1289
+ console.error(chalk2.red(error.message));
1290
+ }
1291
+ process.exit(1);
1292
+ }
1293
+ });
1294
+ }
1295
+ function generateHTMLReport(report, aiReadiness, scanResult) {
1296
+ return `<!DOCTYPE html>
1297
+ <html lang="en">
1298
+ <head>
1299
+ <meta charset="UTF-8">
1300
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1301
+ <title>AI Lighthouse Report - ${report.input?.requested_url}</title>
1302
+ <style>
1303
+ * { margin: 0; padding: 0; box-sizing: border-box; }
1304
+ body {
1305
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
1306
+ line-height: 1.6;
1307
+ color: #333;
1308
+ background: #f5f5f5;
1309
+ padding: 20px;
1310
+ }
1311
+ .container { color: #333; max-width: 1200px; margin: 0 auto; background: white; padding: 40px; border-radius: 8px; }
1312
+ h1 { color: #2563eb; margin-bottom: 10px; font-size: 2em; }
1313
+ h2 { color: #1e40af; margin-top: 30px; margin-bottom: 15px; border-bottom: 2px solid #dbeafe; padding-bottom: 10px; }
1314
+ h3 { color: #1e40af; margin-top: 20px; margin-bottom: 10px; font-size: 1.2em; }
1315
+ .header { margin-bottom: 30px; }
1316
+ .url { color: #64748b; font-size: 0.95em; }
1317
+ .timestamp { color: #94a3b8; font-size: 0.85em; }
1318
+
1319
+ .ai-readiness-banner {
1320
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1321
+ color: white;
1322
+ padding: 30px;
1323
+ border-radius: 8px;
1324
+ margin: 20px 0;
1325
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
1326
+ }
1327
+ .ai-readiness-banner h2 { color: white; border: none; margin: 0 0 20px 0; }
1328
+ .overall-score { font-size: 3em; font-weight: bold; margin: 10px 0; }
1329
+ .grade-badge { background: rgba(255,255,255,0.2); padding: 8px 16px; border-radius: 20px; display: inline-block; margin: 10px 0; }
1330
+ .agent-perspective { background: rgba(255,255,255,0.1); padding: 20px; border-radius: 8px; margin: 20px 0; }
1331
+ .agent-status { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin: 15px 0; }
1332
+ .agent-status-item { display: flex; align-items: center; gap: 10px; }
1333
+
1334
+ .scores { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 20px 0; }
1335
+ .score-card {
1336
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1337
+ color: white;
1338
+ padding: 20px;
1339
+ border-radius: 8px;
1340
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
1341
+ }
1342
+ .score-card h3 { font-size: 0.9em; opacity: 0.9; margin-bottom: 10px; }
1343
+ .score-value { font-size: 2.5em; font-weight: bold; }
1344
+ .grade { font-size: 1.2em; opacity: 0.8; margin-left: 10px; }
1345
+
1346
+ .dimensions { display: grid; gap: 15px; margin: 20px 0; }
1347
+ .dimension { background: #f8fafc; border-left: 4px solid #cbd5e1; padding: 20px; border-radius: 4px; }
1348
+ .dimension.excellent { border-left-color: #10b981; }
1349
+ .dimension.good { border-left-color: #84cc16; }
1350
+ .dimension.needs-work { border-left-color: #f59e0b; }
1351
+ .dimension.critical { border-left-color: #dc2626; }
1352
+ .dimension-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
1353
+ .dimension-name { font-weight: 600; font-size: 1.1em; color: #1e293b; }
1354
+ .dimension-score { font-size: 1.5em; font-weight: bold; }
1355
+ .dimension-level { color: #64748b; font-size: 0.9em; margin-bottom: 10px; text-transform: uppercase; }
1356
+ .dimension-recommendations { margin-top: 10px; padding-left: 20px; }
1357
+ .dimension-recommendations li { margin: 5px 0; color: #475569; }
1358
+
1359
+ .quick-wins { background: #fffbeb; border: 2px solid #fbbf24; padding: 20px; border-radius: 8px; margin: 20px 0; }
1360
+ .quick-wins h3 { color: #92400e; margin-top: 0; }
1361
+ .quick-win { background: white; padding: 15px; margin: 10px 0; border-radius: 4px; border-left: 3px solid #f59e0b; }
1362
+ .quick-win-header { font-weight: 600; color: #1e293b; margin-bottom: 8px; }
1363
+ .quick-win-meta { font-size: 0.85em; color: #64748b; }
1364
+
1365
+ .priorities { margin: 20px 0; }
1366
+ .priority-section { background: #f8fafc; padding: 20px; border-radius: 8px; margin: 15px 0; }
1367
+ .priority-section.immediate { border-left: 4px solid #dc2626; }
1368
+ .priority-section.short-term { border-left: 4px solid #f59e0b; }
1369
+ .priority-section.long-term { border-left: 4px solid #3b82f6; }
1370
+ .priority-item { margin: 10px 0; padding-left: 20px; }
1371
+
1372
+ .issues { margin: 20px 0; }
1373
+ .issue {
1374
+ background: #f8fafc;
1375
+ border-left: 4px solid #cbd5e1;
1376
+ padding: 15px;
1377
+ margin-bottom: 15px;
1378
+ border-radius: 4px;
1379
+ }
1380
+ .issue.critical { border-left-color: #dc2626; background: #fef2f2; }
1381
+ .issue.high { border-left-color: #ea580c; background: #fff7ed; }
1382
+ .issue.medium { border-left-color: #f59e0b; background: #fffbeb; }
1383
+ .issue.low { border-left-color: #84cc16; background: #f7fee7; }
1384
+ .issue-title { font-weight: 600; color: #1e293b; margin-bottom: 8px; }
1385
+ .issue-meta { font-size: 0.85em; color: #64748b; margin-bottom: 8px; }
1386
+ .issue-desc { color: #475569; margin-bottom: 8px; }
1387
+ .issue-fix { color: #0f766e; background: #f0fdfa; padding: 10px; border-radius: 4px; font-size: 0.9em; }
1388
+
1389
+ .entity-list { display: grid; gap: 15px; }
1390
+ .entity { background: #f0f9ff; border: 1px solid #bae6fd; padding: 15px; border-radius: 4px; }
1391
+ .entity-name { font-weight: 600; color: #0c4a6e; }
1392
+ .entity-type { color: #0369a1; font-size: 0.85em; }
1393
+
1394
+ .print-button {
1395
+ background: #2563eb;
1396
+ color: white;
1397
+ padding: 12px 24px;
1398
+ border: none;
1399
+ border-radius: 6px;
1400
+ cursor: pointer;
1401
+ font-size: 1em;
1402
+ margin: 20px 0;
1403
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
1404
+ }
1405
+ .print-button:hover { background: #1e40af; }
1406
+
1407
+ @media print {
1408
+ body { background: white; padding: 0; }
1409
+ .container { box-shadow: none; }
1410
+ .print-button { display: none; }
1411
+ }
1412
+ </style>
1413
+ </head>
1414
+ <body>
1415
+ <div class="container">
1416
+ <div class="header">
1417
+ <h1>\u{1F6A8} AI Lighthouse Report</h1>
1418
+ <div class="url">${report.input?.requested_url}</div>
1419
+ <div class="timestamp">Generated: ${new Date(report.scanned_at).toLocaleString()}</div>
1420
+ </div>
1421
+
1422
+ ${aiReadiness ? `
1423
+ <div class="ai-readiness-banner">
1424
+ <h2>\u{1F916} AI Readiness Assessment</h2>
1425
+ <div class="overall-score">${Math.round(aiReadiness.overall)}/100</div>
1426
+ <div class="grade-badge">Grade: ${aiReadiness.grade}</div>
1427
+ <div style="margin: 10px 0; opacity: 0.9;">
1428
+ Top ${aiReadiness.benchmark?.topPercentile || 0}% of sites \u2022
1429
+ Improvement potential: +${Math.round(aiReadiness.benchmark?.improvement || 0)} points to best-in-class
1430
+ </div>
1431
+
1432
+ <div class="agent-perspective">
1433
+ <strong>AI Agent Perspective:</strong>
1434
+ <div class="agent-status">
1435
+ <div class="agent-status-item">
1436
+ <span>${aiReadiness.aiPerspective?.canUnderstand ? "\u2705" : "\u274C"}</span>
1437
+ <span>Can Understand</span>
1438
+ </div>
1439
+ <div class="agent-status-item">
1440
+ <span>${aiReadiness.aiPerspective?.canExtract ? "\u2705" : "\u274C"}</span>
1441
+ <span>Can Extract</span>
1442
+ </div>
1443
+ <div class="agent-status-item">
1444
+ <span>${aiReadiness.aiPerspective?.canIndex ? "\u2705" : "\u274C"}</span>
1445
+ <span>Can Index</span>
1446
+ </div>
1447
+ <div class="agent-status-item">
1448
+ <span>${aiReadiness.aiPerspective?.canAnswer ? "\u2705" : "\u274C"}</span>
1449
+ <span>Can Answer Questions</span>
1450
+ </div>
1451
+ </div>
1452
+ <div style="margin-top: 15px;">
1453
+ <strong>Confidence Level:</strong> ${Math.round((aiReadiness.aiPerspective?.confidence || 0) * 100)}%
1454
+ </div>
1455
+ </div>
1456
+
1457
+ ${(aiReadiness.aiPerspective?.mainBlockers || []).length > 0 ? `
1458
+ <div style="margin-top: 20px;">
1459
+ <strong>Main Blockers:</strong>
1460
+ <ul style="margin: 10px 0; padding-left: 20px;">
1461
+ ${(aiReadiness.aiPerspective?.mainBlockers || []).map((blocker) => `<li>${blocker}</li>`).join("")}
1462
+ </ul>
1463
+ </div>
1464
+ ` : ""}
1465
+ </div>
1466
+ ` : ""}
1467
+
1468
+ <h2>\u{1F4CA} Technical Scores</h2>
1469
+ <div class="scores">
1470
+ <div class="score-card">
1471
+ <h3>Overall Score</h3>
1472
+ <div>
1473
+ <span class="score-value">${report.scores?.overall || 0}</span>
1474
+ <span class="grade">${getLetterGrade(report.scores?.overall || 0)}</span>
1475
+ </div>
1476
+ </div>
1477
+ <div class="score-card">
1478
+ <h3>AI Readiness</h3>
1479
+ <div class="score-value">${report.scores?.ai_readiness || 0}</div>
1480
+ </div>
1481
+ <div class="score-card">
1482
+ <h3>Crawlability</h3>
1483
+ <div class="score-value">${report.scores?.crawlability || 0}</div>
1484
+ </div>
1485
+ <div class="score-card">
1486
+ <h3>Content Clarity</h3>
1487
+ <div class="score-value">${report.scores?.content_clarity || 0}</div>
1488
+ </div>
1489
+ <div class="score-card">
1490
+ <h3>Schema Coverage</h3>
1491
+ <div class="score-value">${report.scores?.schema_coverage || 0}</div>
1492
+ </div>
1493
+ <div class="score-card">
1494
+ <h3>Structure</h3>
1495
+ <div class="score-value">${report.scores?.structure || 0}</div>
1496
+ </div>
1497
+ </div>
1498
+
1499
+ ${aiReadiness?.dimensions ? `
1500
+ <h2>\u{1F3AF} Dimension Analysis</h2>
1501
+ <div class="dimensions">
1502
+ ${Object.entries(aiReadiness.dimensions).map(([key, dim]) => `
1503
+ <div class="dimension ${dim.status}">
1504
+ <div class="dimension-header">
1505
+ <div class="dimension-name">${getEmojiForDimension(key)} ${formatDimensionName2(key)}</div>
1506
+ <div class="dimension-score">${Math.round(dim.score)}/100</div>
1507
+ </div>
1508
+ <div class="dimension-level">${dim.status}</div>
1509
+ ${dim.strengths && dim.strengths.length > 0 ? `
1510
+ <div><strong>Strengths:</strong> ${dim.strengths.join(", ")}</div>
1511
+ ` : ""}
1512
+ ${dim.weaknesses && dim.weaknesses.length > 0 ? `
1513
+ <div style="margin-top: 8px;"><strong>Weaknesses:</strong> ${dim.weaknesses.join(", ")}</div>
1514
+ ` : ""}
1515
+ ${dim.recommendation ? `
1516
+ <div class="dimension-recommendations">
1517
+ <div style="margin-top: 10px;">\u2192 ${dim.recommendation}</div>
1518
+ </div>
1519
+ ` : ""}
1520
+ </div>
1521
+ `).join("")}
1522
+ </div>
1523
+ ` : ""}
1524
+
1525
+ ${aiReadiness?.quickWins && aiReadiness.quickWins.length > 0 ? `
1526
+ <div class="quick-wins">
1527
+ <h3>\u26A1 Quick Wins (High Impact, Low Effort)</h3>
1528
+ ${aiReadiness.quickWins.slice(0, 5).map((win, idx) => `
1529
+ <div class="quick-win">
1530
+ <div class="quick-win-header">${idx + 1}. ${win.issue}</div>
1531
+ <div class="quick-win-meta">Impact: ${win.impact} | Effort: ${win.effort}</div>
1532
+ <div style="margin-top: 8px; color: #0f766e;">\u2192 ${win.fix}</div>
1533
+ </div>
1534
+ `).join("")}
1535
+ </div>
1536
+ ` : ""}
1537
+
1538
+ ${aiReadiness?.roadmap ? `
1539
+ <h2>\u{1F4CB} Priority Roadmap</h2>
1540
+ <div class="priorities">
1541
+ ${aiReadiness.roadmap.immediate && aiReadiness.roadmap.immediate.length > 0 ? `
1542
+ <div class="priority-section immediate">
1543
+ <h3>\u{1F534} Immediate (&lt; 1 day)</h3>
1544
+ ${aiReadiness.roadmap.immediate.slice(0, 5).map((item) => `
1545
+ <div class="priority-item">\u2022 ${item}</div>
1546
+ `).join("")}
1547
+ </div>
1548
+ ` : ""}
1549
+
1550
+ ${aiReadiness.roadmap.shortTerm && aiReadiness.roadmap.shortTerm.length > 0 ? `
1551
+ <div class="priority-section short-term">
1552
+ <h3>\u{1F7E1} Short-term (1-7 days)</h3>
1553
+ ${aiReadiness.roadmap.shortTerm.slice(0, 5).map((item) => `
1554
+ <div class="priority-item">\u2022 ${item}</div>
1555
+ `).join("")}
1556
+ </div>
1557
+ ` : ""}
1558
+
1559
+ ${aiReadiness.roadmap.longTerm && aiReadiness.roadmap.longTerm.length > 0 ? `
1560
+ <div class="priority-section long-term">
1561
+ <h3>\u{1F535} Long-term (&gt; 7 days)</h3>
1562
+ ${aiReadiness.roadmap.longTerm.slice(0, 5).map((item) => `
1563
+ <div class="priority-item">\u2022 ${item}</div>
1564
+ `).join("")}
1565
+ </div>
1566
+ ` : ""}
1567
+ </div>
1568
+ ` : ""}
1569
+
1570
+ ${scanResult?.llm ? `
1571
+ <h2>\u{1F4DD} AI Understanding Analysis</h2>
1572
+ <div style="background: #f0f9ff; border: 2px solid #0ea5e9; padding: 20px; border-radius: 8px; margin: 20px 0;">
1573
+ <div style="margin-bottom: 15px;">
1574
+ <strong>Summary:</strong> ${scanResult.llm.summary || "N/A"}
1575
+ </div>
1576
+ ${scanResult.llm.pageType ? `
1577
+ <div style="margin-bottom: 15px;">
1578
+ <strong>\u{1F4C4} Page Type:</strong> ${scanResult.llm.pageType}
1579
+ </div>
1580
+ ` : ""}
1581
+ ${scanResult.llm.keyTopics && scanResult.llm.keyTopics.length > 0 ? `
1582
+ <div style="margin-bottom: 15px;">
1583
+ <strong>\u{1F3F7}\uFE0F Key Topics:</strong> ${scanResult.llm.keyTopics.join(", ")}
1584
+ </div>
1585
+ ` : ""}
1586
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-top: 15px;">
1587
+ ${scanResult.llm.readingLevel ? `
1588
+ <div><strong>\u{1F4CA} Reading Level:</strong> ${scanResult.llm.readingLevel.description}</div>
1589
+ ` : ""}
1590
+ ${scanResult.llm.sentiment ? `
1591
+ <div><strong>\u{1F3AD} Sentiment:</strong> ${scanResult.llm.sentiment}</div>
1592
+ ` : ""}
1593
+ ${scanResult.llm.technicalDepth ? `
1594
+ <div><strong>\u{1F3AF} Technical Depth:</strong> ${scanResult.llm.technicalDepth}</div>
1595
+ ` : ""}
1596
+ </div>
1597
+
1598
+ ${scanResult.llm.pageTypeInsights && scanResult.llm.pageTypeInsights.length > 0 ? `
1599
+ <div style="margin-top: 20px; background: #dbeafe; padding: 15px; border-radius: 4px; border-left: 4px solid #0ea5e9;">
1600
+ <div style="font-weight: 600; margin-bottom: 10px;">\u{1F4A1} AI-Generated Insights for ${scanResult.llm.pageType || "This Page"}</div>
1601
+ <ul style="margin: 0; padding-left: 20px;">
1602
+ ${scanResult.llm.pageTypeInsights.map((insight) => `<li style="margin: 5px 0;">${insight}</li>`).join("")}
1603
+ </ul>
1604
+ </div>
1605
+ ` : ""}
1606
+
1607
+
1608
+ ${scanResult.llm.topEntities && scanResult.llm.topEntities.length > 0 ? `
1609
+ <div style="margin-top: 20px;">
1610
+ <strong>\u{1F50D} Key Entities:</strong>
1611
+ <div style="margin-top: 10px; display: grid; gap: 10px;">
1612
+ ${scanResult.llm.topEntities.slice(0, 5).map((entity) => `
1613
+ <div style="background: white; padding: 10px; border-radius: 4px; border-left: 3px solid #0ea5e9;">
1614
+ <strong>${entity.name}</strong> (${entity.type}) - ${Math.round((entity.relevance || 0) * 100)}% relevance
1615
+ </div>
1616
+ `).join("")}
1617
+ </div>
1618
+ </div>
1619
+ ` : ""}
1620
+
1621
+ ${scanResult.llm.questions && scanResult.llm.questions.length > 0 ? `
1622
+ <div style="margin-top: 20px;">
1623
+ <strong>\u2753 Key Questions AI Can Answer:</strong>
1624
+ <ol style="margin-top: 10px; padding-left: 25px;">
1625
+ ${scanResult.llm.questions.slice(0, 5).map((q) => `
1626
+ <li style="margin: 8px 0;"><span style="text-transform: uppercase; font-size: 0.8em; background: #dbeafe; padding: 2px 6px; border-radius: 3px;">${q.difficulty}</span> ${q.question}</li>
1627
+ `).join("")}
1628
+ </ol>
1629
+ </div>
1630
+ ` : ""}
1631
+
1632
+ ${scanResult.llm.suggestedFAQ && scanResult.llm.suggestedFAQ.length > 0 ? `
1633
+ <div style="margin-top: 20px;">
1634
+ <strong>\u{1F4A1} Suggested FAQs:</strong>
1635
+ <div style="margin-top: 10px; display: grid; gap: 15px;">
1636
+ ${scanResult.llm.suggestedFAQ.filter((f) => f.importance === "high").slice(0, 3).map((faq) => `
1637
+ <div style="background: white; padding: 15px; border-radius: 4px; border-left: 3px solid #f59e0b;">
1638
+ <div style="font-weight: 600; margin-bottom: 5px;">Q: ${faq.question}</div>
1639
+ <div style="color: #64748b;">A: ${faq.suggestedAnswer}</div>
1640
+ </div>
1641
+ `).join("")}
1642
+ </div>
1643
+ </div>
1644
+ ` : ""}
1645
+ </div>
1646
+ ` : ""}
1647
+
1648
+ ${scanResult?.chunking ? `
1649
+ <h2>\u{1F4C4} Content Chunking Analysis</h2>
1650
+ <div style="background: #f0fdf4; border: 2px solid #10b981; padding: 20px; border-radius: 8px; margin: 20px 0;">
1651
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;">
1652
+ <div><strong>Strategy:</strong> ${scanResult.chunking.chunkingStrategy}</div>
1653
+ <div><strong>Total Chunks:</strong> ${scanResult.chunking.totalChunks}</div>
1654
+ <div><strong>Avg Tokens/Chunk:</strong> ${scanResult.chunking.averageTokensPerChunk}</div>
1655
+ <div><strong>Avg Noise:</strong> ${(scanResult.chunking.averageNoiseRatio * 100).toFixed(1)}%</div>
1656
+ </div>
1657
+ </div>
1658
+ ` : ""}
1659
+
1660
+ ${scanResult?.extractability ? `
1661
+ <h2>\u{1F504} Extractability Analysis</h2>
1662
+ <div style="background: #fef3c7; border: 2px solid #f59e0b; padding: 20px; border-radius: 8px; margin: 20px 0;">
1663
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 15px;">
1664
+ <div><strong>Overall Score:</strong> ${scanResult.extractability.score.extractabilityScore}/100</div>
1665
+ <div><strong>Server-Rendered:</strong> ${scanResult.extractability.score.serverRenderedPercent}%</div>
1666
+ </div>
1667
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;">
1668
+ <div><strong>Text Extractable:</strong> ${scanResult.extractability.contentTypes.text.percentage}%</div>
1669
+ <div><strong>Images Extractable:</strong> ${scanResult.extractability.contentTypes.images.percentage}%</div>
1670
+ </div>
1671
+ </div>
1672
+ ` : ""}
1673
+
1674
+ ${scanResult?.hallucinationReport ? `
1675
+ <h2>\u26A0\uFE0F Hallucination Risk Assessment</h2>
1676
+ <div style="background: #fef2f2; border: 2px solid #ef4444; padding: 20px; border-radius: 8px; margin: 20px 0;">
1677
+ <div style="font-size: 1.5em; font-weight: bold; margin-bottom: 15px;">
1678
+ Risk Score: ${scanResult.hallucinationReport.hallucinationRiskScore}/100
1679
+ </div>
1680
+
1681
+ ${scanResult.hallucinationReport.factCheckSummary ? `
1682
+ <div style="margin-top: 20px; background: white; padding: 15px; border-radius: 4px;">
1683
+ <strong>\u{1F4CA} Fact Check Summary:</strong>
1684
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin-top: 10px;">
1685
+ <div>\u2705 Verified: ${scanResult.hallucinationReport.factCheckSummary.verifiedFacts}</div>
1686
+ <div>\u2753 Unverified: ${scanResult.hallucinationReport.factCheckSummary.unverifiedFacts}</div>
1687
+ <div>\u26A0\uFE0F Contradictions: ${scanResult.hallucinationReport.factCheckSummary.contradictions}</div>
1688
+ <div>\u{1F914} Ambiguities: ${scanResult.hallucinationReport.factCheckSummary.ambiguities}</div>
1689
+ </div>
1690
+ </div>
1691
+ ` : ""}
1692
+
1693
+ ${scanResult.hallucinationReport.factCheckSummary && scanResult.hallucinationReport.factCheckSummary.unverifiedFacts > 0 ? `
1694
+ <div style="margin-top: 15px; background: #fef9c3; border-left: 4px solid #eab308; padding: 15px; border-radius: 4px;">
1695
+ <div style="display: flex; align-items: start; gap: 10px;">
1696
+ <span style="font-size: 1.5em;">\u{1F4A1}</span>
1697
+ <div>
1698
+ <div style="font-weight: 600; margin-bottom: 8px;">Why Some Facts Can't Be Verified</div>
1699
+ <div style="color: #854d0e; margin-bottom: 8px;">
1700
+ AI systems need external sources to verify claims. When information lacks citations, links, or
1701
+ references, it becomes difficult to confirm accuracy, increasing the risk of AI hallucination or misinformation.
1702
+ </div>
1703
+ <div style="font-weight: 600; margin-top: 12px; margin-bottom: 6px;">Best Practices:</div>
1704
+ <ul style="margin: 0; padding-left: 20px; color: #854d0e;">
1705
+ <li>Add source links directly in or near claim text</li>
1706
+ <li>Include dates for time-sensitive information</li>
1707
+ <li>Link to authoritative sources (research, official docs)</li>
1708
+ <li>Provide context for statistics and data points</li>
1709
+ <li>Use schema.org markup to specify citations</li>
1710
+ </ul>
1711
+ </div>
1712
+ </div>
1713
+ </div>
1714
+ ` : ""}
1715
+
1716
+ ${scanResult.hallucinationReport.triggers && scanResult.hallucinationReport.triggers.length > 0 ? `
1717
+ <div style="margin-top: 20px;">
1718
+ <strong>\u{1F6A8} Identified Triggers:</strong>
1719
+ <div style="margin-top: 10px; display: grid; gap: 10px;">
1720
+ ${scanResult.hallucinationReport.triggers.filter((t) => t.severity === "high" || t.severity === "critical").slice(0, 5).map((trigger) => `
1721
+ <div style="background: white; padding: 15px; border-radius: 4px; border-left: 3px solid #dc2626;">
1722
+ <div style="font-weight: 600; text-transform: uppercase; font-size: 0.85em; color: #dc2626;">${trigger.type} - ${trigger.severity}</div>
1723
+ <div style="margin-top: 5px;">${trigger.description}</div>
1724
+ <div style="margin-top: 5px; font-size: 0.9em; color: #64748b;">Confidence: ${Math.round((trigger.confidence || 0) * 100)}%</div>
1725
+ </div>
1726
+ `).join("")}
1727
+ </div>
1728
+ </div>
1729
+ ` : ""}
1730
+
1731
+ ${scanResult.hallucinationReport.recommendations && scanResult.hallucinationReport.recommendations.length > 0 ? `
1732
+ <div style="margin-top: 20px;">
1733
+ <strong>\u{1F4A1} Recommendations:</strong>
1734
+ <ul style="margin-top: 10px; padding-left: 25px;">
1735
+ ${scanResult.hallucinationReport.recommendations.slice(0, 3).map((rec) => `<li style="margin: 5px 0;">${rec}</li>`).join("")}
1736
+ </ul>
1737
+ </div>
1738
+ ` : ""}
1739
+ </div>
1740
+ ` : ""}
1741
+
1742
+ ${scanResult?.mirrorReport ? `
1743
+ <h2>\u{1F50D} AI Misunderstanding Check</h2>
1744
+ <div style="background: #faf5ff; border: 2px solid #a855f7; padding: 20px; border-radius: 8px; margin: 20px 0;">
1745
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 15px;">
1746
+ <div><strong>\u{1F3AF} Alignment Score:</strong> ${scanResult.mirrorReport.summary.alignmentScore}/100</div>
1747
+ <div><strong>\u{1F4D6} Clarity Score:</strong> ${scanResult.mirrorReport.summary.clarityScore}/100</div>
1748
+ <div><strong>\u26A0\uFE0F Critical Issues:</strong> ${scanResult.mirrorReport.summary.critical}</div>
1749
+ <div><strong>\u{1F7E1} Major Issues:</strong> ${scanResult.mirrorReport.summary.major}</div>
1750
+ </div>
1751
+
1752
+ ${scanResult.mirrorReport.llmInterpretation ? `
1753
+ <div style="background: #dbeafe; padding: 16px; border-radius: 8px; margin-bottom: 16px; border: 1px solid #3b82f6;">
1754
+ <div style="font-weight: bold; margin-bottom: 12px; color: #1e40af;">
1755
+ \u{1F916} What AI Actually Understood (${Math.round(scanResult.mirrorReport.llmInterpretation.confidence * 100)}% confident)
1756
+ </div>
1757
+
1758
+ <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;">
1759
+ ${scanResult.mirrorReport.llmInterpretation.productName ? `
1760
+ <div>
1761
+ <div style="font-size: 11px; font-weight: 600; color: #64748b; margin-bottom: 4px;">PRODUCT NAME</div>
1762
+ <div style="font-size: 14px;">${scanResult.mirrorReport.llmInterpretation.productName}</div>
1763
+ </div>
1764
+ ` : ""}
1765
+
1766
+ ${scanResult.mirrorReport.llmInterpretation.purpose ? `
1767
+ <div style="grid-column: 1 / -1;">
1768
+ <div style="font-size: 11px; font-weight: 600; color: #64748b; margin-bottom: 4px;">MAIN PURPOSE</div>
1769
+ <div style="font-size: 14px;">${scanResult.mirrorReport.llmInterpretation.purpose}</div>
1770
+ </div>
1771
+ ` : ""}
1772
+
1773
+ ${scanResult.mirrorReport.llmInterpretation.valueProposition ? `
1774
+ <div style="grid-column: 1 / -1;">
1775
+ <div style="font-size: 11px; font-weight: 600; color: #64748b; margin-bottom: 4px;">\u{1F48E} UNIQUE VALUE</div>
1776
+ <div style="font-size: 14px; font-weight: 600; color: #7c3aed;">${scanResult.mirrorReport.llmInterpretation.valueProposition}</div>
1777
+ </div>
1778
+ ` : ""}
1779
+
1780
+ ${scanResult.mirrorReport.llmInterpretation.keyBenefits && scanResult.mirrorReport.llmInterpretation.keyBenefits.length > 0 ? `
1781
+ <div>
1782
+ <div style="font-size: 11px; font-weight: 600; color: #64748b; margin-bottom: 4px;">KEY BENEFITS</div>
1783
+ <ul style="font-size: 14px; margin: 0; padding-left: 20px;">
1784
+ ${scanResult.mirrorReport.llmInterpretation.keyBenefits.map((b) => `<li style="margin: 4px 0;">${b}</li>`).join("")}
1785
+ </ul>
1786
+ </div>
1787
+ ` : ""}
1788
+
1789
+ ${scanResult.mirrorReport.llmInterpretation.keyFeatures && scanResult.mirrorReport.llmInterpretation.keyFeatures.length > 0 ? `
1790
+ <div>
1791
+ <div style="font-size: 11px; font-weight: 600; color: #64748b; margin-bottom: 4px;">KEY FEATURES</div>
1792
+ <ul style="font-size: 14px; margin: 0; padding-left: 20px;">
1793
+ ${scanResult.mirrorReport.llmInterpretation.keyFeatures.slice(0, 3).map((f) => `<li style="margin: 4px 0;">${f}</li>`).join("")}
1794
+ </ul>
1795
+ </div>
1796
+ ` : ""}
1797
+ </div>
1798
+ </div>
1799
+ ` : ""}
1800
+
1801
+ ${scanResult.mirrorReport.mismatches && scanResult.mirrorReport.mismatches.length > 0 ? `
1802
+ <div style="margin-top: 20px;">
1803
+ <strong>Priority Mismatches:</strong>
1804
+ <div style="margin-top: 10px; display: grid; gap: 10px;">
1805
+ ${scanResult.mirrorReport.mismatches.filter((m) => m.severity === "critical" || m.severity === "major").slice(0, 5).map((mismatch) => `
1806
+ <div style="background: white; padding: 15px; border-radius: 4px; border-left: 3px solid ${mismatch.severity === "critical" ? "#dc2626" : "#f59e0b"};">
1807
+ <div style="font-weight: 600;">${mismatch.severity === "critical" ? "\u{1F534}" : "\u{1F7E1}"} ${mismatch.field}</div>
1808
+ <div style="margin-top: 5px; color: #64748b;">${mismatch.description}</div>
1809
+ <div style="margin-top: 10px; padding: 10px; background: #f0fdfa; border-radius: 4px;">
1810
+ <strong>Fix:</strong> ${mismatch.recommendation}
1811
+ </div>
1812
+ </div>
1813
+ `).join("")}
1814
+ </div>
1815
+ </div>
1816
+ ` : ""}
1817
+
1818
+ ${scanResult.mirrorReport.recommendations && scanResult.mirrorReport.recommendations.length > 0 ? `
1819
+ <div style="margin-top: 20px;">
1820
+ <strong>\u{1F4A1} Top Recommendations:</strong>
1821
+ <ul style="margin-top: 10px; padding-left: 25px;">
1822
+ ${scanResult.mirrorReport.recommendations.slice(0, 3).map((rec) => `<li style="margin: 5px 0;">${rec}</li>`).join("")}
1823
+ </ul>
1824
+ </div>
1825
+ ` : ""}
1826
+ </div>
1827
+ ` : ""}
1828
+
1829
+ <h2>\u26A0\uFE0F All Issues (${report.issues?.length || 0})</h2>
1830
+ <div class="issues">
1831
+ ${(report.issues || []).map((issue) => `
1832
+ <div class="issue ${issue.severity}">
1833
+ <div class="issue-title">${issue.message}</div>
1834
+ <div class="issue-meta">
1835
+ <span style="text-transform: uppercase; font-weight: 600;">${issue.severity}</span>
1836
+ \xB7 Impact: ${issue.impact}
1837
+ \xB7 Category: ${issue.category}
1838
+ </div>
1839
+ <div class="issue-desc">${issue.evidence || "No additional evidence"}</div>
1840
+ <div class="issue-fix"><strong>Fix:</strong> ${issue.suggested_fix}</div>
1841
+ </div>
1842
+ `).join("")}
1843
+ </div>
1844
+
1845
+ ${(report.entities || []).length > 0 ? `
1846
+ <h2>\u{1F3F7}\uFE0F Detected Entities (${(report.entities || []).length})</h2>
1847
+ <div class="entity-list">
1848
+ ${(report.entities || []).map((entity) => `
1849
+ <div class="entity">
1850
+ <div class="entity-name">${entity.name}</div>
1851
+ <div class="entity-type">${entity.type} \xB7 Source: ${entity.source}</div>
1852
+ ${entity.description ? `<div style="margin-top: 8px; color: #334155;">${entity.description}</div>` : ""}
1853
+ </div>
1854
+ `).join("")}
1855
+ </div>
1856
+ ` : ""}
1857
+ </div>
1858
+ </body>
1859
+ </html>`;
1860
+ }
1861
+ function getLetterGrade(score) {
1862
+ if (score >= 90) return "A";
1863
+ if (score >= 80) return "B";
1864
+ if (score >= 70) return "C";
1865
+ if (score >= 60) return "D";
1866
+ return "F";
1867
+ }
1868
+ function getEmojiForDimension(name) {
1869
+ const emojiMap = {
1870
+ "contentQuality": "\u{1F4DD}",
1871
+ "discoverability": "\u{1F50D}",
1872
+ "extractability": "\u{1F504}",
1873
+ "comprehensibility": "\u{1F9E0}",
1874
+ "trustworthiness": "\u2705",
1875
+ "structured": "\u{1F4CA}",
1876
+ "semantic": "\u{1F3F7}\uFE0F"
1877
+ };
1878
+ return emojiMap[name] || "\u{1F4CC}";
1879
+ }
1880
+ function formatDimensionName2(key) {
1881
+ const nameMap = {
1882
+ "contentQuality": "Content Quality",
1883
+ "discoverability": "Discoverability",
1884
+ "extractability": "Extractability",
1885
+ "comprehensibility": "Comprehensibility",
1886
+ "trustworthiness": "Trustworthiness"
1887
+ };
1888
+ return nameMap[key] || key;
1889
+ }
1890
+ function convertToLighthouseFormat(report) {
1891
+ return {
1892
+ lighthouseVersion: "1.0.0",
1893
+ userAgent: "AI-Lighthouse/1.0.0",
1894
+ fetchTime: report.scanned_at,
1895
+ requestedUrl: report.input?.requested_url,
1896
+ finalUrl: report.input?.final_url,
1897
+ categories: {
1898
+ "ai-readiness": {
1899
+ id: "ai-readiness",
1900
+ title: "AI Readiness",
1901
+ score: (report.scores?.ai_readiness || 0) / 100
1902
+ },
1903
+ "crawlability": {
1904
+ id: "crawlability",
1905
+ title: "Crawlability",
1906
+ score: (report.scores?.crawlability || 0) / 100
1907
+ },
1908
+ "content-clarity": {
1909
+ id: "content-clarity",
1910
+ title: "Content Clarity",
1911
+ score: (report.scores?.content_clarity || 0) / 100
1912
+ }
1913
+ },
1914
+ audits: (report.issues || []).reduce((acc, issue, idx) => {
1915
+ acc[`issue-${idx}`] = {
1916
+ id: issue.id,
1917
+ title: issue.message,
1918
+ description: issue.evidence || "",
1919
+ score: issue.severity === "critical" ? 0 : issue.severity === "high" ? 0.25 : 0.5,
1920
+ displayValue: issue.suggested_fix
1921
+ };
1922
+ return acc;
1923
+ }, {})
1924
+ };
1925
+ }
1926
+ function generateCSVReport(report) {
1927
+ const headers = ["ID", "Severity", "Category", "Message", "Impact", "Suggested Fix"];
1928
+ const rows = (report.issues || []).map((issue) => [
1929
+ issue.id,
1930
+ issue.severity,
1931
+ issue.category,
1932
+ `"${issue.message.replace(/"/g, '""')}"`,
1933
+ issue.impact,
1934
+ `"${issue.suggested_fix.replace(/"/g, '""')}"`
1935
+ ]);
1936
+ return [headers.join(","), ...rows.map((r) => r.join(","))].join("\n");
1937
+ }
1938
+
1939
+ // src/commands/crawl.ts
1940
+ import chalk3 from "chalk";
1941
+ import ora2 from "ora";
1942
+ import { analyzeUrlWithRules as analyzeUrlWithRules2 } from "@ai-lighthouse/scanner";
1943
+ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1944
+ import { join as join2, resolve as resolve2 } from "path";
1945
+ import { existsSync as existsSync2 } from "fs";
1946
+ function crawlCommand(program2) {
1947
+ program2.command("crawl").description("Crawl and audit multiple pages from a website").argument("<url>", "Starting URL to crawl from").option("-d, --depth <number>", "Maximum crawl depth", (val) => parseInt(val, 10), 2).option("--sitemap", "Parse sitemap.xml for URLs", false).option("--max-pages <number>", "Maximum number of pages to crawl", (val) => parseInt(val, 10), 50).option("-o, --output <format>", "Output format: json, html", "json").option("--follow-external", "Follow external links", false).option("--respect-robots", "Respect robots.txt rules", true).action(async (url, options) => {
1948
+ const spinner = ora2("Starting crawl...").start();
1949
+ try {
1950
+ const urlObj = new URL(url);
1951
+ const baseUrl = `${urlObj.protocol}//${urlObj.host}`;
1952
+ spinner.text = "Discovering URLs...";
1953
+ let urlsToCrawl = [];
1954
+ if (options.sitemap) {
1955
+ const sitemapUrl = `${baseUrl}/sitemap.xml`;
1956
+ spinner.text = `Fetching sitemap from ${sitemapUrl}...`;
1957
+ urlsToCrawl = await parseSitemap(sitemapUrl);
1958
+ spinner.succeed(`Found ${urlsToCrawl.length} URLs in sitemap`);
1959
+ } else {
1960
+ urlsToCrawl = await crawlByDepth(url, options.depth, options.maxPages, options.followExternal);
1961
+ spinner.succeed(`Discovered ${urlsToCrawl.length} URLs`);
1962
+ }
1963
+ if (urlsToCrawl.length > options.maxPages) {
1964
+ urlsToCrawl = urlsToCrawl.slice(0, options.maxPages);
1965
+ }
1966
+ const results = [];
1967
+ for (let i = 0; i < urlsToCrawl.length; i++) {
1968
+ const pageUrl = urlsToCrawl[i];
1969
+ spinner.text = `Auditing ${i + 1}/${urlsToCrawl.length}: ${pageUrl}`;
1970
+ try {
1971
+ const result = await analyzeUrlWithRules2(pageUrl, {
1972
+ maxChunkTokens: 1200,
1973
+ enableChunking: false,
1974
+ enableExtractability: false,
1975
+ enableLLM: false,
1976
+ minImpactScore: 8
1977
+ });
1978
+ results.push(result);
1979
+ } catch (error) {
1980
+ console.error(chalk3.yellow(`
1981
+ \u26A0\uFE0F Failed to audit ${pageUrl}: ${error}`));
1982
+ }
1983
+ }
1984
+ spinner.text = "Generating crawl report...";
1985
+ const crawlReport = {
1986
+ crawl_id: generateId(),
1987
+ crawled_at: (/* @__PURE__ */ new Date()).toISOString(),
1988
+ base_url: baseUrl,
1989
+ start_url: url,
1990
+ total_pages: results.length,
1991
+ crawl_depth: options.depth,
1992
+ pages: results,
1993
+ summary: {
1994
+ avgOverallScore: average(results.map((r) => r.scoring.overallScore)),
1995
+ avgAIReadinessScore: average(results.map((r) => r.scoring.categoryScores.find((c) => c.category === "AI Readiness")?.score || 0)),
1996
+ total_issues: results.reduce((sum, r) => sum + r.issues.length, 0),
1997
+ issues_by_severity: aggregateIssuesBySeverity(results)
1998
+ }
1999
+ };
2000
+ const outputDir = resolve2(process.cwd(), ".ai-lighthouse");
2001
+ if (!existsSync2(outputDir)) {
2002
+ await mkdir2(outputDir, { recursive: true });
2003
+ }
2004
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-").split(".")[0];
2005
+ const baseFilename = `crawl_${new URL(url).hostname}_${timestamp}`;
2006
+ if (options.output === "json") {
2007
+ const jsonPath = join2(outputDir, `${baseFilename}.json`);
2008
+ await writeFile2(jsonPath, JSON.stringify(crawlReport, null, 2));
2009
+ spinner.succeed(chalk3.green("Crawl complete!"));
2010
+ console.log(chalk3.dim(`Report saved to: ${jsonPath}`));
2011
+ } else if (options.output === "html") {
2012
+ const htmlPath = join2(outputDir, `${baseFilename}.html`);
2013
+ const html = generateCrawlHTML(crawlReport);
2014
+ await writeFile2(htmlPath, html);
2015
+ spinner.succeed(chalk3.green("Crawl complete!"));
2016
+ console.log(chalk3.dim(`HTML report saved to: ${htmlPath}`));
2017
+ }
2018
+ console.log("\n" + chalk3.bold("\u{1F310} Crawl Summary"));
2019
+ console.log(`Pages audited: ${chalk3.cyan(crawlReport.total_pages)}`);
2020
+ console.log(`Average overall score: ${chalk3.cyan(crawlReport.summary.avgOverallScore.toFixed(1))}`);
2021
+ console.log(`Average AI readiness: ${chalk3.cyan(crawlReport.summary.avgAIReadinessScore.toFixed(1))}`);
2022
+ console.log(`Total issues found: ${chalk3.yellow(crawlReport.summary.total_issues)}`);
2023
+ } catch (error) {
2024
+ spinner.fail(chalk3.red("Crawl failed"));
2025
+ if (error instanceof Error) {
2026
+ console.error(chalk3.red(error.message));
2027
+ }
2028
+ process.exit(1);
2029
+ }
2030
+ });
2031
+ }
2032
+ async function parseSitemap(sitemapUrl) {
2033
+ try {
2034
+ const response = await fetch(sitemapUrl);
2035
+ if (!response.ok) {
2036
+ throw new Error(`Failed to fetch sitemap: ${response.statusText}`);
2037
+ }
2038
+ const text = await response.text();
2039
+ const urlMatches = text.match(/<loc>(.*?)<\/loc>/g) || [];
2040
+ return urlMatches.map((match) => match.replace(/<\/?loc>/g, ""));
2041
+ } catch (error) {
2042
+ console.warn(chalk3.yellow(`Could not parse sitemap: ${error}`));
2043
+ return [];
2044
+ }
2045
+ }
2046
+ async function crawlByDepth(startUrl, maxDepth, maxPages, followExternal) {
2047
+ const visited = /* @__PURE__ */ new Set();
2048
+ const toVisit = [{ url: startUrl, depth: 0 }];
2049
+ const baseHost = new URL(startUrl).host;
2050
+ while (toVisit.length > 0 && visited.size < maxPages) {
2051
+ const { url, depth } = toVisit.shift();
2052
+ if (visited.has(url) || depth > maxDepth) {
2053
+ continue;
2054
+ }
2055
+ visited.add(url);
2056
+ if (depth < maxDepth) {
2057
+ try {
2058
+ const links = await extractLinks(url);
2059
+ for (const link of links) {
2060
+ const linkHost = new URL(link).host;
2061
+ if (followExternal || linkHost === baseHost) {
2062
+ if (!visited.has(link)) {
2063
+ toVisit.push({ url: link, depth: depth + 1 });
2064
+ }
2065
+ }
2066
+ }
2067
+ } catch (error) {
2068
+ }
2069
+ }
2070
+ }
2071
+ return Array.from(visited);
2072
+ }
2073
+ async function extractLinks(url) {
2074
+ try {
2075
+ const response = await fetch(url);
2076
+ const html = await response.text();
2077
+ const linkMatches = html.match(/href=["'](https?:\/\/[^"']+)["']/g) || [];
2078
+ return linkMatches.map((match) => match.match(/href=["'](https?:\/\/[^"']+)["']/)[1]).filter((link, idx, arr) => arr.indexOf(link) === idx);
2079
+ } catch {
2080
+ return [];
2081
+ }
2082
+ }
2083
+ function average(numbers) {
2084
+ if (numbers.length === 0) return 0;
2085
+ return numbers.reduce((sum, n) => sum + n, 0) / numbers.length;
2086
+ }
2087
+ function aggregateIssuesBySeverity(results) {
2088
+ const counts = {
2089
+ critical: 0,
2090
+ high: 0,
2091
+ medium: 0,
2092
+ low: 0,
2093
+ info: 0
2094
+ };
2095
+ for (const result of results) {
2096
+ for (const issue of result.issues) {
2097
+ counts[issue.severity] = (counts[issue.severity] || 0) + 1;
2098
+ }
2099
+ }
2100
+ return counts;
2101
+ }
2102
+ function generateId() {
2103
+ return `crawl_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2104
+ }
2105
+ function generateCrawlHTML(report) {
2106
+ return `<!DOCTYPE html>
2107
+ <html lang="en">
2108
+ <head>
2109
+ <meta charset="UTF-8">
2110
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2111
+ <title>Crawl Report - ${report.base_url}</title>
2112
+ <style>
2113
+ * { margin: 0; padding: 0; box-sizing: border-box; }
2114
+ body {
2115
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
2116
+ background: #f5f5f5;
2117
+ padding: 20px;
2118
+ }
2119
+ .container { max-width: 1400px; margin: 0 auto; }
2120
+ .header { background: white; padding: 30px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
2121
+ h1 { color: #2563eb; margin-bottom: 10px; }
2122
+ .summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-top: 20px; }
2123
+ .stat { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 8px; }
2124
+ .stat-value { font-size: 2em; font-weight: bold; }
2125
+ .stat-label { opacity: 0.9; font-size: 0.9em; }
2126
+ .pages { display: grid; gap: 15px; }
2127
+ .page-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
2128
+ .page-url { color: #1e40af; font-weight: 600; margin-bottom: 10px; word-break: break-all; }
2129
+ .page-scores { display: flex; gap: 15px; margin-top: 10px; }
2130
+ .score-badge { background: #dbeafe; color: #1e40af; padding: 5px 12px; border-radius: 4px; font-size: 0.9em; }
2131
+ </style>
2132
+ </head>
2133
+ <body>
2134
+ <div class="container">
2135
+ <div class="header">
2136
+ <h1>\u{1F310} Crawl Report</h1>
2137
+ <div style="color: #64748b; margin-top: 5px;">${report.base_url}</div>
2138
+ <div style="color: #94a3b8; font-size: 0.85em; margin-top: 5px;">
2139
+ Generated: ${new Date(report.crawled_at).toLocaleString()}
2140
+ </div>
2141
+
2142
+ <div class="summary">
2143
+ <div class="stat">
2144
+ <div class="stat-label">Pages Audited</div>
2145
+ <div class="stat-value">${report.total_pages}</div>
2146
+ </div>
2147
+ <div class="stat">
2148
+ <div class="stat-label">Avg Overall Score</div>
2149
+ <div class="stat-value">${report.summary.avgOverallScore.toFixed(1)}</div>
2150
+ </div>
2151
+ <div class="stat">
2152
+ <div class="stat-label">Avg AI Readiness</div>
2153
+ <div class="stat-value">${report.summary.avgAIReadinessScore.toFixed(1)}</div>
2154
+ </div> avg
2155
+ <div class="stat">
2156
+ <div class="stat-label">Total Issues</div>
2157
+ <div class="stat-value">${report.summary.total_issues}</div>
2158
+ </div>
2159
+ </div>
2160
+ </div>
2161
+
2162
+ <div class="pages">
2163
+ ${report.pages.map((page) => `
2164
+ <div class="page-card">
2165
+ <div class="page-url">${page.input.requested_url}</div>
2166
+ <div class="page-scores">
2167
+ <div class="score-badge">Overall: ${page.scores.overall}</div>
2168
+ <div class="score-badge">AI Readiness: ${page.scores.ai_readiness}</div>
2169
+ <div class="score-badge">Issues: ${page.issues.length}</div>
2170
+ </div>
2171
+ </div>
2172
+ `).join("")}
2173
+ </div>
2174
+ </div>
2175
+ </body>
2176
+ </html>`;
2177
+ }
2178
+
2179
+ // src/commands/report.ts
2180
+ import chalk4 from "chalk";
2181
+ import ora3 from "ora";
2182
+ import open from "open";
2183
+ import { readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
2184
+ import { resolve as resolve3 } from "path";
2185
+ import { existsSync as existsSync3 } from "fs";
2186
+ function reportCommand(program2) {
2187
+ program2.command("report").description("Generate and view reports from saved audit results").argument("<file>", "Path to audit JSON file (e.g., ./last_run.json)").option("--open", "Open the report in browser", false).option("-f, --format <format>", "Output format: html, json, csv", "html").action(async (file, options) => {
2188
+ const spinner = ora3("Loading audit results...").start();
2189
+ try {
2190
+ const filePath = resolve3(process.cwd(), file);
2191
+ if (!existsSync3(filePath)) {
2192
+ throw new Error(`File not found: ${filePath}`);
2193
+ }
2194
+ const jsonContent = await readFile2(filePath, "utf-8");
2195
+ const report = JSON.parse(jsonContent);
2196
+ spinner.text = "Generating report...";
2197
+ const isCrawlReport = "pages" in report && Array.isArray(report.pages);
2198
+ if (options.format === "html") {
2199
+ const htmlPath = filePath.replace(/\.json$/, ".html");
2200
+ const html = isCrawlReport ? generateCrawlHTML2(report) : generateSingleHTML(report);
2201
+ await writeFile3(htmlPath, html);
2202
+ spinner.succeed(chalk4.green("Report generated!"));
2203
+ console.log(chalk4.dim(`HTML saved to: ${htmlPath}`));
2204
+ if (options.open) {
2205
+ spinner.text = "Opening report in browser...";
2206
+ await open(htmlPath);
2207
+ spinner.succeed("Report opened in browser");
2208
+ }
2209
+ } else if (options.format === "json") {
2210
+ spinner.succeed("Report loaded");
2211
+ console.log(JSON.stringify(report, null, 2));
2212
+ } else if (options.format === "csv") {
2213
+ const csvPath = filePath.replace(/\.json$/, ".csv");
2214
+ const csv = generateCSV(report);
2215
+ await writeFile3(csvPath, csv);
2216
+ spinner.succeed(chalk4.green("CSV report generated!"));
2217
+ console.log(chalk4.dim(`CSV saved to: ${csvPath}`));
2218
+ }
2219
+ if (isCrawlReport) {
2220
+ console.log("\n" + chalk4.bold("\u{1F310} Crawl Summary"));
2221
+ console.log(`Pages: ${chalk4.cyan(report.total_pages)}`);
2222
+ console.log(`Avg Score: ${chalk4.cyan(report.summary.avg_overall_score.toFixed(1))}`);
2223
+ console.log(`Total Issues: ${chalk4.yellow(report.summary.total_issues)}`);
2224
+ } else {
2225
+ console.log("\n" + chalk4.bold("\u{1F4CA} Audit Summary"));
2226
+ console.log(`URL: ${chalk4.cyan(report.input.requested_url)}`);
2227
+ console.log(`Overall Score: ${chalk4.cyan(report.scores.overall)}`);
2228
+ console.log(`Issues: ${chalk4.yellow(report.issues.length)}`);
2229
+ }
2230
+ } catch (error) {
2231
+ spinner.fail(chalk4.red("Failed to generate report"));
2232
+ if (error instanceof Error) {
2233
+ console.error(chalk4.red(error.message));
2234
+ }
2235
+ process.exit(1);
2236
+ }
2237
+ });
2238
+ }
2239
+ function generateSingleHTML(report) {
2240
+ return `<!DOCTYPE html>
2241
+ <html lang="en">
2242
+ <head>
2243
+ <meta charset="UTF-8">
2244
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2245
+ <title>AI Lighthouse Report - ${report.input.requested_url}</title>
2246
+ <style>
2247
+ * { margin: 0; padding: 0; box-sizing: border-box; }
2248
+ body {
2249
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
2250
+ background: #f5f5f5;
2251
+ padding: 20px;
2252
+ line-height: 1.6;
2253
+ }
2254
+ .container { max-width: 1200px; margin: 0 auto; }
2255
+ .header { background: white; padding: 40px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
2256
+ h1 { color: #2563eb; margin-bottom: 10px; }
2257
+ .scores { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 15px; margin: 25px 0; }
2258
+ .score-card {
2259
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
2260
+ color: white;
2261
+ padding: 20px;
2262
+ border-radius: 8px;
2263
+ text-align: center;
2264
+ }
2265
+ .score-value { font-size: 2.5em; font-weight: bold; }
2266
+ .score-label { font-size: 0.9em; opacity: 0.9; margin-top: 5px; }
2267
+ .section { background: white; padding: 30px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
2268
+ .section-title { color: #1e40af; font-size: 1.5em; margin-bottom: 20px; border-bottom: 2px solid #dbeafe; padding-bottom: 10px; }
2269
+ .issues { display: grid; gap: 15px; }
2270
+ .issue {
2271
+ background: #f8fafc;
2272
+ border-left: 4px solid #cbd5e1;
2273
+ padding: 15px;
2274
+ border-radius: 4px;
2275
+ }
2276
+ .issue.critical { border-left-color: #dc2626; background: #fef2f2; }
2277
+ .issue.high { border-left-color: #ea580c; background: #fff7ed; }
2278
+ .issue.medium { border-left-color: #f59e0b; background: #fffbeb; }
2279
+ .issue-title { font-weight: 600; color: #1e293b; margin-bottom: 8px; font-size: 1.05em; }
2280
+ .issue-meta { font-size: 0.85em; color: #64748b; margin-bottom: 10px; }
2281
+ .issue-fix { color: #0f766e; background: #f0fdfa; padding: 12px; border-radius: 4px; margin-top: 10px; }
2282
+ .entity { background: #f0f9ff; border: 1px solid #bae6fd; padding: 15px; border-radius: 6px; margin-bottom: 15px; }
2283
+ .entity-name { font-weight: 600; color: #0c4a6e; font-size: 1.1em; }
2284
+ </style>
2285
+ </head>
2286
+ <body>
2287
+ <div class="container">
2288
+ <div class="header">
2289
+ <h1>\u{1F6A8} AI Lighthouse Report</h1>
2290
+ <div style="color: #64748b; margin-top: 5px;">${report.input.requested_url}</div>
2291
+ <div style="color: #94a3b8; font-size: 0.85em; margin-top: 5px;">
2292
+ ${new Date(report.scanned_at).toLocaleString()}
2293
+ </div>
2294
+
2295
+ <div class="scores">
2296
+ <div class="score-card">
2297
+ <div class="score-value">${report.scores.overall}</div>
2298
+ <div class="score-label">Overall</div>
2299
+ </div>
2300
+ <div class="score-card">
2301
+ <div class="score-value">${report.scores.ai_readiness}</div>
2302
+ <div class="score-label">AI Readiness</div>
2303
+ </div>
2304
+ <div class="score-card">
2305
+ <div class="score-value">${report.scores.crawlability}</div>
2306
+ <div class="score-label">Crawlability</div>
2307
+ </div>
2308
+ <div class="score-card">
2309
+ <div class="score-value">${report.scores.content_clarity}</div>
2310
+ <div class="score-label">Content Clarity</div>
2311
+ </div>
2312
+ <div class="score-card">
2313
+ <div class="score-value">${report.scores.schema_coverage}</div>
2314
+ <div class="score-label">Schema</div>
2315
+ </div>
2316
+ </div>
2317
+ </div>
2318
+
2319
+ ${report.issues.length > 0 ? `
2320
+ <div class="section">
2321
+ <div class="section-title">\u26A0\uFE0F Issues (${report.issues.length})</div>
2322
+ <div class="issues">
2323
+ ${report.issues.map((issue) => `
2324
+ <div class="issue ${issue.severity}">
2325
+ <div class="issue-title">${issue.message}</div>
2326
+ <div class="issue-meta">
2327
+ <span style="text-transform: uppercase; font-weight: 600; color: ${getSeverityColor(issue.severity)}">
2328
+ ${issue.severity}
2329
+ </span>
2330
+ \xB7 ${issue.category}
2331
+ </div>
2332
+ ${issue.evidence ? `<div style="color: #475569; margin: 10px 0;">${issue.evidence}</div>` : ""}
2333
+ <div class="issue-fix"><strong>\u{1F4A1} Fix:</strong> ${issue.suggested_fix}</div>
2334
+ </div>
2335
+ `).join("")}
2336
+ </div>
2337
+ </div>
2338
+ ` : ""}
2339
+
2340
+ ${report.entities && report.entities.length > 0 ? `
2341
+ <div class="section">
2342
+ <div class="section-title">\u{1F3F7}\uFE0F Detected Entities (${report.entities.length})</div>
2343
+ ${report.entities.map((entity) => `
2344
+ <div class="entity">
2345
+ <div class="entity-name">${entity.name}</div>
2346
+ <div style="color: #0369a1; font-size: 0.9em; margin-top: 3px;">
2347
+ ${entity.type} \xB7 ${entity.source}
2348
+ </div>
2349
+ ${entity.description ? `<div style="margin-top: 10px; color: #334155;">${entity.description}</div>` : ""}
2350
+ </div>
2351
+ `).join("")}
2352
+ </div>
2353
+ ` : ""}
2354
+ </div>
2355
+ </body>
2356
+ </html>`;
2357
+ }
2358
+ function generateCrawlHTML2(report) {
2359
+ return `<!DOCTYPE html>
2360
+ <html lang="en">
2361
+ <head>
2362
+ <meta charset="UTF-8">
2363
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2364
+ <title>Crawl Report - ${report.base_url}</title>
2365
+ <style>
2366
+ * { margin: 0; padding: 0; box-sizing: border-box; }
2367
+ body {
2368
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
2369
+ background: #f5f5f5;
2370
+ padding: 20px;
2371
+ }
2372
+ .container { max-width: 1400px; margin: 0 auto; }
2373
+ .header { background: white; padding: 30px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
2374
+ h1 { color: #2563eb; }
2375
+ .summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-top: 20px; }
2376
+ .stat { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 8px; text-align: center; }
2377
+ .stat-value { font-size: 2em; font-weight: bold; }
2378
+ .pages { display: grid; gap: 15px; }
2379
+ .page-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
2380
+ .page-url { color: #1e40af; font-weight: 600; word-break: break-all; margin-bottom: 12px; }
2381
+ .page-scores { display: flex; flex-wrap: wrap; gap: 10px; }
2382
+ .score-badge { background: #dbeafe; color: #1e40af; padding: 6px 12px; border-radius: 4px; font-size: 0.9em; }
2383
+ </style>
2384
+ </head>
2385
+ <body>
2386
+ <div class="container">
2387
+ <div class="header">
2388
+ <h1>\u{1F310} Multi-Page Crawl Report</h1>
2389
+ <div style="color: #64748b; margin-top: 8px;">${report.base_url}</div>
2390
+ <div style="color: #94a3b8; font-size: 0.85em; margin-top: 5px;">
2391
+ ${new Date(report.crawled_at).toLocaleString()}
2392
+ </div>
2393
+
2394
+ <div class="summary">
2395
+ <div class="stat">
2396
+ <div class="stat-value">${report.total_pages}</div>
2397
+ <div style="opacity: 0.9; margin-top: 5px;">Pages</div>
2398
+ </div>
2399
+ <div class="stat">
2400
+ <div class="stat-value">${report.summary.avg_overall_score.toFixed(1)}</div>
2401
+ <div style="opacity: 0.9; margin-top: 5px;">Avg Score</div>
2402
+ </div>
2403
+ <div class="stat">
2404
+ <div class="stat-value">${report.summary.avg_ai_readiness.toFixed(1)}</div>
2405
+ <div style="opacity: 0.9; margin-top: 5px;">Avg AI Readiness</div>
2406
+ </div>
2407
+ <div class="stat">
2408
+ <div class="stat-value">${report.summary.total_issues}</div>
2409
+ <div style="opacity: 0.9; margin-top: 5px;">Total Issues</div>
2410
+ </div>
2411
+ </div>
2412
+ </div>
2413
+
2414
+ <div class="pages">
2415
+ ${report.pages.map((page, idx) => `
2416
+ <div class="page-card">
2417
+ <div style="color: #94a3b8; font-size: 0.85em; margin-bottom: 5px;">Page ${idx + 1}</div>
2418
+ <div class="page-url">${page.input.requested_url}</div>
2419
+ <div class="page-scores">
2420
+ <div class="score-badge">Overall: ${page.scores.overall}</div>
2421
+ <div class="score-badge">AI Readiness: ${page.scores.ai_readiness}</div>
2422
+ <div class="score-badge">Crawlability: ${page.scores.crawlability}</div>
2423
+ <div class="score-badge">Issues: ${page.issues.length}</div>
2424
+ </div>
2425
+ </div>
2426
+ `).join("")}
2427
+ </div>
2428
+ </div>
2429
+ </body>
2430
+ </html>`;
2431
+ }
2432
+ function generateCSV(report) {
2433
+ const isCrawlReport = "pages" in report && Array.isArray(report.pages);
2434
+ if (isCrawlReport) {
2435
+ const headers = ["Page", "URL", "Overall Score", "AI Readiness", "Crawlability", "Issues"];
2436
+ const rows = report.pages.map((page, idx) => [
2437
+ idx + 1,
2438
+ `"${page.input.requested_url}"`,
2439
+ page.scores.overall,
2440
+ page.scores.ai_readiness,
2441
+ page.scores.crawlability,
2442
+ page.issues.length
2443
+ ]);
2444
+ return [headers.join(","), ...rows.map((r) => r.join(","))].join("\n");
2445
+ } else {
2446
+ const headers = ["ID", "Severity", "Category", "Message", "Fix"];
2447
+ const rows = report.issues.map((issue) => [
2448
+ issue.id,
2449
+ issue.severity,
2450
+ issue.category,
2451
+ `"${issue.message.replace(/"/g, '""')}"`,
2452
+ `"${issue.suggested_fix.replace(/"/g, '""')}"`
2453
+ ]);
2454
+ return [headers.join(","), ...rows.map((r) => r.join(","))].join("\n");
2455
+ }
2456
+ }
2457
+ function getSeverityColor(severity) {
2458
+ const colors = {
2459
+ critical: "#dc2626",
2460
+ high: "#ea580c",
2461
+ medium: "#f59e0b",
2462
+ low: "#84cc16",
2463
+ info: "#3b82f6"
2464
+ };
2465
+ return colors[severity] || "#64748b";
2466
+ }
2467
+
2468
+ // src/commands/audit-wizard.ts
2469
+ import { render as render2 } from "ink";
2470
+ import React11 from "react";
2471
+ import { analyzeUrlWithRules as analyzeUrlWithRules3, calculateAIReadiness as calculateAIReadiness2 } from "@ai-lighthouse/scanner";
2472
+ function auditWizardCommand(program2) {
2473
+ program2.command("wizard").alias("w").description("Interactive wizard to configure and run an audit").argument("[url]", "Optional URL to audit (will prompt if not provided)").action(async (url) => {
2474
+ const originalConsoleError = console.error;
2475
+ const originalConsoleWarn = console.warn;
2476
+ console.error = () => {
2477
+ };
2478
+ console.warn = () => {
2479
+ };
2480
+ let auditConfig = null;
2481
+ const wizardRender = render2(
2482
+ React11.createElement(SetupWizard, {
2483
+ initialUrl: url,
2484
+ onComplete: (config) => {
2485
+ auditConfig = config;
2486
+ }
2487
+ })
2488
+ );
2489
+ await wizardRender.waitUntilExit();
2490
+ if (!auditConfig) {
2491
+ console.error = originalConsoleError;
2492
+ console.warn = originalConsoleWarn;
2493
+ console.log("\nAudit cancelled.");
2494
+ process.exit(0);
2495
+ }
2496
+ const { clear, rerender } = render2(
2497
+ React11.createElement(AuditReportUI, {
2498
+ url: auditConfig.url,
2499
+ result: {},
2500
+ aiReadiness: {},
2501
+ loading: true,
2502
+ currentStep: "Starting audit..."
2503
+ })
2504
+ );
2505
+ try {
2506
+ const scanOptions = {
2507
+ maxChunkTokens: 1200,
2508
+ chunkingStrategy: "auto",
2509
+ enableChunking: auditConfig.enableChunking,
2510
+ enableExtractability: auditConfig.enableExtractability,
2511
+ enableHallucinationDetection: auditConfig.enableHallucination,
2512
+ enableLLM: auditConfig.enableLlm,
2513
+ minImpactScore: 8,
2514
+ minConfidence: 0.7,
2515
+ maxIssues: 20
2516
+ };
2517
+ if (auditConfig.enableLlm && auditConfig.llmProvider) {
2518
+ scanOptions.llmConfig = {
2519
+ provider: auditConfig.llmProvider,
2520
+ model: auditConfig.llmModel,
2521
+ baseUrl: auditConfig.llmBaseUrl,
2522
+ apiKey: auditConfig.llmApiKey
2523
+ };
2524
+ }
2525
+ rerender(
2526
+ React11.createElement(AuditReportUI, {
2527
+ url: auditConfig.url,
2528
+ result: {},
2529
+ aiReadiness: {},
2530
+ loading: true,
2531
+ currentStep: "Scanning page..."
2532
+ })
2533
+ );
2534
+ const result = await analyzeUrlWithRules3(auditConfig.url, scanOptions);
2535
+ rerender(
2536
+ React11.createElement(AuditReportUI, {
2537
+ url: auditConfig.url,
2538
+ result,
2539
+ aiReadiness: {},
2540
+ loading: true,
2541
+ currentStep: "Calculating AI readiness scores..."
2542
+ })
2543
+ );
2544
+ const aiReadiness = calculateAIReadiness2(result);
2545
+ clear();
2546
+ const finalRender = render2(
2547
+ React11.createElement(AuditReportUI, {
2548
+ url: auditConfig.url,
2549
+ result,
2550
+ aiReadiness,
2551
+ loading: false
2552
+ })
2553
+ );
2554
+ await finalRender.waitUntilExit();
2555
+ console.error = originalConsoleError;
2556
+ console.warn = originalConsoleWarn;
2557
+ } catch (error) {
2558
+ console.error = originalConsoleError;
2559
+ console.warn = originalConsoleWarn;
2560
+ clear();
2561
+ console.log("\n\x1B[1m\x1B[31m\u274C Audit Failed\x1B[0m");
2562
+ console.log("\x1B[31m" + "\u2500".repeat(70) + "\x1B[0m");
2563
+ console.log("\x1B[31m" + (error instanceof Error ? error.message : String(error)) + "\x1B[0m");
2564
+ console.log("\n\x1B[2mPlease check the URL and your configuration.\x1B[0m");
2565
+ process.exit(1);
2566
+ }
2567
+ });
2568
+ }
2569
+
2570
+ // src/index.ts
2571
+ var program = new Command();
2572
+ program.name("ai-lighthouse").description("AI Lighthouse - Audit websites for AI readiness and SEO optimization").version("1.0.0");
2573
+ auditWizardCommand(program);
13
2574
  auditCommand(program);
14
2575
  crawlCommand(program);
15
2576
  reportCommand(program);