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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aeo-ready",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
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",
@@ -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 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),
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 forms for agent understanding",
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(`Fix these issues to improve AI/agent discoverability:\n`);
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
- for (const rec of recs) {
268
- const tier = tierLabel(rec.priority);
269
- if (tier !== currentTier) {
270
- currentTier = tier;
271
- lines.push(`## ${tier} (${tierDescription(rec.priority)})`);
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
- const benchmarkList = rec.benchmarks
274
- .map((b) => BENCHMARK_NAMES[b])
275
- .join(", ");
276
- lines.push(`${num}. ${rec.label} [${benchmarkList}]`);
277
- if (rec.detail) {
278
- lines.push(` ${rec.detail}`);
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 any issues that can't be fixed programmatically, outline them for me",
333
+ "- For anything that requires infrastructure/config changes you can't make,",
286
334
  );
287
335
  lines.push(
288
- "with clear step-by-step instructions on how to address them manually.",
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, dir) {
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 recommendations"]);
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
@@ -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;