aeo-ready 1.5.0 → 1.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aeo-ready",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
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": {
@@ -12,21 +12,31 @@ const KNOWN_FILES = [
12
12
  "skill.md",
13
13
  "agent-permissions.json",
14
14
  "agents.json",
15
+ "agents.txt",
16
+ "openapi.json",
15
17
  "sitemap.xml",
16
18
  ".well-known/ai-plugin.json",
19
+ ".well-known/agent-card.json",
20
+ ".well-known/mcp/server-card.json",
21
+ ".well-known/agent-skills/index.json",
17
22
  ];
18
23
 
19
- async function fetchText(url) {
24
+ async function fetchText(url, headers) {
20
25
  try {
21
- const res = await fetch(url, { redirect: "follow" });
26
+ const res = await fetch(url, { redirect: "follow", headers });
22
27
  if (!res.ok) return null;
23
28
  return await res.text();
24
- } catch (err) {
25
- console.warn(`Warning: failed to fetch ${url}: ${err.message}`);
29
+ } catch {
26
30
  return null;
27
31
  }
28
32
  }
29
33
 
34
+ function saveToDisk(tempDir, relativePath, content) {
35
+ const filePath = join(tempDir, relativePath);
36
+ mkdirSync(dirname(filePath), { recursive: true });
37
+ writeFileSync(filePath, content);
38
+ }
39
+
30
40
  function parseSitemapUrls(xml, baseUrl) {
31
41
  const urls = [];
32
42
  const matches = xml.matchAll(/<loc>([^<]+)<\/loc>/g);
@@ -37,25 +47,71 @@ function parseSitemapUrls(xml, baseUrl) {
37
47
  return urls;
38
48
  }
39
49
 
40
- function urlToFilePath(url, baseUrl) {
50
+ function urlToFilePath(url) {
41
51
  let path = new URL(url).pathname;
42
52
  if (path.endsWith("/")) path += "index.html";
43
53
  else if (!path.includes(".")) path += ".html";
44
54
  return path.replace(/^\//, "");
45
55
  }
46
56
 
47
- async function fetchSiteToDir(baseUrl) {
48
- const tempDir = mkdtempSync(join(tmpdir(), "aeo-"));
57
+ async function discoverSkillFiles(baseUrl, tempDir) {
58
+ const indexContent = await fetchText(
59
+ `${baseUrl}/.well-known/agent-skills/index.json`,
60
+ );
61
+ if (!indexContent) return;
49
62
 
50
- for (const file of KNOWN_FILES) {
51
- const content = await fetchText(`${baseUrl}/${file}`);
52
- if (content) {
53
- const filePath = join(tempDir, file);
54
- mkdirSync(dirname(filePath), { recursive: true });
55
- writeFileSync(filePath, content);
63
+ let index;
64
+ try {
65
+ index = JSON.parse(indexContent);
66
+ } catch {
67
+ return;
68
+ }
69
+
70
+ const skills = index.skills || index;
71
+ if (!Array.isArray(skills)) return;
72
+
73
+ const fetches = [];
74
+ for (const skill of skills) {
75
+ const dir = skill.path || skill.id;
76
+ if (!dir) continue;
77
+
78
+ const base = `.well-known/agent-skills/${dir}`;
79
+ fetches.push(
80
+ fetchText(`${baseUrl}/${base}/SKILL.md`).then((content) => {
81
+ if (content) saveToDisk(tempDir, `${base}/SKILL.md`, content);
82
+ }),
83
+ );
84
+
85
+ const refs = skill.references || [];
86
+ for (const ref of refs) {
87
+ const refPath = typeof ref === "string" ? ref : ref.path || ref.file;
88
+ if (!refPath) continue;
89
+ fetches.push(
90
+ fetchText(`${baseUrl}/${base}/references/${refPath}`).then(
91
+ (content) => {
92
+ if (content)
93
+ saveToDisk(tempDir, `${base}/references/${refPath}`, content);
94
+ },
95
+ ),
96
+ );
56
97
  }
57
98
  }
58
99
 
100
+ await Promise.all(fetches);
101
+ }
102
+
103
+ async function fetchSiteToDir(baseUrl) {
104
+ const tempDir = mkdtempSync(join(tmpdir(), "aeo-"));
105
+
106
+ await Promise.all(
107
+ KNOWN_FILES.map(async (file) => {
108
+ const content = await fetchText(`${baseUrl}/${file}`);
109
+ if (content) saveToDisk(tempDir, file, content);
110
+ }),
111
+ );
112
+
113
+ await discoverSkillFiles(baseUrl, tempDir);
114
+
59
115
  const sitemap = await fetchText(`${baseUrl}/sitemap.xml`);
60
116
  if (!sitemap) return tempDir;
61
117
 
@@ -63,25 +119,27 @@ async function fetchSiteToDir(baseUrl) {
63
119
 
64
120
  await Promise.all(
65
121
  urls.map(async (url) => {
66
- const path = urlToFilePath(url, baseUrl);
122
+ const path = urlToFilePath(url);
67
123
  if (KNOWN_FILES.includes(path)) return;
68
124
 
69
- const html = await fetchText(url);
70
- if (html) {
71
- const htmlPath = join(tempDir, path);
72
- mkdirSync(dirname(htmlPath), { recursive: true });
73
- writeFileSync(htmlPath, html);
74
- }
75
-
76
- const mdUrl = url
77
- .replace(/\.html$/, ".md")
78
- .replace(/\/?$/, (m) => (m === "/" ? "/index.md" : ".md"));
79
- if (mdUrl !== url) {
80
- const md = await fetchText(mdUrl);
81
- if (md) {
82
- const mdPath = join(tempDir, path.replace(/\.html$/, ".md"));
83
- mkdirSync(dirname(mdPath), { recursive: true });
84
- writeFileSync(mdPath, md);
125
+ const [html, md] = await Promise.all([
126
+ fetchText(url),
127
+ fetchText(url, { Accept: "text/markdown" }),
128
+ ]);
129
+
130
+ if (html) saveToDisk(tempDir, path, html);
131
+
132
+ if (md && md !== html) {
133
+ saveToDisk(tempDir, path.replace(/\.html$/, ".md"), md);
134
+ } else {
135
+ const mdUrl = url
136
+ .replace(/\.html$/, ".md")
137
+ .replace(/\/?$/, (m) => (m === "/" ? "/index.md" : ".md"));
138
+ if (mdUrl !== url) {
139
+ const mdFallback = await fetchText(mdUrl);
140
+ if (mdFallback) {
141
+ saveToDisk(tempDir, path.replace(/\.html$/, ".md"), mdFallback);
142
+ }
85
143
  }
86
144
  }
87
145
  }),
@@ -8,24 +8,23 @@ import { runAgentgrade } from "./agentgrade.js";
8
8
  const REFERENCE_SCORES = {
9
9
  agenticSeo: {
10
10
  Stripe: 17,
11
- Vercel: 48,
12
- Supabase: 52,
13
- Cloudflare: 55,
14
- Average: 25,
11
+ Cloudflare: 20,
12
+ Supabase: 20,
13
+ Average: 19,
15
14
  },
16
15
  cloudflare: {
17
- Stripe: 2,
18
- Vercel: 4,
19
- Supabase: 3,
20
- Cloudflare: 5,
16
+ Supabase: 4,
17
+ Cloudflare: 3,
18
+ Vercel: 2,
19
+ Stripe: 1,
21
20
  Average: 2,
22
21
  },
23
22
  fern: {
24
- Stripe: 85,
25
- Vercel: 60,
26
- Supabase: 78,
27
- Anthropic: 72,
28
- Average: 45,
23
+ Cloudflare: 85,
24
+ Stripe: 84,
25
+ Supabase: 82,
26
+ Vercel: 75,
27
+ Average: 55,
29
28
  },
30
29
  };
31
30