aeo-ready 1.3.2 → 1.4.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.4.0
4
+
5
+ - Add Vercel Agent Readability benchmark (`@vercel/agent-readability`)
6
+ - Add AgentGrade benchmark (`agentgrade-cli`)
7
+ - Scan now runs 5 benchmarks in parallel: agentic-seo, Cloudflare, Fern, Vercel, AgentGrade
8
+
3
9
  ## 1.3.2
4
10
 
5
11
  - Fix broken `afdocs` fix command — was calling `npx afdocs <url>` instead of `npx afdocs check <url>`
package/README.md CHANGED
@@ -17,6 +17,8 @@ Runs every major AEO (Agentic Engine Optimization) benchmark against your site i
17
17
  | **agentic-seo** (Addy Osmani) | Discovery, content structure, token economics, capability signaling, UX bridge | 10 |
18
18
  | **Cloudflare** (isitagentready.com) | Discoverability, content accessibility, bot access, API/MCP/A2A discovery, commerce | 19 |
19
19
  | **Fern** (afdocs) | llms.txt quality, markdown availability, page size, content structure, URL stability, auth | 23 |
20
+ | **Vercel** (Agent Readability Spec) | Agent reachability, discoverability, markdown serving, HTML agent-friendliness | 25 |
21
+ | **AgentGrade** (agentgrade.com) | MCP, payment protocols, identity standards, content negotiation, OpenAPI, infrastructure | 70+ |
20
22
 
21
23
  ## Usage
22
24
 
