aeo-ready 1.7.1 → 1.7.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.
- package/package.json +1 -1
- package/src/recommendations.js +88 -71
- package/src/scan.js +1 -1
package/package.json
CHANGED
package/src/recommendations.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { execSync } from "child_process";
|
|
3
|
-
import { existsSync } from "fs";
|
|
4
|
-
import { join } from "path";
|
|
5
3
|
import { createInterface } from "readline";
|
|
6
|
-
import { runFixes } from "./fix.js";
|
|
7
4
|
|
|
8
5
|
const BENCHMARK_NAMES = {
|
|
9
6
|
cloudflare: "Cloudflare",
|
|
@@ -24,9 +21,11 @@ const RECOMMENDATION_MAP = [
|
|
|
24
21
|
key: "llms-txt",
|
|
25
22
|
label: "Create and link llms.txt",
|
|
26
23
|
detail:
|
|
27
|
-
'Create llms.txt with site overview, then add <link rel="llms-txt" href="/llms.txt"> to your HTML <head>',
|
|
24
|
+
'Create llms.txt with site overview, then add <link rel="llms-txt" href="/llms.txt"> to your HTML <head>. Also add a <link rel="alternate" type="text/markdown"> on each page that has a markdown version.',
|
|
28
25
|
match: (id) =>
|
|
29
|
-
/llms-txt|llms.*linked|llms-full.*linked|llms.*coverage/i.test(
|
|
26
|
+
/llms-txt|llms.*linked|llms-full.*linked|llms.*coverage|markdown link alternate/i.test(
|
|
27
|
+
id,
|
|
28
|
+
),
|
|
30
29
|
},
|
|
31
30
|
{
|
|
32
31
|
key: "agents-txt",
|
|
@@ -39,7 +38,7 @@ const RECOMMENDATION_MAP = [
|
|
|
39
38
|
key: "content-negotiation",
|
|
40
39
|
label: "Support content negotiation",
|
|
41
40
|
detail:
|
|
42
|
-
"Return markdown when requests include Accept: text/markdown. Add Vary: Accept header for
|
|
41
|
+
"Return markdown when requests include Accept: text/markdown. Return the content type the client actually preferred, respecting order and q-values per RFC 9110. Add Vary: Accept header for caching.",
|
|
43
42
|
match: (id) =>
|
|
44
43
|
/content.negotiation|agent ua.*markdown|accept.*markdown|accept.*text.*returns|accept.*json.*returns|preferred content.type|^vary/i.test(
|
|
45
44
|
id,
|
|
@@ -49,16 +48,18 @@ const RECOMMENDATION_MAP = [
|
|
|
49
48
|
key: "md-urls",
|
|
50
49
|
label: "Serve markdown at .md URLs",
|
|
51
50
|
detail:
|
|
52
|
-
"Make pages available at .md extensions (e.g. /docs/page.md returns markdown)",
|
|
51
|
+
"Make pages available at .md extensions (e.g. /docs/page.md returns markdown). Missing pages should return markdown 404, not HTML.",
|
|
53
52
|
match: (id) =>
|
|
54
|
-
/markdown.url.support|\.md.*url.*markdown|\.md url/i.test(
|
|
53
|
+
/markdown.url.support|\.md.*url.*markdown|\.md url|missing page.*markdown/i.test(
|
|
54
|
+
id,
|
|
55
|
+
),
|
|
55
56
|
},
|
|
56
57
|
{
|
|
57
58
|
key: "content-structure",
|
|
58
59
|
label: "Improve content structure for agents",
|
|
59
60
|
detail:
|
|
60
|
-
"Move nav/chrome below main content so agents find content earlier. Add frontmatter to markdown pages.",
|
|
61
|
-
match: (id) => /content.start.position
|
|
61
|
+
"Move nav/chrome below main content so agents find content earlier. Add YAML frontmatter (title, description, date) to markdown pages.",
|
|
62
|
+
match: (id) => /content.start.position|^frontmatter$/i.test(id),
|
|
62
63
|
},
|
|
63
64
|
{
|
|
64
65
|
key: "markdown-parity",
|
|
@@ -92,8 +93,9 @@ const RECOMMENDATION_MAP = [
|
|
|
92
93
|
label: "Add identity and discovery protocols",
|
|
93
94
|
detail:
|
|
94
95
|
"Implement WebFinger, DID Document, A2A Agent Card, and/or WebMCP manifest for agent discovery",
|
|
96
|
+
optional: true,
|
|
95
97
|
match: (id) =>
|
|
96
|
-
/webfinger|did document|nostr|at protocol|agent card|webmcp|apple app links|android asset links/i.test(
|
|
98
|
+
/webfinger|did document|nostr|at protocol|agent card.*published|agent card.*verified|webmcp|apple app links|android asset links/i.test(
|
|
97
99
|
id,
|
|
98
100
|
),
|
|
99
101
|
},
|
|
@@ -102,6 +104,7 @@ const RECOMMENDATION_MAP = [
|
|
|
102
104
|
label: "Declare payment information",
|
|
103
105
|
detail:
|
|
104
106
|
"Add x-payment-info header to paid API operations for agent billing awareness",
|
|
107
|
+
apiOnly: true,
|
|
105
108
|
match: (id) => /x-payment-info/i.test(id),
|
|
106
109
|
},
|
|
107
110
|
{
|
|
@@ -109,6 +112,7 @@ const RECOMMENDATION_MAP = [
|
|
|
109
112
|
label: "Add skill.md reference",
|
|
110
113
|
detail:
|
|
111
114
|
"Create a skill.md file describing your API's capabilities for agent consumption",
|
|
115
|
+
apiOnly: true,
|
|
112
116
|
match: (id) => /skill\.md/i.test(id),
|
|
113
117
|
},
|
|
114
118
|
{
|
|
@@ -116,26 +120,23 @@ const RECOMMENDATION_MAP = [
|
|
|
116
120
|
label: "Return rate limit headers",
|
|
117
121
|
detail:
|
|
118
122
|
"Add X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset headers to API responses",
|
|
123
|
+
apiOnly: true,
|
|
119
124
|
match: (id) => /rate limit/i.test(id),
|
|
120
125
|
},
|
|
121
126
|
{
|
|
122
127
|
key: "signatures",
|
|
123
|
-
label: "Publish signatures directory",
|
|
128
|
+
label: "Publish signatures directory and public keys",
|
|
124
129
|
detail:
|
|
125
|
-
"Serve /.well-known/http-message-signatures-directory
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
key: "members",
|
|
130
|
-
label: "Declare team members",
|
|
131
|
-
detail: "Add a members array to your signatures directory or agent card",
|
|
132
|
-
match: (id) => /members declared/i.test(id),
|
|
130
|
+
"Serve /.well-known/http-message-signatures-directory with agent identity and public keys (RFC 9421)",
|
|
131
|
+
optional: true,
|
|
132
|
+
match: (id) =>
|
|
133
|
+
/signatures directory|public keys|members declared/i.test(id),
|
|
133
134
|
},
|
|
134
135
|
{
|
|
135
136
|
key: "form-annotations",
|
|
136
137
|
label: "Add form tool annotations",
|
|
137
138
|
detail:
|
|
138
|
-
"Add tool-name and tool-description attributes to
|
|
139
|
+
"Add tool-name and tool-description attributes to <form> elements for browser AI agents",
|
|
139
140
|
match: (id) => /form tool annotations/i.test(id),
|
|
140
141
|
},
|
|
141
142
|
{
|
|
@@ -174,6 +175,8 @@ function buildRecommendations(result) {
|
|
|
174
175
|
key: rec.key,
|
|
175
176
|
label: rec.label,
|
|
176
177
|
detail: rec.detail,
|
|
178
|
+
apiOnly: rec.apiOnly || false,
|
|
179
|
+
optional: rec.optional || false,
|
|
177
180
|
benchmarks: new Set(),
|
|
178
181
|
checks: [],
|
|
179
182
|
});
|
|
@@ -255,38 +258,84 @@ function printRecommendations(recs) {
|
|
|
255
258
|
}
|
|
256
259
|
|
|
257
260
|
function generateAgentPrompt(result, recs) {
|
|
261
|
+
const core = recs.filter((r) => !r.apiOnly && !r.optional);
|
|
262
|
+
const optional = recs.filter((r) => r.optional && !r.apiOnly);
|
|
263
|
+
const apiOnly = recs.filter((r) => r.apiOnly);
|
|
264
|
+
|
|
258
265
|
const lines = [];
|
|
259
266
|
lines.push(
|
|
260
267
|
`My site ${result.url} scored ${result.averageScore}/100 on aeo-ready (AEO readiness scanner).`,
|
|
261
268
|
);
|
|
262
|
-
lines.push(
|
|
269
|
+
lines.push("Fix these issues to improve AI/agent discoverability.");
|
|
270
|
+
lines.push(
|
|
271
|
+
"Items are ordered by priority — issues flagged by multiple benchmarks are most important.\n",
|
|
272
|
+
);
|
|
263
273
|
|
|
264
|
-
let currentTier = null;
|
|
265
274
|
let num = 1;
|
|
266
275
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
276
|
+
if (core.length > 0) {
|
|
277
|
+
let currentTier = null;
|
|
278
|
+
for (const rec of core) {
|
|
279
|
+
const tier = tierLabel(rec.priority);
|
|
280
|
+
if (tier !== currentTier) {
|
|
281
|
+
currentTier = tier;
|
|
282
|
+
lines.push(`## ${tier} (${tierDescription(rec.priority)})`);
|
|
283
|
+
}
|
|
284
|
+
const benchmarkList = rec.benchmarks
|
|
285
|
+
.map((b) => BENCHMARK_NAMES[b])
|
|
286
|
+
.join(", ");
|
|
287
|
+
lines.push(`${num}. ${rec.label} [${benchmarkList}]`);
|
|
288
|
+
if (rec.detail) {
|
|
289
|
+
lines.push(` ${rec.detail}`);
|
|
290
|
+
}
|
|
291
|
+
num++;
|
|
272
292
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
lines.push(
|
|
277
|
-
|
|
278
|
-
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (optional.length > 0) {
|
|
296
|
+
lines.push("");
|
|
297
|
+
lines.push("## Optional (advanced agent discovery)");
|
|
298
|
+
for (const rec of optional) {
|
|
299
|
+
const benchmarkList = rec.benchmarks
|
|
300
|
+
.map((b) => BENCHMARK_NAMES[b])
|
|
301
|
+
.join(", ");
|
|
302
|
+
lines.push(`${num}. ${rec.label} [${benchmarkList}]`);
|
|
303
|
+
if (rec.detail) {
|
|
304
|
+
lines.push(` ${rec.detail}`);
|
|
305
|
+
}
|
|
306
|
+
num++;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (apiOnly.length > 0) {
|
|
311
|
+
lines.push("");
|
|
312
|
+
lines.push(
|
|
313
|
+
"## API-only (skip if this is not an API or developer platform)",
|
|
314
|
+
);
|
|
315
|
+
for (const rec of apiOnly) {
|
|
316
|
+
const benchmarkList = rec.benchmarks
|
|
317
|
+
.map((b) => BENCHMARK_NAMES[b])
|
|
318
|
+
.join(", ");
|
|
319
|
+
lines.push(`${num}. ${rec.label} [${benchmarkList}]`);
|
|
320
|
+
if (rec.detail) {
|
|
321
|
+
lines.push(` ${rec.detail}`);
|
|
322
|
+
}
|
|
323
|
+
num++;
|
|
279
324
|
}
|
|
280
|
-
num++;
|
|
281
325
|
}
|
|
282
326
|
|
|
283
327
|
lines.push("");
|
|
328
|
+
lines.push("## Instructions");
|
|
329
|
+
lines.push(
|
|
330
|
+
"- Fix what you can programmatically. For each fix, explain what you changed.",
|
|
331
|
+
);
|
|
284
332
|
lines.push(
|
|
285
|
-
"For
|
|
333
|
+
"- For anything that requires infrastructure/config changes you can't make,",
|
|
286
334
|
);
|
|
287
335
|
lines.push(
|
|
288
|
-
"with
|
|
336
|
+
" list it separately with step-by-step instructions I can follow manually.",
|
|
289
337
|
);
|
|
338
|
+
lines.push("- Skip API-only items if this site doesn't expose an API.");
|
|
290
339
|
lines.push("");
|
|
291
340
|
lines.push(`Re-scan after: npx aeo-ready scan ${result.url}`);
|
|
292
341
|
|
|
@@ -318,31 +367,6 @@ function copyToClipboard(text) {
|
|
|
318
367
|
}
|
|
319
368
|
}
|
|
320
369
|
|
|
321
|
-
function detectLocalProject(dir) {
|
|
322
|
-
if (dir) return dir;
|
|
323
|
-
const cwd = process.cwd();
|
|
324
|
-
const indicators = [
|
|
325
|
-
"package.json",
|
|
326
|
-
"index.html",
|
|
327
|
-
"next.config.js",
|
|
328
|
-
"next.config.mjs",
|
|
329
|
-
"next.config.ts",
|
|
330
|
-
"nuxt.config.ts",
|
|
331
|
-
"astro.config.mjs",
|
|
332
|
-
"vite.config.ts",
|
|
333
|
-
"vite.config.js",
|
|
334
|
-
"gatsby-config.js",
|
|
335
|
-
"angular.json",
|
|
336
|
-
"svelte.config.js",
|
|
337
|
-
"remix.config.js",
|
|
338
|
-
"public",
|
|
339
|
-
];
|
|
340
|
-
for (const f of indicators) {
|
|
341
|
-
if (existsSync(join(cwd, f))) return cwd;
|
|
342
|
-
}
|
|
343
|
-
return null;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
370
|
function ask(question) {
|
|
347
371
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
348
372
|
return new Promise((resolve) => {
|
|
@@ -353,7 +377,7 @@ function ask(question) {
|
|
|
353
377
|
});
|
|
354
378
|
}
|
|
355
379
|
|
|
356
|
-
export async function showRecommendations(result
|
|
380
|
+
export async function showRecommendations(result) {
|
|
357
381
|
const recs = buildRecommendations(result);
|
|
358
382
|
if (recs.length === 0) return;
|
|
359
383
|
|
|
@@ -365,14 +389,9 @@ export async function showRecommendations(result, dir) {
|
|
|
365
389
|
|
|
366
390
|
console.log(`\n ${chalk.bold(summary)}\n`);
|
|
367
391
|
|
|
368
|
-
const localDir = detectLocalProject(dir);
|
|
369
|
-
|
|
370
392
|
const options = [];
|
|
371
|
-
options.push(["v", "View
|
|
393
|
+
options.push(["v", "View"]);
|
|
372
394
|
options.push(["c", "Copy prompt for AI agent"]);
|
|
373
|
-
if (localDir) {
|
|
374
|
-
options.push(["f", "Fix now"]);
|
|
375
|
-
}
|
|
376
395
|
options.push(["q", "Done"]);
|
|
377
396
|
|
|
378
397
|
const optStr = options
|
|
@@ -396,8 +415,6 @@ export async function showRecommendations(result, dir) {
|
|
|
396
415
|
console.log(prompt);
|
|
397
416
|
console.log("");
|
|
398
417
|
}
|
|
399
|
-
} else if (answer === "f" && localDir) {
|
|
400
|
-
await runFixes(result, localDir);
|
|
401
418
|
break;
|
|
402
419
|
} else {
|
|
403
420
|
break;
|
package/src/scan.js
CHANGED