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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aeo-ready",
3
- "version": "1.7.1",
3
+ "version": "1.7.3",
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": {
@@ -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(id),
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 proper caching.",
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(id),
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|frontmatter/i.test(id),
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 for request verification",
126
- match: (id) => /signatures directory|public keys/i.test(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 forms for agent understanding",
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(`Fix these issues to improve AI/agent discoverability:\n`);
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
- "For any issues that can't be fixed programmatically, outline them for me",
262
+ "- Fix what you can programmatically. For each fix, explain what you changed.",
286
263
  );
287
264
  lines.push(
288
- "with clear step-by-step instructions on how to address them manually.",
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, dir) {
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 localDir = detectLocalProject(dir);
323
+ const prompt = generateAgentPrompt(result, recs);
369
324
 
370
- const options = [];
371
- options.push(["v", "View recommendations"]);
372
- options.push(["c", "Copy prompt for AI agent"]);
373
- if (localDir) {
374
- options.push(["f", "Fix now"]);
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
- printRecommendations(recs);
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
@@ -37,7 +37,7 @@ export async function scan(opts) {
37
37
  await saveResult(result, baseDir);
38
38
 
39
39
  if (!json && averageScore < 100 && process.stdin.isTTY) {
40
- await showRecommendations(result, dir);
40
+ await showRecommendations(result);
41
41
  }
42
42
 
43
43
  return result;