@@ -41,19 +43,19 @@ With --dir: agentic-seo 92/100 (A)
41
43
  ```
42
44
  aeo-ready — yoursite.com
43
45
 
46
+ Checking agentic-seo · Cloudflare · Fern · Vercel · AgentGrade...
47
+
44
48
  agentic-seo ·································· 91/100 A
45
49
  ✓ Discovery 25/25
46
50
  ◑ Content Structure 18/25
47
51
  ✓ Token Economics 25/25
48
52
  ✓ Capability Signaling 15/15
49
53
  ✓ UX Bridge 8/10
50
- vs Cloudflare 55 · Supabase 52 · Vercel 48 · Stripe 17
51
54
 
52
55
  Cloudflare ···································· 4/5 B
53
56
  10 passed 2 failed
54
57
  ✗ robotsTxtAiRules No rules for AI bots found
55
58
  ✗ contentSignals No content signals in robots.txt
56
- vs Cloudflare 5 · Vercel 4 · Supabase 3 · Stripe 2
57
59
 
58
60
  Fern ········································ 83/100 B
59
61
  9 passed 4 failed
@@ -61,14 +63,26 @@ With --dir: agentic-seo 92/100 (A)
61
63
  ✗ content-start-position 2 pages have content past 50%
62
64
  ✗ llms-txt-coverage Covers 67% of sitemap
63
65
  ✗ markdown-content-parity 4 pages have content differences
64
- vs Stripe 85 · Supabase 78 · Anthropic 72 · Vercel 60
66
+
67
+ Vercel ····································· 75/100 B
68
+ 15 passed 5 failed
69
+ ✗ robots.txt blocked: ccbot
70
+ ✗ Agent UA → markdown returned HTML
71
+ ✗ .md URL → markdown status 404
72
+ ✗ Frontmatter no frontmatter found
73
+ ✗ Missing page → markdown returned 404
74
+
75
+ AgentGrade ································ 81/100 B+
76
+ 30 passed 10 failed
77
+ ✗ llms.txt linked from HTML
78
+ ✗ Accept: JSON returns JSON
79
+ ✗ Accept: text returns text
65
80
 
66
81
  ──────────────────────────────────────────────────
67
82
  Overall 85/100
68
83
 
69
84
  Next steps
70
- npx agentic-seo init scaffold llms.txt, AGENTS.md
71
- npx afdocs https://yoursite.com 4 Fern issues
85
+ npx afdocs check https://yoursite.com 4 Fern issues
72
86
  npx skills add katrinalaszlo/agent-serve make your product agent-ready
73
87
 
74
88
  Fix now? [y/N]
@@ -96,7 +110,7 @@ npx aeo-ready history # show last 10 scans
96
110
  import { scan, getHistory } from "aeo-ready";
97
111
 
98
112
  const result = await scan({ url: "https://yoursite.com", dir: "./public", json: true });
99
- // result.averageScore, result.benchmarks.agenticSeo, .cloudflare, .fern
113
+ // result.averageScore, result.benchmarks.agenticSeo, .cloudflare, .fern, .vercel, .agentgrade
100
114
 
101
115
  const history = getHistory(process.cwd());
102
116
  // history.scans — array of past scan results
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aeo-ready",
3
- "version": "1.3.2",
4
- "description": "AEO benchmark aggregator. One scan, every score. Collects agentic-seo, Cloudflare, and Fern in one report.",
3
+ "version": "1.4.0",
4
+ "description": "AEO benchmark aggregator. One scan, every score. Collects agentic-seo, Cloudflare, Fern, Vercel, and AgentGrade in one report.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "aeo-ready": "./bin/cli.js"
@@ -0,0 +1,55 @@
1
+ import { execFileSync } from "child_process";
2
+
3
+ export async function runAgentgrade(url) {
4
+ try {
5
+ const raw = execFileSync("npx", ["agentgrade-cli", url, "--json"], {
6
+ timeout: 60000,
7
+ encoding: "utf8",
8
+ stdio: ["pipe", "pipe", "pipe"],
9
+ });
10
+
11
+ const jsonStart = raw.indexOf("{");
12
+ if (jsonStart === -1) {
13
+ return { available: false, reason: "no JSON in output" };
14
+ }
15
+ const result = JSON.parse(raw.slice(jsonStart));
16
+ const scoreObj = result.score || {};
17
+
18
+ const categories = {};
19
+ for (const group of scoreObj.groups || []) {
20
+ if (!group.applicable) continue;
21
+ categories[group.key] = {
22
+ name: group.label,
23
+ score: group.passed,
24
+ maxScore: group.total,
25
+ percentage: group.pct ?? 0,
26
+ };
27
+ }
28
+
29
+ const checks = [];
30
+ for (const group of scoreObj.groups || []) {
31
+ if (!group.applicable) continue;
32
+ for (const check of group.checks || []) {
33
+ checks.push({
34
+ id: check.label,
35
+ status: check.passed ? "pass" : "fail",
36
+ message: check.hint || "",
37
+ });
38
+ }
39
+ }
40
+
41
+ return {
42
+ score: scoreObj.pct ?? 0,
43
+ maxScore: 100,
44
+ grade: scoreObj.grade || null,
45
+ categories,
46
+ checks,
47
+ available: true,
48
+ };
49
+ } catch (err) {
50
+ console.warn(
51
+ `Warning: AgentGrade benchmark failed for ${url}: ${err.message}`,
52
+ );
53
+ return { available: false, reason: err.message?.slice(0, 100) };
54
+ }
55
+ }
@@ -2,6 +2,8 @@ import chalk from "chalk";
2
2
  import { runBenchmark as runAgenticSeo } from "./agentic-seo.js";
3
3
  import { runCloudflare } from "./cloudflare.js";
4
4
  import { runFern } from "./fern.js";
5
+ import { runVercel } from "./vercel.js";
6
+ import { runAgentgrade } from "./agentgrade.js";
5
7
 
6
8
  const REFERENCE_SCORES = {
7
9
  agenticSeo: {
@@ -36,12 +38,16 @@ export async function runAllBenchmarks(target, dir) {
36
38
  runAgenticSeo(dir || target),
37
39
  isUrl ? runCloudflare(target) : Promise.resolve(null),
38
40
  isUrl ? runFern(target) : Promise.resolve(null),
41
+ isUrl ? runVercel(target) : Promise.resolve(null),
42
+ isUrl ? runAgentgrade(target) : Promise.resolve(null),
39
43
  ]);
40
44
 
41
45
  return {
42
46
  agenticSeo: results[0].status === "fulfilled" ? results[0].value : null,
43
47
  cloudflare: results[1].status === "fulfilled" ? results[1].value : null,
44
48
  fern: results[2].status === "fulfilled" ? results[2].value : null,
49
+ vercel: results[3].status === "fulfilled" ? results[3].value : null,
50
+ agentgrade: results[4].status === "fulfilled" ? results[4].value : null,
45
51
  };
46
52
  }
47
53
 
@@ -60,6 +66,12 @@ export function printBenchmarks(benchmarks) {
60
66
  if (benchmarks.fern?.available) {
61
67
  printBenchmarkBlock("Fern", "fern", benchmarks.fern);
62
68
  }
69
+ if (benchmarks.vercel?.available) {
70
+ printBenchmarkBlock("Vercel", "vercel", benchmarks.vercel);
71
+ }
72
+ if (benchmarks.agentgrade?.available) {
73
+ printBenchmarkBlock("AgentGrade", "agentgrade", benchmarks.agentgrade);
74
+ }
63
75
  }
64
76
 
65
77
  function scoreColor(pct) {
@@ -0,0 +1,57 @@
1
+ import { execFileSync } from "child_process";
2
+
3
+ export async function runVercel(url) {
4
+ try {
5
+ const output = execFileSync(
6
+ "npx",
7
+ ["@vercel/agent-readability", "audit", url, "--json"],
8
+ { timeout: 60000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] },
9
+ );
10
+
11
+ const result = JSON.parse(output);
12
+ const score = result.score ?? 0;
13
+ const maxScore = 100;
14
+
15
+ const categories = {};
16
+ for (const [key, cat] of Object.entries(result.categories || {})) {
17
+ categories[key] = {
18
+ name: key.replace(/_/g, " "),
19
+ score: cat.passed,
20
+ maxScore: cat.total,
21
+ percentage:
22
+ cat.total > 0 ? Math.round((cat.passed / cat.total) * 100) : 0,
23
+ };
24
+ }
25
+
26
+ const checks = [];
27
+ for (const cat of Object.values(result.categories || {})) {
28
+ for (const check of cat.checks || []) {
29
+ checks.push({
30
+ id: check.name,
31
+ status: check.skipped ? "skip" : check.passed ? "pass" : "fail",
32
+ message: check.detail || check.hint || "",
33
+ });
34
+ }
35
+ }
36
+
37
+ return {
38
+ score,
39
+ maxScore,
40
+ grade: ratingToGrade(result.rating),
41
+ categories,
42
+ checks,
43
+ available: true,
44
+ };
45
+ } catch (err) {
46
+ console.warn(`Warning: Vercel benchmark failed for ${url}: ${err.message}`);
47
+ return { available: false, reason: err.message?.slice(0, 100) };
48
+ }
49
+ }
50
+
51
+ function ratingToGrade(rating) {
52
+ if (rating === "Excellent") return "A";
53
+ if (rating === "Good") return "B";
54
+ if (rating === "Fair") return "C";
55
+ if (rating === "Poor") return "D";
56
+ return "F";
57
+ }
package/src/scan.js CHANGED
@@ -9,7 +9,11 @@ export async function scan(opts) {
9
9
 
10
10
  if (!json) {
11
11
  console.log(chalk.bold("\n aeo-ready") + chalk.dim(` — ${url}\n`));
12
- console.log(chalk.dim(" Checking agentic-seo · Cloudflare · Fern...\n"));
12
+ console.log(
13
+ chalk.dim(
14
+ " Checking agentic-seo · Cloudflare · Fern · Vercel · AgentGrade...\n",
15
+ ),
16
+ );
13
17
  }
14
18
 
15
19
  const benchmarks = await runAllBenchmarks(url, dir);
@@ -55,6 +59,12 @@ function collectScores(benchmarks) {
55
59
  if (benchmarks.fern?.available) {
56
60
  scores.push(benchmarks.fern.score);
57
61
  }
62
+ if (benchmarks.vercel?.available) {
63
+ scores.push(benchmarks.vercel.score);
64
+ }
65
+ if (benchmarks.agentgrade?.available) {
66
+ scores.push(benchmarks.agentgrade.score);
67
+ }
58
68
  return scores;
59
69
  }
60
70