aeo-ready 1.0.0 → 1.1.0

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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "WebSearch"
5
+ ]
6
+ }
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aeo-ready",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Is your site AEO ready? Two scorecards: Agent Readiness + AI Visibility. One scan, one score.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,7 +29,8 @@
29
29
  "node": ">=18.0.0"
30
30
  },
31
31
  "dependencies": {
32
- "commander": "^12.0.0",
33
- "chalk": "^5.3.0"
32
+ "afdocs": "^0.18.7",
33
+ "chalk": "^5.3.0",
34
+ "commander": "^12.0.0"
34
35
  }
35
36
  }
@@ -0,0 +1,75 @@
1
+ export async function runCloudflare(url) {
2
+ try {
3
+ const res = await fetch("https://isitagentready.com/mcp", {
4
+ method: "POST",
5
+ headers: {
6
+ "Content-Type": "application/json",
7
+ Accept: "application/json, text/event-stream",
8
+ },
9
+ body: JSON.stringify({
10
+ jsonrpc: "2.0",
11
+ id: 1,
12
+ method: "tools/call",
13
+ params: {
14
+ name: "scan_site",
15
+ arguments: { url },
16
+ },
17
+ }),
18
+ });
19
+
20
+ const text = await res.text();
21
+ const dataLine = text.split("\n").find((l) => l.startsWith("data:"));
22
+ if (!dataLine) return { available: false, reason: "no data in response" };
23
+
24
+ const json = JSON.parse(dataLine.slice(5));
25
+ const content = json.result?.content?.[0]?.text || "";
26
+
27
+ const levelMatch = content.match(/Level (\d)\/5/i);
28
+ const level = levelMatch ? parseInt(levelMatch[1]) : 0;
29
+
30
+ const categories = {};
31
+ const catRegex = /## (.+?) \((\d+)\/(\d+) passing\)/g;
32
+ let match;
33
+ while ((match = catRegex.exec(content)) !== null) {
34
+ const name = match[1];
35
+ categories[name.toLowerCase().replace(/[^a-z]+/g, "-")] = {
36
+ name,
37
+ score: parseInt(match[2]),
38
+ maxScore: parseInt(match[3]),
39
+ percentage: Math.round((parseInt(match[2]) / parseInt(match[3])) * 100),
40
+ };
41
+ }
42
+
43
+ const checks = [];
44
+ const checkRegex =
45
+ /- (PASS|FAIL|OK) (\w+)(?:: (.+?))?(?:\n\s+(.+?))?(?=\n- |\n##|\n$)/g;
46
+ let cm;
47
+ while ((cm = checkRegex.exec(content)) !== null) {
48
+ checks.push({
49
+ status: cm[1].toLowerCase(),
50
+ id: cm[2],
51
+ message: cm[3] || "",
52
+ });
53
+ }
54
+
55
+ return {
56
+ score: level,
57
+ maxScore: 5,
58
+ grade: levelToGrade(level),
59
+ level,
60
+ checks,
61
+ categories,
62
+ available: true,
63
+ };
64
+ } catch (err) {
65
+ return { available: false, reason: err.message?.slice(0, 100) };
66
+ }
67
+ }
68
+
69
+ function levelToGrade(level) {
70
+ if (level >= 5) return "A";
71
+ if (level >= 4) return "B";
72
+ if (level >= 3) return "C";
73
+ if (level >= 2) return "D";
74
+ return "F";
75
+ }
@@ -0,0 +1,51 @@
1
+ export async function runFern(url) {
2
+ try {
3
+ const { runChecks, computeScore, toGrade, CATEGORIES } =
4
+ await import("afdocs");
5
+
6
+ const report = await runChecks(url);
7
+ const checks = report.results || [];
8
+ const computed = computeScore(report);
9
+ const score = computed.overall ?? 0;
10
+ const maxScore = 100;
11
+ const grade = computed.grade || toGrade(score);
12
+
13
+ const catMap = {};
14
+ for (const cat of CATEGORIES) {
15
+ catMap[cat.id] = { name: cat.name, score: 0, maxScore: 0 };
16
+ }
17
+
18
+ for (const check of checks) {
19
+ const catId = check.category || "other";
20
+ if (!catMap[catId]) {
21
+ catMap[catId] = { name: catId, score: 0, maxScore: 0 };
22
+ }
23
+ catMap[catId].maxScore += 1;
24
+ if (check.status === "pass") catMap[catId].score += 1;
25
+ }
26
+
27
+ for (const cat of Object.values(catMap)) {
28
+ cat.percentage =
29
+ cat.maxScore > 0 ? Math.round((cat.score / cat.maxScore) * 100) : 0;
30
+ }
31
+
32
+ const categories = Object.fromEntries(
33
+ Object.entries(catMap).filter(([, v]) => v.maxScore > 0),
34
+ );
35
+
36
+ return {
37
+ score,
38
+ maxScore,
39
+ grade,
40
+ categories,
41
+ checks: checks.map((c) => ({
42
+ id: c.id,
43
+ status: c.status,
44
+ message: c.message || "",
45
+ })),
46
+ available: true,
47
+ };
48
+ } catch (err) {
49
+ return { available: false, reason: err.message?.slice(0, 100) };
50
+ }
51
+ }
@@ -0,0 +1,119 @@
1
+ import chalk from "chalk";
2
+ import { runBenchmark as runAgenticSeo } from "./agentic-seo.js";
3
+ import { runCloudflare } from "./cloudflare.js";
4
+ import { runFern } from "./fern.js";
5
+
6
+ const REFERENCE_SCORES = {
7
+ agenticSeo: {
8
+ Stripe: 17,
9
+ Vercel: 48,
10
+ Supabase: 52,
11
+ Cloudflare: 55,
12
+ Average: 25,
13
+ },
14
+ cloudflare: {
15
+ Stripe: 2,
16
+ Vercel: 4,
17
+ Supabase: 3,
18
+ Cloudflare: 5,
19
+ Average: 2,
20
+ },
21
+ fern: {
22
+ Stripe: 85,
23
+ Vercel: 60,
24
+ Supabase: 78,
25
+ Anthropic: 72,
26
+ Average: 45,
27
+ },
28
+ };
29
+
30
+ export async function runAllBenchmarks(target) {
31
+ const isUrl = target && target.startsWith("http");
32
+
33
+ const results = await Promise.allSettled([
34
+ runAgenticSeo(target),
35
+ isUrl ? runCloudflare(target) : Promise.resolve(null),
36
+ isUrl ? runFern(target) : Promise.resolve(null),
37
+ ]);
38
+
39
+ return {
40
+ agenticSeo: results[0].status === "fulfilled" ? results[0].value : null,
41
+ cloudflare: results[1].status === "fulfilled" ? results[1].value : null,
42
+ fern: results[2].status === "fulfilled" ? results[2].value : null,
43
+ };
44
+ }
45
+
46
+ export function printBenchmarks(benchmarks) {
47
+ const any = Object.values(benchmarks).some((b) => b && b.available);
48
+ if (!any) return;
49
+
50
+ console.log(
51
+ chalk.bold("\n ─── Benchmarks ────────────────────────────────────\n"),
52
+ );
53
+
54
+ if (benchmarks.agenticSeo?.available) {
55
+ printBenchmarkBlock("agentic-seo", "agenticSeo", benchmarks.agenticSeo);
56
+ }
57
+ if (benchmarks.cloudflare?.available) {
58
+ printBenchmarkBlock("Cloudflare", "cloudflare", benchmarks.cloudflare);
59
+ }
60
+ if (benchmarks.fern?.available) {
61
+ printBenchmarkBlock("Fern", "fern", benchmarks.fern);
62
+ }
63
+ }
64
+
65
+ function printBenchmarkBlock(name, key, b) {
66
+ const pct = b.maxScore > 0 ? Math.round((b.score / b.maxScore) * 100) : 0;
67
+ const color = pct >= 80 ? chalk.green : pct >= 50 ? chalk.yellow : chalk.red;
68
+ const barW = 16;
69
+ const filled = Math.round((pct / 100) * barW);
70
+ const bar = color("█".repeat(filled)) + chalk.dim("░".repeat(barW - filled));
71
+
72
+ console.log(
73
+ ` ${bar} ${chalk.bold(name.padEnd(16))} ${chalk.dim(`${b.score}/${b.maxScore}`)}${b.grade ? chalk.dim(` (${b.grade})`) : ""}`,
74
+ );
75
+
76
+ if (b.checks && b.checks.length > 0) {
77
+ for (const check of b.checks) {
78
+ if (check.status === "pass") {
79
+ console.log(
80
+ chalk.green(` + ${check.id}`) +
81
+ chalk.dim(check.message ? ` ${check.message.slice(0, 60)}` : ""),
82
+ );
83
+ } else if (check.status === "fail") {
84
+ console.log(
85
+ chalk.red(` - ${check.id}`) +
86
+ chalk.dim(check.message ? ` ${check.message.slice(0, 60)}` : ""),
87
+ );
88
+ }
89
+ }
90
+ } else if (b.categories) {
91
+ for (const [, cat] of Object.entries(b.categories)) {
92
+ const catPct =
93
+ cat.percentage ??
94
+ (cat.maxScore > 0 ? Math.round((cat.score / cat.maxScore) * 100) : 0);
95
+ const icon =
96
+ catPct >= 80
97
+ ? chalk.green("✓")
98
+ : catPct >= 40
99
+ ? chalk.yellow("◑")
100
+ : chalk.red("✗");
101
+ console.log(
102
+ chalk.dim(
103
+ ` ${icon} ${(cat.name || "").padEnd(22)} ${cat.score}/${cat.maxScore}`,
104
+ ),
105
+ );
106
+ }
107
+ }
108
+
109
+ const refs = REFERENCE_SCORES[key];
110
+ if (refs) {
111
+ const names = Object.entries(refs)
112
+ .sort((a, b) => b[1] - a[1])
113
+ .map(([n, s]) => `${n} ${s}`)
114
+ .join(chalk.dim(" · "));
115
+ console.log(chalk.dim(` compare: ${names}`));
116
+ }
117
+
118
+ console.log("");
119
+ }
@@ -1,3 +1,117 @@
1
+ const HOW_TO_FIX = {
2
+ "llms.txt": {
3
+ time: "1 hour",
4
+ steps: [
5
+ "Create a file called <code>llms.txt</code> in your site root",
6
+ "First line: <code># Your Company Name</code>",
7
+ "Second line: one sentence describing what you do and for whom",
8
+ "List 3-5 key features, pricing model, and how to get started",
9
+ "Keep it under 5,000 tokens — this is NOT your homepage copy",
10
+ "Deploy it so it's accessible at <code>yoursite.com/llms.txt</code>",
11
+ ],
12
+ example: `# Acme Corp\n> API-first billing platform for SaaS companies.\n\n## What it does\n- Usage-based billing with real-time metering\n- Self-serve pricing pages\n- Revenue recognition automation\n\n## Pricing\n- Starter: $0/mo (up to $10K MRR)\n- Growth: $499/mo\n- Enterprise: custom\n\n## Get started\n1. Sign up at acme.com/signup\n2. Install SDK: npm install @acme/billing\n3. Add 3 lines of code to start metering`,
13
+ },
14
+ "robots.txt AI crawlers": {
15
+ time: "5 minutes",
16
+ steps: [
17
+ "Open your <code>robots.txt</code> file (or create one in your site root)",
18
+ "Add explicit Allow rules for each AI crawler",
19
+ "Deploy — most hosting platforms serve this automatically from root",
20
+ ],
21
+ example: `User-agent: GPTBot\nAllow: /\n\nUser-agent: ClaudeBot\nAllow: /\n\nUser-agent: Google-Extended\nAllow: /\n\nUser-agent: PerplexityBot\nAllow: /`,
22
+ auto: "Run <code>npx aeo-ready scan --fix</code> to generate this automatically.",
23
+ },
24
+ "AGENTS.md / CLAUDE.md": {
25
+ time: "30 minutes",
26
+ steps: [
27
+ "Create <code>AGENTS.md</code> in your repo root",
28
+ "Describe your project structure — key directories and what's in them",
29
+ "List development commands (install, run, test)",
30
+ "Note constraints: rate limits, auth requirements, external dependencies",
31
+ "Keep it factual and precise — LLM-generated AGENTS.md files hurt agent success rates",
32
+ ],
33
+ auto: "Run <code>npx aeo-ready scan --fix</code> to get a scaffold, then edit it.",
34
+ },
35
+ "agents.json manifest": {
36
+ time: "20 minutes",
37
+ steps: [
38
+ "Create <code>.well-known/agent.json</code> in your site root",
39
+ "Add name, description, and capabilities array",
40
+ "Link to your llms.txt and OpenAPI spec",
41
+ "Include contact info and auth method",
42
+ ],
43
+ example: `{\n "schema_version": "1.0",\n "name": "Your Product",\n "description": "What it does in one sentence",\n "capabilities": ["api-integration", "pricing-lookup"],\n "interfaces": {\n "human": "/",\n "llm": "/llms.txt",\n "api": "/openapi.json"\n }\n}`,
44
+ auto: "Run <code>npx aeo-ready scan --fix</code> to generate a draft.",
45
+ },
46
+ "Question-based headings": {
47
+ time: "1-2 hours",
48
+ steps: [
49
+ "Find your top 10 pages by traffic or importance",
50
+ "Rewrite H2/H3 headings as questions users would ask AI",
51
+ 'Example: change "Pricing" → "How much does [Product] cost?"',
52
+ 'Example: change "Features" → "What can [Product] do?"',
53
+ "Aim for 30%+ of headings to be question-based",
54
+ ],
55
+ },
56
+ "Direct answer formatting": {
57
+ time: "1-2 hours",
58
+ steps: [
59
+ "On each key page, add a 40-60 word summary as the first paragraph",
60
+ "This paragraph should directly answer the question the page addresses",
61
+ "Don't start with background or context — lead with the answer",
62
+ "AI systems extract this as the citation snippet",
63
+ ],
64
+ example: `<!-- Before -->\n<h1>Our Billing Platform</h1>\n<p>Founded in 2020, we set out to solve...</p>\n\n<!-- After -->\n<h1>Our Billing Platform</h1>\n<p>Acme is a usage-based billing platform that helps SaaS companies meter API calls, automate invoicing, and recognize revenue. Plans start at $0/mo for up to $10K MRR.</p>`,
65
+ },
66
+ "FAQ markup": {
67
+ time: "30 minutes",
68
+ steps: [
69
+ "Identify 5-10 common questions about your product",
70
+ "Add them to your page as a FAQ section",
71
+ "Wrap them in FAQPage JSON-LD schema",
72
+ "Add the script tag to your page's <code>&lt;head&gt;</code>",
73
+ ],
74
+ example: `<script type="application/ld+json">\n{\n "@context": "https://schema.org",\n "@type": "FAQPage",\n "mainEntity": [{\n "@type": "Question",\n "name": "What does Acme cost?",\n "acceptedAnswer": {\n "@type": "Answer",\n "text": "Acme starts free for up to $10K MRR. Growth is $499/mo."\n }\n }]\n}\n</script>`,
75
+ },
76
+ "Schema.org markup": {
77
+ time: "30 minutes",
78
+ steps: [
79
+ "Determine your schema type: SoftwareApplication (SaaS), Person (portfolio), Article (blog)",
80
+ "Create a JSON-LD block with required fields (name, description, url)",
81
+ "Add it to your homepage <code>&lt;head&gt;</code> in a script tag",
82
+ "For SaaS: include <code>offers</code> with pricing per plan",
83
+ ],
84
+ example: `<script type="application/ld+json">\n{\n "@context": "https://schema.org",\n "@type": "SoftwareApplication",\n "name": "Your Product",\n "description": "One sentence",\n "url": "https://yoursite.com",\n "offers": [{\n "@type": "Offer",\n "name": "Starter",\n "price": "0",\n "priceCurrency": "USD"\n }]\n}\n</script>`,
85
+ },
86
+ "E-E-A-T signals": {
87
+ time: "1 hour",
88
+ steps: [
89
+ "Add author bios with name, role, and credentials to content pages",
90
+ "Add a Person JSON-LD schema for each author",
91
+ "Include first-hand experience markers: 'We built...', 'In our experience...'",
92
+ "Link to author profiles on LinkedIn, GitHub, Twitter",
93
+ ],
94
+ },
95
+ "Content negotiation": {
96
+ time: "2-4 hours (requires code)",
97
+ steps: [
98
+ "Add middleware that checks the <code>Accept</code> header on requests",
99
+ "When <code>Accept: text/markdown</code> is present, return markdown instead of HTML",
100
+ "Strip navigation, footers, and chrome — return just the content",
101
+ "This saves agents thousands of tokens vs parsing your HTML",
102
+ ],
103
+ },
104
+ "Definitive statements": {
105
+ time: "1-2 hours",
106
+ steps: [
107
+ "Search your content for hedging words: 'might', 'perhaps', 'possibly', 'could be'",
108
+ "Replace with direct claims: 'is', 'does', 'provides', 'enables'",
109
+ "AI cites confident statements over uncertain ones",
110
+ "Back claims with data: '50% faster' is better than 'significantly faster'",
111
+ ],
112
+ },
113
+ };
114
+
1
115
  export function renderRecommendations(result) {
2
116
  const failed = [];
3
117
 
@@ -19,15 +133,48 @@ export function renderRecommendations(result) {
19
133
  return `<h2 id="recommendations">Recommendations</h2>\n<p style="color:#3fb950;font-size:13px;">All checks passing. Nice.</p>`;
20
134
  }
21
135
 
22
- let html = `<h2 id="recommendations">Top Recommendations</h2>\n<p style="color:#8b949e;font-size:12px;margin-bottom:12px;">Sorted by point impact — fix these first.</p>`;
136
+ let html = `<h2 id="recommendations">How To Fix It</h2>
137
+ <p style="color:#8b949e;font-size:12px;margin-bottom:16px;">Sorted by point impact. Expand each for step-by-step instructions.</p>`;
23
138
 
24
139
  for (const rec of top) {
25
140
  const category = formatName(rec.category);
26
- html += `\n<div class="rec">
27
- <div class="rec-title">${escapeHtml(rec.name)}</div>
28
- <div class="rec-fix">${escapeHtml(rec.fix)}</div>
29
- <div class="rec-impact">+${rec.impact} points · ${category}</div>
30
- </div>`;
141
+ const howTo = HOW_TO_FIX[rec.name];
142
+
143
+ html += `\n<details class="rec" style="cursor:pointer;">
144
+ <summary style="display:flex;justify-content:space-between;align-items:center;">
145
+ <div>
146
+ <div class="rec-title">${esc(rec.name)}</div>
147
+ <div class="rec-fix">${esc(rec.fix)}</div>
148
+ </div>
149
+ <div style="text-align:right;white-space:nowrap;">
150
+ <span class="rec-impact">+${rec.impact} pts</span>
151
+ ${howTo?.time ? `<div style="font-size:11px;color:#8b949e;">${howTo.time}</div>` : ""}
152
+ </div>
153
+ </summary>`;
154
+
155
+ if (howTo) {
156
+ html += `\n <div style="margin-top:12px;padding-top:12px;border-top:1px solid #21262d;">`;
157
+
158
+ if (howTo.auto) {
159
+ html += `\n <div style="background:#3fb95010;border:1px solid #3fb95033;border-radius:4px;padding:8px 12px;margin-bottom:12px;font-size:12px;color:#3fb950;">${howTo.auto}</div>`;
160
+ }
161
+
162
+ html += `\n <div style="font-size:12px;color:#c9d1d9;"><strong>Steps:</strong></div>
163
+ <ol style="margin:8px 0 0 20px;font-size:12px;color:#8b949e;">`;
164
+ for (const step of howTo.steps) {
165
+ html += `\n <li style="margin:4px 0;">${step}</li>`;
166
+ }
167
+ html += `\n </ol>`;
168
+
169
+ if (howTo.example) {
170
+ html += `\n <div style="margin-top:12px;font-size:12px;color:#c9d1d9;"><strong>Example:</strong></div>
171
+ <pre style="background:#0d1117;border:1px solid #21262d;border-radius:4px;padding:12px;margin-top:6px;font-size:11px;color:#79c0ff;overflow-x:auto;white-space:pre-wrap;">${esc(howTo.example)}</pre>`;
172
+ }
173
+
174
+ html += `\n </div>`;
175
+ }
176
+
177
+ html += `\n</details>`;
31
178
  }
32
179
 
33
180
  return html;
@@ -40,7 +187,7 @@ function formatName(camelCase) {
40
187
  .trim();
41
188
  }
42
189
 
43
- function escapeHtml(str) {
190
+ function esc(str) {
44
191
  return str
45
192
  .replace(/&/g, "&amp;")
46
193
  .replace(/</g, "&lt;")
@@ -20,7 +20,11 @@ export async function saveResult(result, baseDir) {
20
20
  target: result.target,
21
21
  agentReadiness: result.agentReadiness.score,
22
22
  aiVisibility: result.aiVisibility.score,
23
- benchmark: result.benchmark?.score ?? null,
23
+ benchmarks: {
24
+ agenticSeo: result.benchmarks?.agenticSeo?.score ?? null,
25
+ cloudflare: result.benchmarks?.cloudflare?.score ?? null,
26
+ fern: result.benchmarks?.fern?.score ?? null,
27
+ },
24
28
  });
25
29
 
26
30
  writeFileSync(historyPath, JSON.stringify(history, null, 2));
package/src/scan.js CHANGED
@@ -9,7 +9,7 @@ import { runStructuredDataChecks } from "./checks/ai-visibility/structured-data.
9
9
  import { runCitationReadinessChecks } from "./checks/ai-visibility/citation-readiness.js";
10
10
  import { runAuthorityChecks } from "./checks/ai-visibility/authority.js";
11
11
  import { runFreshnessChecks } from "./checks/ai-visibility/freshness.js";
12
- import { runBenchmark } from "./benchmark/agentic-seo.js";
12
+ import { runAllBenchmarks, printBenchmarks } from "./benchmark/index.js";
13
13
  import { saveResult } from "./history/index.js";
14
14
  import { generateDashboard } from "./dashboard/generate.js";
15
15
  import { readdirSync, readFileSync, existsSync } from "fs";
@@ -40,7 +40,7 @@ export async function scan(opts) {
40
40
 
41
41
  let benchmarkResult = null;
42
42
  if (benchmark) {
43
- benchmarkResult = await runBenchmark(mode === "url" ? url : dir);
43
+ benchmarkResult = await runAllBenchmarks(mode === "url" ? url : dir);
44
44
  }
45
45
 
46
46
  const result = {
@@ -52,7 +52,7 @@ export async function scan(opts) {
52
52
  timestamp: new Date().toISOString(),
53
53
  agentReadiness,
54
54
  aiVisibility,
55
- benchmark: benchmarkResult,
55
+ benchmarks: benchmarkResult,
56
56
  };
57
57
 
58
58
  if (!json) {
@@ -207,7 +207,7 @@ function scoreToGrade(score) {
207
207
  }
208
208
 
209
209
  function printReport(result) {
210
- const { score, grade, agentReadiness, aiVisibility, benchmark } = result;
210
+ const { score, grade, agentReadiness, aiVisibility, benchmarks } = result;
211
211
 
212
212
  const gradeColor =
213
213
  grade === "A"
@@ -260,8 +260,32 @@ function printReport(result) {
260
260
  ),
261
261
  );
262
262
 
263
- if (benchmark && benchmark.available) {
264
- printBenchmark(benchmark);
263
+ if (benchmarks) {
264
+ printBenchmarks(benchmarks);
265
+ }
266
+
267
+ if (failed + partial > 0) {
268
+ console.log(chalk.bold(" Next steps:\n"));
269
+ const topFixes = allChecks
270
+ .filter((c) => !c.passed && c.fix)
271
+ .sort(
272
+ (a, b) =>
273
+ b.maxPoints - (b.points || 0) - (a.maxPoints - (a.points || 0)),
274
+ )
275
+ .slice(0, 3);
276
+ for (const fix of topFixes) {
277
+ console.log(chalk.dim(` - ${fix.fix}`));
278
+ }
279
+ console.log(
280
+ chalk.dim(
281
+ `\n Run ${chalk.bold("npx aeo-ready scan --fix")} to auto-fix what we can.`,
282
+ ),
283
+ );
284
+ console.log(
285
+ chalk.dim(
286
+ " Open the dashboard for step-by-step guides on everything else.\n",
287
+ ),
288
+ );
265
289
  }
266
290
  }
267
291
 
@@ -306,32 +330,6 @@ function printScorecard(name, scorecard) {
306
330
  console.log("");
307
331
  }
308
332
 
309
- function printBenchmark(benchmark) {
310
- console.log(
311
- chalk.dim(" ─── Second opinion: agentic-seo ───────────────────\n"),
312
- );
313
- console.log(
314
- ` ${bar(benchmark.score, benchmark.maxScore, 20)} ${chalk.dim(`${benchmark.score}/${benchmark.maxScore}`)}${benchmark.grade ? chalk.dim(` (${benchmark.grade})`) : ""}\n`,
315
- );
316
-
317
- if (benchmark.categories) {
318
- for (const [, cat] of Object.entries(benchmark.categories)) {
319
- const pct =
320
- cat.percentage ?? Math.round((cat.score / cat.maxScore) * 100);
321
- const icon =
322
- pct >= 80
323
- ? chalk.green("✓")
324
- : pct >= 40
325
- ? chalk.yellow("◑")
326
- : chalk.red("✗");
327
- console.log(
328
- ` ${icon} ${cat.name.padEnd(24)} ${bar(cat.score, cat.maxScore, 16)} ${chalk.dim(`${cat.score}/${cat.maxScore} (${pct}%)`)}`,
329
- );
330
- }
331
- console.log("");
332
- }
333
- }
334
-
335
333
  function bar(value, max, width) {
336
334
  const filled = max > 0 ? Math.round((value / max) * width) : 0;
337
335
  const empty = width - filled;
@@ -1,339 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <title>aeo-ready — AI Readiness Dashboard</title>
6
- <style>
7
- * { box-sizing: border-box; margin: 0; padding: 0; }
8
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0d1117; color: #c9d1d9; display: flex; }
9
- nav { position: fixed; top: 0; left: 0; width: 200px; height: 100vh; background: #010409; border-right: 1px solid #21262d; padding: 0; overflow-y: auto; z-index: 10; }
10
- nav h2 { color: #f0f6fc; font-size: 13px; letter-spacing: 0.02em; padding: 20px 16px 14px; border-bottom: 1px solid #21262d; margin: 0 0 8px; }
11
- nav a { display: block; padding: 6px 16px; font-size: 12px; color: #8b949e; text-decoration: none; transition: color 0.15s; }
12
- nav a:hover { color: #f0f6fc; }
13
- nav a.section-head { color: #c9d1d9; font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; margin-top: 12px; }
14
- main { margin-left: 200px; padding: 32px 40px; max-width: 900px; width: 100%; }
15
- h1 { color: #f0f6fc; font-size: 24px; margin-bottom: 4px; }
16
- .subtitle { color: #8b949e; font-size: 13px; margin-bottom: 32px; }
17
- h2 { color: #f0f6fc; font-size: 18px; margin-top: 40px; margin-bottom: 16px; padding-top: 16px; border-top: 1px solid #21262d; }
18
- h3 { color: #f0f6fc; font-size: 14px; margin-top: 16px; margin-bottom: 8px; }
19
- .score-hero { text-align: center; padding: 32px 0; }
20
- .score-hero .grade { font-size: 64px; font-weight: 700; }
21
- .score-hero .number { font-size: 24px; color: #8b949e; margin-top: 4px; }
22
- .score-hero .bar { margin: 16px auto; width: 300px; height: 8px; background: #21262d; border-radius: 4px; overflow: hidden; }
23
- .score-hero .bar-fill { height: 100%; border-radius: 4px; }
24
- .grade-a { color: #3fb950; } .grade-b { color: #79c0ff; } .grade-c { color: #d29922; } .grade-d { color: #f85149; } .grade-f { color: #f85149; }
25
- .bar-a { background: #3fb950; } .bar-b { background: #79c0ff; } .bar-c { background: #d29922; } .bar-d { background: #f85149; } .bar-f { background: #f85149; }
26
- .scorecard { background: #161b22; border: 1px solid #21262d; border-radius: 8px; padding: 20px; margin: 16px 0; }
27
- .scorecard-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
28
- .scorecard-header h3 { margin: 0; }
29
- .scorecard-header .pct { font-size: 20px; font-weight: 600; }
30
- .category { margin: 12px 0; padding: 8px 0; border-bottom: 1px solid #21262d; }
31
- .category:last-child { border-bottom: none; }
32
- .cat-header { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
33
- .cat-name { font-size: 13px; font-weight: 500; flex: 1; }
34
- .cat-score { font-size: 12px; color: #8b949e; }
35
- .cat-bar { width: 120px; height: 4px; background: #21262d; border-radius: 2px; overflow: hidden; }
36
- .cat-bar-fill { height: 100%; border-radius: 2px; }
37
- .check { font-size: 12px; padding: 2px 0 2px 16px; color: #8b949e; }
38
- .check.pass { color: #3fb950; }
39
- .check.fail { color: #f85149; }
40
- .check .fix { display: block; color: #8b949e; font-size: 11px; padding-left: 16px; margin-top: 2px; }
41
- table { width: 100%; border-collapse: collapse; margin: 12px 0; }
42
- th, td { text-align: left; padding: 8px 12px; font-size: 12px; border-bottom: 1px solid #21262d; }
43
- th { color: #8b949e; font-weight: 600; }
44
- .trend { color: #3fb950; } .regression { color: #f85149; }
45
- .rec { background: #161b22; border: 1px solid #21262d; border-radius: 6px; padding: 12px 16px; margin: 8px 0; }
46
- .rec-title { font-size: 13px; font-weight: 500; color: #f0f6fc; }
47
- .rec-fix { font-size: 12px; color: #8b949e; margin-top: 4px; }
48
- .rec-impact { font-size: 11px; color: #d29922; margin-top: 4px; }
49
- svg { display: block; margin: 16px 0; }
50
- </style>
51
- </head>
52
- <body>
53
-
54
- <nav>
55
- <h2>aeo-ready</h2>
56
- <a href="#overall" class="section-head">Overall Score</a>
57
- <a href="#agent-readiness" class="section-head">Agent Readiness</a>
58
- <a href="#ai-visibility" class="section-head">AI Visibility</a>
59
- <a href="#trends" class="section-head">Trends</a>
60
- <a href="#history" class="section-head">History</a>
61
- <a href="#recommendations" class="section-head">Recommendations</a>
62
- </nav>
63
-
64
- <main>
65
-
66
- <h1>AI Readiness Dashboard</h1>
67
- <p class="subtitle">/Users/katlaszlo/Desktop/Github-Wiki/GitHub/agent-web · Updated 2026-05-18</p>
68
-
69
- <!-- SECTION:overall-score -->
70
- <div class="score-hero" id="overall">
71
- <div class="grade grade-c">C</div>
72
- <div class="number">50 / 100</div>
73
- <div class="bar"><div class="bar-fill bar-c" style="width:50%"></div></div>
74
- <div style="display:flex;justify-content:center;gap:40px;margin-top:16px;font-size:13px;">
75
- <span>Agent Readiness: <strong>22/50</strong></span>
76
- <span>AI Visibility: <strong>28/50</strong></span>
77
- </div>
78
- </div>
79
- <div style="text-align:center;padding:12px 24px;margin:16px auto;max-width:500px;background:#d2992210;border:1px solid #d2992233;border-radius:6px;font-size:13px;color:#d29922;">
80
- <strong>Agent Readiness is low.</strong> AI engines can't cite what they can't read.<br>
81
- <span style="color:#8b949e;">Fix the agent side first. Visibility follows.</span>
82
- </div>
83
- <div style="margin-top:24px;padding:20px;background:#161b22;border:1px solid #21262d;border-radius:8px;">
84
- <div style="font-size:13px;font-weight:600;color:#f0f6fc;margin-bottom:12px;">How you compare</div>
85
- <div style="display:flex;align-items:center;gap:8px;margin:6px 0;">
86
- <span style="width:120px;font-size:12px;color:#8b949e;">Cloudflare</span>
87
- <div style="flex:1;height:4px;background:#21262d;border-radius:2px;overflow:hidden;">
88
- <div style="width:55%;height:100%;background:#30363d;border-radius:2px;"></div>
89
- </div>
90
- <span style="font-size:11px;color:#8b949e;width:32px;text-align:right;">55</span>
91
- </div>
92
- <div style="display:flex;align-items:center;gap:8px;margin:6px 0;">
93
- <span style="width:120px;font-size:12px;color:#8b949e;">Supabase</span>
94
- <div style="flex:1;height:4px;background:#21262d;border-radius:2px;overflow:hidden;">
95
- <div style="width:52%;height:100%;background:#30363d;border-radius:2px;"></div>
96
- </div>
97
- <span style="font-size:11px;color:#8b949e;width:32px;text-align:right;">52</span>
98
- </div>
99
- <div style="display:flex;align-items:center;gap:8px;margin:6px 0;">
100
- <span style="width:120px;font-size:12px;color:#f0f6fc;font-weight:600;">You</span>
101
- <div style="flex:1;height:4px;background:#21262d;border-radius:2px;overflow:hidden;">
102
- <div style="width:50%;height:100%;background:#d29922;border-radius:2px;"></div>
103
- </div>
104
- <span style="font-size:11px;color:#f0f6fc;width:32px;text-align:right;font-weight:600;">50</span>
105
- </div>
106
- <div style="display:flex;align-items:center;gap:8px;margin:6px 0;">
107
- <span style="width:120px;font-size:12px;color:#8b949e;">Vercel</span>
108
- <div style="flex:1;height:4px;background:#21262d;border-radius:2px;overflow:hidden;">
109
- <div style="width:48%;height:100%;background:#30363d;border-radius:2px;"></div>
110
- </div>
111
- <span style="font-size:11px;color:#8b949e;width:32px;text-align:right;">48</span>
112
- </div>
113
- <div style="display:flex;align-items:center;gap:8px;margin:6px 0;">
114
- <span style="width:120px;font-size:12px;color:#8b949e;">Anthropic Docs</span>
115
- <div style="flex:1;height:4px;background:#21262d;border-radius:2px;overflow:hidden;">
116
- <div style="width:43%;height:100%;background:#30363d;border-radius:2px;"></div>
117
- </div>
118
- <span style="font-size:11px;color:#8b949e;width:32px;text-align:right;">43</span>
119
- </div>
120
- <div style="display:flex;align-items:center;gap:8px;margin:6px 0;">
121
- <span style="width:120px;font-size:12px;color:#8b949e;">Stripe</span>
122
- <div style="flex:1;height:4px;background:#21262d;border-radius:2px;overflow:hidden;">
123
- <div style="width:33%;height:100%;background:#30363d;border-radius:2px;"></div>
124
- </div>
125
- <span style="font-size:11px;color:#8b949e;width:32px;text-align:right;">33</span>
126
- </div>
127
- <div style="display:flex;align-items:center;gap:8px;margin:6px 0;">
128
- <span style="width:120px;font-size:12px;color:#8b949e;">Average site</span>
129
- <div style="flex:1;height:4px;background:#21262d;border-radius:2px;overflow:hidden;">
130
- <div style="width:25%;height:100%;background:#30363d;border-radius:2px;"></div>
131
- </div>
132
- <span style="font-size:11px;color:#8b949e;width:32px;text-align:right;">25</span>
133
- </div>
134
- </div>
135
- <!-- /SECTION:overall-score -->
136
-
137
- <!-- SECTION:agent-readiness-scorecard -->
138
- <div class="scorecard" id="agent-readiness">
139
- <div class="scorecard-header">
140
- <h3>Agent Readiness</h3>
141
- <span class="pct grade-c">22/50 (44%)</span>
142
- </div>
143
- <div class="category">
144
- <div class="cat-header">
145
- <span class="cat-name">Discovery</span>
146
- <span class="cat-score">0/12</span>
147
- <div class="cat-bar"><div class="cat-bar-fill" style="width:0%;background:#f85149"></div></div>
148
- </div>
149
- <div class="check fail">- llms.txt [0/4]<span class="fix">No llms.txt found. Create one describing what your site does for AI agents.</span></div>
150
- <div class="check fail">- robots.txt AI crawlers [0/3]<span class="fix">No robots.txt found. Add one with explicit AI crawler rules.</span></div>
151
- <div class="check fail">- sitemap.xml [0/2]<span class="fix">No sitemap.xml found.</span></div>
152
- <div class="check fail">- AI-friendly meta tags [0/3]<span class="fix">No HTML available to check meta tags.</span></div>
153
- </div>
154
- <div class="category">
155
- <div class="cat-header">
156
- <span class="cat-name">Content Structure</span>
157
- <span class="cat-score">10/15</span>
158
- <div class="cat-bar"><div class="cat-bar-fill" style="width:67%;background:#d29922"></div></div>
159
- </div>
160
- <div class="check pass">+ Markdown content available [3]</div>
161
- <div class="check pass">+ Heading hierarchy [3]</div>
162
- <div class="check pass">+ Token budget compliance [4]</div>
163
- <div class="check fail">- Front-loading (first 500 tokens) [1/3]<span class="fix">Most pages don't answer what/why/how in first 500 tokens. Lead with the point.</span></div>
164
- <div class="check fail">- Copy-for-AI affordances [0/2]<span class="fix">No copy-for-AI affordances. Add a &quot;Copy as Markdown&quot; button on doc pages.</span></div>
165
- </div>
166
- <div class="category">
167
- <div class="cat-header">
168
- <span class="cat-name">Capability</span>
169
- <span class="cat-score">4/13</span>
170
- <div class="cat-bar"><div class="cat-bar-fill" style="width:31%;background:#f85149"></div></div>
171
- </div>
172
- <div class="check pass">+ AGENTS.md / CLAUDE.md [4]</div>
173
- <div class="check fail">- agents.json manifest [0/3]<span class="fix">No agents.json or .well-known/agent.json found.</span></div>
174
- <div class="check fail">- OpenAPI spec [0/3]<span class="fix">No OpenAPI spec found. Add openapi.json or openapi.yaml.</span></div>
175
- <div class="check fail">- Content negotiation [0/3]<span class="fix">No content negotiation setup. Consider serving markdown for Accept: text/markdown requests.</span></div>
176
- </div>
177
- <div class="category">
178
- <div class="cat-header">
179
- <span class="cat-name">Actionable</span>
180
- <span class="cat-score">8/10</span>
181
- <div class="cat-bar"><div class="cat-bar-fill" style="width:80%;background:#3fb950"></div></div>
182
- </div>
183
- <div class="check pass">+ Machine-readable contact [3]</div>
184
- <div class="check pass">+ Programmatic pricing [3]</div>
185
- <div class="check fail">- API endpoint [1/2]<span class="fix">API references found but no formal spec. Add OpenAPI spec.</span></div>
186
- <div class="check pass">+ SDK / integration manifest [2]</div>
187
- </div>
188
- </div>
189
- <!-- /SECTION:agent-readiness-scorecard -->
190
-
191
- <!-- SECTION:ai-visibility-scorecard -->
192
- <div class="scorecard" id="ai-visibility">
193
- <div class="scorecard-header">
194
- <h3>AI Visibility</h3>
195
- <span class="pct grade-c">28/50 (56%)</span>
196
- </div>
197
- <div class="category">
198
- <div class="cat-header">
199
- <span class="cat-name">Structured Data</span>
200
- <span class="cat-score">6/12</span>
201
- <div class="cat-bar"><div class="cat-bar-fill" style="width:50%;background:#d29922"></div></div>
202
- </div>
203
- <div class="check fail">- Schema.org markup [2/4]<span class="fix">Schema.org present but: type doesn't match site category, missing required fields.</span></div>
204
- <div class="check pass">+ FAQ markup [3]</div>
205
- <div class="check pass">+ Rich schemas (Breadcrumb/HowTo/Product) [3]</div>
206
- <div class="check fail">- Open Graph + meta description [0/2]<span class="fix">No Open Graph or meta description tags. AI systems use these for page summaries.</span></div>
207
- </div>
208
- <div class="category">
209
- <div class="cat-header">
210
- <span class="cat-name">Citation Readiness</span>
211
- <span class="cat-score">4/15</span>
212
- <div class="cat-bar"><div class="cat-bar-fill" style="width:27%;background:#f85149"></div></div>
213
- </div>
214
- <div class="check fail">- Direct answer formatting [1/4]<span class="fix">Most pages don't lead with a concise summary (40-60 words). This is what AI systems cite.</span></div>
215
- <div class="check fail">- Question-based headings [1/4]<span class="fix">Very few question-based headings. Rephrase key H2/H3s as questions users would ask AI.</span></div>
216
- <div class="check pass">+ Citation-friendly structure [4]</div>
217
- <div class="check fail">- Definitive statements [2/3]<span class="fix">Some definitive statements but too much hedging. AI cites confident claims over uncertain ones.</span></div>
218
- </div>
219
- <div class="category">
220
- <div class="cat-header">
221
- <span class="cat-name">Authority</span>
222
- <span class="cat-score">10/13</span>
223
- <div class="cat-bar"><div class="cat-bar-fill" style="width:77%;background:#d29922"></div></div>
224
- </div>
225
- <div class="check pass">+ E-E-A-T signals [4]</div>
226
- <div class="check pass">+ Entity optimization [3]</div>
227
- <div class="check pass">+ External validation & references [3]</div>
228
- </div>
229
- <div class="category">
230
- <div class="cat-header">
231
- <span class="cat-name">Freshness</span>
232
- <span class="cat-score">8/10</span>
233
- <div class="cat-bar"><div class="cat-bar-fill" style="width:80%;background:#3fb950"></div></div>
234
- </div>
235
- <div class="check pass">+ Last modified dates [3]</div>
236
- <div class="check fail">- Publication cadence [0/2]<span class="fix">Infrequent updates (~541 day gaps). Regular publishing signals authority to AI systems.</span></div>
237
- <div class="check pass">+ Content recency [3]</div>
238
- <div class="check pass">+ Evergreen content flagged [2]</div>
239
- </div>
240
- </div>
241
- <!-- /SECTION:ai-visibility-scorecard -->
242
-
243
- <!-- SECTION:trend-chart -->
244
- <h2 id="trends">Score Trends</h2>
245
- <svg width="700" height="200" xmlns="http://www.w3.org/2000/svg">
246
- <line x1="40" y1="170" x2="680" y2="170" stroke="#21262d" stroke-width="1"/>
247
- <text x="32" y="174" text-anchor="end" fill="#8b949e" font-size="10">0</text>
248
- <line x1="40" y1="132.5" x2="680" y2="132.5" stroke="#21262d" stroke-width="1"/>
249
- <text x="32" y="136.5" text-anchor="end" fill="#8b949e" font-size="10">25</text>
250
- <line x1="40" y1="95" x2="680" y2="95" stroke="#21262d" stroke-width="1"/>
251
- <text x="32" y="99" text-anchor="end" fill="#8b949e" font-size="10">50</text>
252
- <line x1="40" y1="57.5" x2="680" y2="57.5" stroke="#21262d" stroke-width="1"/>
253
- <text x="32" y="61.5" text-anchor="end" fill="#8b949e" font-size="10">75</text>
254
- <line x1="40" y1="20" x2="680" y2="20" stroke="#21262d" stroke-width="1"/>
255
- <text x="32" y="24" text-anchor="end" fill="#8b949e" font-size="10">100</text>
256
- <text x="40" y="195" text-anchor="middle" fill="#8b949e" font-size="10">05-18</text>
257
- <text x="680" y="195" text-anchor="middle" fill="#8b949e" font-size="10">05-18</text>
258
- <path d="M 40.0 80.0 L 680.0 104.0" fill="none" stroke="#79c0ff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
259
- <path d="M 40.0 89.0 L 680.0 86.0" fill="none" stroke="#d29922" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
260
- <path d="M 40.0 84.5 L 680.0 95.0" fill="none" stroke="#f0f6fc" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
261
- <circle cx="40.0" cy="84.5" r="3" fill="#f0f6fc"/>
262
- <circle cx="680.0" cy="95.0" r="3" fill="#f0f6fc"/>
263
- <text x="680" y="15" text-anchor="end" fill="#f0f6fc" font-size="10">Overall</text>
264
- <text x="600" y="15" text-anchor="end" fill="#79c0ff" font-size="10">Agent (x2)</text>
265
- <text x="500" y="15" text-anchor="end" fill="#d29922" font-size="10">Visibility (x2)</text>
266
- </svg>
267
- <!-- /SECTION:trend-chart -->
268
-
269
- <!-- SECTION:history-table -->
270
- <h2 id="history">Scan History</h2>
271
- <table>
272
- <tr><th>Date</th><th>Score</th><th>Grade</th><th>Agent</th><th>Visibility</th><th>Delta</th></tr>
273
- <tr>
274
- <td>2026-05-18</td>
275
- <td>50/100</td>
276
- <td class="grade-c">C</td>
277
- <td>22/50</td>
278
- <td>28/50</td>
279
- <td><span class="regression">-7</span></td>
280
- </tr>
281
- <tr>
282
- <td>2026-05-18</td>
283
- <td>57/100</td>
284
- <td class="grade-c">C</td>
285
- <td>30/50</td>
286
- <td>27/50</td>
287
- <td><span style="color:#8b949e">—</span></td>
288
- </tr>
289
- </table>
290
- <!-- /SECTION:history-table -->
291
-
292
- <!-- SECTION:recommendations -->
293
- <h2 id="recommendations">Top Recommendations</h2>
294
- <p style="color:#8b949e;font-size:12px;margin-bottom:12px;">Sorted by point impact — fix these first.</p>
295
- <div class="rec">
296
- <div class="rec-title">llms.txt</div>
297
- <div class="rec-fix">No llms.txt found. Create one describing what your site does for AI agents.</div>
298
- <div class="rec-impact">+4 points · Discovery</div>
299
- </div>
300
- <div class="rec">
301
- <div class="rec-title">robots.txt AI crawlers</div>
302
- <div class="rec-fix">No robots.txt found. Add one with explicit AI crawler rules.</div>
303
- <div class="rec-impact">+3 points · Discovery</div>
304
- </div>
305
- <div class="rec">
306
- <div class="rec-title">AI-friendly meta tags</div>
307
- <div class="rec-fix">No HTML available to check meta tags.</div>
308
- <div class="rec-impact">+3 points · Discovery</div>
309
- </div>
310
- <div class="rec">
311
- <div class="rec-title">agents.json manifest</div>
312
- <div class="rec-fix">No agents.json or .well-known/agent.json found.</div>
313
- <div class="rec-impact">+3 points · Capability</div>
314
- </div>
315
- <div class="rec">
316
- <div class="rec-title">OpenAPI spec</div>
317
- <div class="rec-fix">No OpenAPI spec found. Add openapi.json or openapi.yaml.</div>
318
- <div class="rec-impact">+3 points · Capability</div>
319
- </div>
320
- <div class="rec">
321
- <div class="rec-title">Content negotiation</div>
322
- <div class="rec-fix">No content negotiation setup. Consider serving markdown for Accept: text/markdown requests.</div>
323
- <div class="rec-impact">+3 points · Capability</div>
324
- </div>
325
- <div class="rec">
326
- <div class="rec-title">Direct answer formatting</div>
327
- <div class="rec-fix">Most pages don't lead with a concise summary (40-60 words). This is what AI systems cite.</div>
328
- <div class="rec-impact">+3 points · Citation Readiness</div>
329
- </div>
330
- <div class="rec">
331
- <div class="rec-title">Question-based headings</div>
332
- <div class="rec-fix">Very few question-based headings. Rephrase key H2/H3s as questions users would ask AI.</div>
333
- <div class="rec-impact">+3 points · Citation Readiness</div>
334
- </div>
335
- <!-- /SECTION:recommendations -->
336
-
337
- </main>
338
- </body>
339
- </html>