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 +6 -0
- package/README.md +20 -6
- package/package.json +2 -2
- package/src/benchmark/agentgrade.js +55 -0
- package/src/benchmark/index.js +12 -0
- package/src/benchmark/vercel.js +57 -0
- package/src/scan.js +11 -1
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
|
-
|
|
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
|
|
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.
|
|
4
|
-
"description": "AEO benchmark aggregator. One scan, every score. Collects agentic-seo, Cloudflare, and
|
|
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
|
+
}
|
package/src/benchmark/index.js
CHANGED
|
@@ -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(
|
|
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
|
|