aeo-ready 1.7.1 → 1.7.3
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 +49 -98
- 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",
|
|
@@ -93,7 +94,7 @@ const RECOMMENDATION_MAP = [
|
|
|
93
94
|
detail:
|
|
94
95
|
"Implement WebFinger, DID Document, A2A Agent Card, and/or WebMCP manifest for agent discovery",
|
|
95
96
|
match: (id) =>
|
|
96
|
-
/webfinger|did document|nostr|at protocol|agent card|webmcp|apple app links|android asset links/i.test(
|
|
97
|
+
/webfinger|did document|nostr|at protocol|agent card.*published|agent card.*verified|webmcp|apple app links|android asset links/i.test(
|
|
97
98
|
id,
|
|
98
99
|
),
|
|
99
100
|
},
|
|
@@ -120,22 +121,17 @@ const RECOMMENDATION_MAP = [
|
|
|
120
121
|
},
|
|
121
122
|
{
|
|
122
123
|
key: "signatures",
|
|
123
|
-
label: "Publish signatures directory",
|
|
124
|
+
label: "Publish signatures directory and public keys",
|
|
124
125
|
detail:
|
|
125
|
-
"Serve /.well-known/http-message-signatures-directory
|
|
126
|
-
match: (id) =>
|
|
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),
|
|
126
|
+
"Serve /.well-known/http-message-signatures-directory with agent identity and public keys (RFC 9421)",
|
|
127
|
+
match: (id) =>
|
|
128
|
+
/signatures directory|public keys|members declared/i.test(id),
|
|
133
129
|
},
|
|
134
130
|
{
|
|
135
131
|
key: "form-annotations",
|
|
136
132
|
label: "Add form tool annotations",
|
|
137
133
|
detail:
|
|
138
|
-
"Add tool-name and tool-description attributes to
|
|
134
|
+
"Add tool-name and tool-description attributes to <form> elements for browser AI agents",
|
|
139
135
|
match: (id) => /form tool annotations/i.test(id),
|
|
140
136
|
},
|
|
141
137
|
{
|
|
@@ -225,41 +221,21 @@ function tierDescription(count) {
|
|
|
225
221
|
return "flagged by 1 benchmark";
|
|
226
222
|
}
|
|
227
223
|
|
|
228
|
-
function printRecommendations(recs) {
|
|
229
|
-
let currentTier = null;
|
|
230
|
-
let num = 1;
|
|
231
|
-
|
|
232
|
-
console.log("");
|
|
233
|
-
for (const rec of recs) {
|
|
234
|
-
const tier = tierLabel(rec.priority);
|
|
235
|
-
if (tier !== currentTier) {
|
|
236
|
-
currentTier = tier;
|
|
237
|
-
const color =
|
|
238
|
-
rec.priority >= 3
|
|
239
|
-
? chalk.red
|
|
240
|
-
: rec.priority >= 2
|
|
241
|
-
? chalk.yellow
|
|
242
|
-
: chalk.dim;
|
|
243
|
-
console.log(
|
|
244
|
-
` ${color.bold(tier)} ${chalk.dim(`(${tierDescription(rec.priority)})`)}`,
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
const benchmarkList = rec.benchmarks
|
|
248
|
-
.map((b) => BENCHMARK_NAMES[b])
|
|
249
|
-
.join(" · ");
|
|
250
|
-
console.log(` ${chalk.dim(`${num}.`)} ${rec.label}`);
|
|
251
|
-
console.log(` ${chalk.dim(benchmarkList)}`);
|
|
252
|
-
num++;
|
|
253
|
-
}
|
|
254
|
-
console.log("");
|
|
255
|
-
}
|
|
256
|
-
|
|
257
224
|
function generateAgentPrompt(result, recs) {
|
|
258
225
|
const lines = [];
|
|
259
226
|
lines.push(
|
|
260
227
|
`My site ${result.url} scored ${result.averageScore}/100 on aeo-ready (AEO readiness scanner).`,
|
|
261
228
|
);
|
|
262
|
-
lines.push(
|
|
229
|
+
lines.push("Fix these issues to improve AI/agent discoverability.");
|
|
230
|
+
lines.push(
|
|
231
|
+
"Items are ordered by priority — issues flagged by multiple benchmarks matter most.",
|
|
232
|
+
);
|
|
233
|
+
lines.push(
|
|
234
|
+
"This scan covers benchmarks across different site types (content sites, APIs, developer platforms).",
|
|
235
|
+
);
|
|
236
|
+
lines.push(
|
|
237
|
+
"Not every recommendation may apply to this site — review each and prioritize accordingly.\n",
|
|
238
|
+
);
|
|
263
239
|
|
|
264
240
|
let currentTier = null;
|
|
265
241
|
let num = 1;
|
|
@@ -281,11 +257,15 @@ function generateAgentPrompt(result, recs) {
|
|
|
281
257
|
}
|
|
282
258
|
|
|
283
259
|
lines.push("");
|
|
260
|
+
lines.push("## Instructions");
|
|
284
261
|
lines.push(
|
|
285
|
-
"
|
|
262
|
+
"- Fix what you can programmatically. For each fix, explain what you changed.",
|
|
286
263
|
);
|
|
287
264
|
lines.push(
|
|
288
|
-
"
|
|
265
|
+
"- For anything that requires infrastructure or configuration changes you can't make,",
|
|
266
|
+
);
|
|
267
|
+
lines.push(
|
|
268
|
+
" list it separately with clear step-by-step instructions I can follow manually.",
|
|
289
269
|
);
|
|
290
270
|
lines.push("");
|
|
291
271
|
lines.push(`Re-scan after: npx aeo-ready scan ${result.url}`);
|
|
@@ -318,31 +298,6 @@ function copyToClipboard(text) {
|
|
|
318
298
|
}
|
|
319
299
|
}
|
|
320
300
|
|
|
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
301
|
function ask(question) {
|
|
347
302
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
348
303
|
return new Promise((resolve) => {
|
|
@@ -353,7 +308,7 @@ function ask(question) {
|
|
|
353
308
|
});
|
|
354
309
|
}
|
|
355
310
|
|
|
356
|
-
export async function showRecommendations(result
|
|
311
|
+
export async function showRecommendations(result) {
|
|
357
312
|
const recs = buildRecommendations(result);
|
|
358
313
|
if (recs.length === 0) return;
|
|
359
314
|
|
|
@@ -365,17 +320,13 @@ export async function showRecommendations(result, dir) {
|
|
|
365
320
|
|
|
366
321
|
console.log(`\n ${chalk.bold(summary)}\n`);
|
|
367
322
|
|
|
368
|
-
const
|
|
323
|
+
const prompt = generateAgentPrompt(result, recs);
|
|
369
324
|
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
}
|
|
376
|
-
options.push(["q", "Done"]);
|
|
377
|
-
|
|
378
|
-
const optStr = options
|
|
325
|
+
const optStr = [
|
|
326
|
+
["v", "View prompt"],
|
|
327
|
+
["c", "Copy prompt"],
|
|
328
|
+
["q", "Done"],
|
|
329
|
+
]
|
|
379
330
|
.map(([key, label]) => `${chalk.bold(`[${key}]`)} ${label}`)
|
|
380
331
|
.join(" ");
|
|
381
332
|
|
|
@@ -383,21 +334,21 @@ export async function showRecommendations(result, dir) {
|
|
|
383
334
|
const answer = await ask(` ${optStr} `);
|
|
384
335
|
|
|
385
336
|
if (answer === "v") {
|
|
386
|
-
|
|
337
|
+
console.log("");
|
|
338
|
+
console.log(
|
|
339
|
+
prompt
|
|
340
|
+
.split("\n")
|
|
341
|
+
.map((line) => ` ${line}`)
|
|
342
|
+
.join("\n"),
|
|
343
|
+
);
|
|
344
|
+
console.log("");
|
|
387
345
|
} else if (answer === "c") {
|
|
388
|
-
const prompt = generateAgentPrompt(result, recs);
|
|
389
346
|
const copied = copyToClipboard(prompt);
|
|
390
347
|
if (copied) {
|
|
391
348
|
console.log(chalk.green("\n Copied to clipboard.\n"));
|
|
392
349
|
} else {
|
|
393
|
-
console.log(
|
|
394
|
-
chalk.dim("\n Could not copy — here are the instructions:\n"),
|
|
395
|
-
);
|
|
396
|
-
console.log(prompt);
|
|
397
|
-
console.log("");
|
|
350
|
+
console.log(chalk.dim("\n Could not copy to clipboard.\n"));
|
|
398
351
|
}
|
|
399
|
-
} else if (answer === "f" && localDir) {
|
|
400
|
-
await runFixes(result, localDir);
|
|
401
352
|
break;
|
|
402
353
|
} else {
|
|
403
354
|
break;
|
package/src/scan.js
CHANGED