aeo-ready 1.1.0 → 1.2.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.
@@ -1,73 +0,0 @@
1
- import { existsSync, readFileSync } from "fs";
2
- import { join } from "path";
3
-
4
- export function generateAgentsJson(check, scanResult, dir) {
5
- const { siteType, target } = scanResult;
6
-
7
- const hostname = extractHostname(target);
8
- const name = inferName(dir, hostname);
9
-
10
- const manifest = {
11
- schema_version: "1.0",
12
- name,
13
- description: "",
14
- url: target || "",
15
- site_type: siteType,
16
- interfaces: {
17
- human: "/",
18
- llm: "/llms.txt",
19
- structured: "/.well-known/agent.json",
20
- },
21
- capabilities: getCapabilities(siteType),
22
- contact: {},
23
- };
24
-
25
- if (siteType === "saas" || siteType === "api") {
26
- manifest.api_base = "";
27
- manifest.openapi = "/openapi.json";
28
- manifest.protocols = ["rest"];
29
- }
30
-
31
- return {
32
- file: ".well-known/agent.json",
33
- description: `Agent manifest for ${siteType} site — fill in description and contact`,
34
- draft: true,
35
- content: JSON.stringify(manifest, null, 2) + "\n",
36
- };
37
- }
38
-
39
- function getCapabilities(siteType) {
40
- switch (siteType) {
41
- case "saas":
42
- return ["pricing-lookup", "api-integration", "trial-signup"];
43
- case "api":
44
- return ["api-integration", "sdk-install", "docs-lookup"];
45
- case "content":
46
- return ["content-search", "topic-lookup", "citation"];
47
- case "personal":
48
- return ["contact", "expertise-lookup", "project-listing"];
49
- default:
50
- return [];
51
- }
52
- }
53
-
54
- function extractHostname(target) {
55
- if (!target) return null;
56
- try {
57
- return new URL(target).hostname.replace("www.", "");
58
- } catch {
59
- return null;
60
- }
61
- }
62
-
63
- function inferName(dir, hostname) {
64
- const pkgPath = join(dir, "package.json");
65
- if (existsSync(pkgPath)) {
66
- try {
67
- const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
68
- if (pkg.name) return pkg.name;
69
- } catch {}
70
- }
71
- if (hostname) return hostname.split(".")[0];
72
- return "";
73
- }
@@ -1,85 +0,0 @@
1
- import { existsSync, readFileSync, readdirSync } from "fs";
2
- import { join } from "path";
3
-
4
- export function generateAgentsMd(check, scanResult, dir) {
5
- const { siteType, target } = scanResult;
6
- const name = inferName(dir);
7
- const structure = inferStructure(dir);
8
-
9
- const content = `# ${name || "[Project Name]"}
10
-
11
- > [One sentence: what this project does]
12
-
13
- ## Project structure
14
-
15
- ${structure || "```\n[Add key directories and their purpose]\n```"}
16
-
17
- ## Key files
18
-
19
- - Entry point: [path]
20
- - Config: [path]
21
- - API routes: [path]
22
-
23
- ## Development
24
-
25
- \`\`\`bash
26
- [install command]
27
- [run command]
28
- \`\`\`
29
-
30
- ## Conventions
31
-
32
- - [Language/framework convention 1]
33
- - [Language/framework convention 2]
34
-
35
- ## Constraints
36
-
37
- - [Rate limits, auth requirements, etc]
38
- - [External dependencies]
39
- `;
40
-
41
- return {
42
- file: "AGENTS.md",
43
- description: "Draft AGENTS.md — REVIEW AND EDIT with real project details",
44
- draft: true,
45
- content,
46
- };
47
- }
48
-
49
- function inferName(dir) {
50
- const pkgPath = join(dir, "package.json");
51
- if (existsSync(pkgPath)) {
52
- try {
53
- return JSON.parse(readFileSync(pkgPath, "utf8")).name || "";
54
- } catch {}
55
- }
56
- const pomPath = join(dir, "pom.xml");
57
- if (existsSync(pomPath)) {
58
- const pom = readFileSync(pomPath, "utf8");
59
- const match = pom.match(/<artifactId>([^<]+)<\/artifactId>/);
60
- if (match) return match[1];
61
- }
62
- return "";
63
- }
64
-
65
- function inferStructure(dir) {
66
- try {
67
- const entries = readdirSync(dir, { withFileTypes: true });
68
- const dirs = entries
69
- .filter(
70
- (e) =>
71
- e.isDirectory() &&
72
- !e.name.startsWith(".") &&
73
- e.name !== "node_modules",
74
- )
75
- .map((e) => e.name)
76
- .slice(0, 10);
77
-
78
- if (dirs.length === 0) return null;
79
-
80
- const lines = dirs.map((d) => `${d}/`);
81
- return "```\n" + lines.join("\n") + "\n```";
82
- } catch {
83
- return null;
84
- }
85
- }
@@ -1,166 +0,0 @@
1
- import { existsSync, readFileSync } from "fs";
2
- import { join } from "path";
3
-
4
- export function generateLlmsTxt(check, scanResult, dir) {
5
- const { siteType, target } = scanResult;
6
- const hostname = extractHostname(target);
7
- const name = inferName(dir, hostname);
8
-
9
- const content = generateByType(siteType, name, target);
10
-
11
- return {
12
- file: "llms.txt",
13
- description: `Draft llms.txt for ${siteType} site — REVIEW AND EDIT before publishing`,
14
- draft: true,
15
- content,
16
- };
17
- }
18
-
19
- function generateByType(siteType, name, target) {
20
- const base = target || "https://example.com";
21
-
22
- switch (siteType) {
23
- case "saas":
24
- return `# ${name || "[Company Name]"}
25
-
26
- > [One sentence: what problem you solve and for whom]
27
-
28
- ## What it does
29
- - [Core capability 1]
30
- - [Core capability 2]
31
- - [Core capability 3]
32
-
33
- ## Pricing
34
- - [Plan 1]: $X/mo — [what's included]
35
- - [Plan 2]: $X/mo — [what's included]
36
- - Details: ${base}/pricing
37
-
38
- ## Integration
39
- - API docs: ${base}/docs
40
- - OpenAPI spec: ${base}/openapi.json
41
- - Auth: [API key / OAuth / etc]
42
-
43
- ## Get started
44
- 1. [Signup step]
45
- 2. [First API call or setup]
46
- 3. [See results]
47
-
48
- ## Links
49
- - Documentation: ${base}/docs
50
- - Changelog: ${base}/changelog
51
- - Status: ${base}/status
52
- `;
53
-
54
- case "api":
55
- return `# ${name || "[Tool Name]"}
56
-
57
- > [One sentence: what this tool does]
58
-
59
- ## Quick start
60
- \`\`\`bash
61
- [install command]
62
- \`\`\`
63
-
64
- ## Key concepts
65
- - [Concept 1]: [one-line explanation]
66
- - [Concept 2]: [one-line explanation]
67
-
68
- ## API reference
69
- - Base URL: ${base}/api/v1
70
- - Auth: [method]
71
- - OpenAPI: ${base}/openapi.json
72
-
73
- ## SDKs
74
- - Node.js: \`npm install [package]\`
75
- - Python: \`pip install [package]\`
76
-
77
- ## Links
78
- - Full docs: ${base}/docs
79
- - Examples: ${base}/examples
80
- - Changelog: ${base}/changelog
81
- `;
82
-
83
- case "personal":
84
- return `# ${name || "[Your Name]"}
85
-
86
- > [Role/title] — [what you're known for]
87
-
88
- ## Expertise
89
- - [Area 1]
90
- - [Area 2]
91
- - [Area 3]
92
-
93
- ## Notable work
94
- - [Project 1]: [one-line description] — [url]
95
- - [Project 2]: [one-line description] — [url]
96
-
97
- ## Writing & speaking
98
- - [Topic you cover]
99
- - [Where to find your content]
100
-
101
- ## Contact
102
- - Email: [email]
103
- - LinkedIn: [url]
104
- - Available for: [consulting / speaking / hiring / etc]
105
- `;
106
-
107
- case "content":
108
- return `# ${name || "[Publication Name]"}
109
-
110
- > [What topics you cover and your angle/expertise]
111
-
112
- ## Topics
113
- - [Topic 1]: [what you cover, how many pieces]
114
- - [Topic 2]: [what you cover, how many pieces]
115
-
116
- ## Best starting points
117
- - [Most important article]: ${base}/[path]
118
- - [Second article]: ${base}/[path]
119
- - [Third article]: ${base}/[path]
120
-
121
- ## About
122
- - Author(s): [who writes]
123
- - Cadence: [how often you publish]
124
- - Angle: [what makes your coverage unique]
125
-
126
- ## Links
127
- - All articles: ${base}/blog
128
- - RSS: ${base}/feed.xml
129
- - Newsletter: ${base}/subscribe
130
- `;
131
-
132
- default:
133
- return `# ${name || "[Site Name]"}
134
-
135
- > [One sentence: what this site is and who it's for]
136
-
137
- ## What you'll find here
138
- - [Section 1]: [description]
139
- - [Section 2]: [description]
140
-
141
- ## Links
142
- - Homepage: ${base}
143
- `;
144
- }
145
- }
146
-
147
- function extractHostname(target) {
148
- if (!target) return null;
149
- try {
150
- return new URL(target).hostname.replace("www.", "");
151
- } catch {
152
- return null;
153
- }
154
- }
155
-
156
- function inferName(dir, hostname) {
157
- const pkgPath = join(dir, "package.json");
158
- if (existsSync(pkgPath)) {
159
- try {
160
- const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
161
- if (pkg.name) return pkg.name;
162
- } catch {}
163
- }
164
- if (hostname) return hostname.split(".")[0];
165
- return "";
166
- }
@@ -1,64 +0,0 @@
1
- import { existsSync, readFileSync } from "fs";
2
- import { join } from "path";
3
-
4
- const AI_CRAWLERS = [
5
- "GPTBot",
6
- "OAI-SearchBot",
7
- "ClaudeBot",
8
- "Claude-User",
9
- "Claude-SearchBot",
10
- "Google-Extended",
11
- "PerplexityBot",
12
- "Meta-ExternalAgent",
13
- "CCBot",
14
- ];
15
-
16
- export function generateRobotsTxt(check, scanResult, dir) {
17
- const filePath = join(dir, "robots.txt");
18
- const exists = existsSync(filePath);
19
-
20
- if (exists) {
21
- return {
22
- file: "robots.txt",
23
- description: "Add missing AI crawler rules",
24
- draft: false,
25
- merge: mergeRobotsTxt,
26
- content: generateCrawlerBlock(),
27
- };
28
- }
29
-
30
- return {
31
- file: "robots.txt",
32
- description: "Allow all AI crawlers for maximum agent discoverability",
33
- draft: false,
34
- content: generateFullRobotsTxt(),
35
- };
36
- }
37
-
38
- function mergeRobotsTxt(existing, newContent) {
39
- const presentBots = AI_CRAWLERS.filter((bot) => existing.includes(bot));
40
- const missingBots = AI_CRAWLERS.filter((bot) => !existing.includes(bot));
41
-
42
- if (missingBots.length === 0) return existing;
43
-
44
- const additions = missingBots
45
- .map((bot) => `User-agent: ${bot}\nAllow: /\n`)
46
- .join("\n");
47
-
48
- return (
49
- existing.trimEnd() + "\n\n# AI crawlers (added by agent-web)\n" + additions
50
- );
51
- }
52
-
53
- function generateCrawlerBlock() {
54
- return AI_CRAWLERS.map((bot) => `User-agent: ${bot}\nAllow: /\n`).join("\n");
55
- }
56
-
57
- function generateFullRobotsTxt() {
58
- let content = "User-agent: *\nAllow: /\n\n";
59
- content += "# AI crawlers — explicit allow for agent discoverability\n";
60
- content += AI_CRAWLERS.map((bot) => `User-agent: ${bot}\nAllow: /\n`).join(
61
- "\n",
62
- );
63
- return content;
64
- }
package/src/fix/index.js DELETED
@@ -1,177 +0,0 @@
1
- import chalk from "chalk";
2
- import { writeFileSync, existsSync, readFileSync, mkdirSync } from "fs";
3
- import { join, dirname } from "path";
4
- import { generateRobotsTxt } from "./generators/robots-txt.js";
5
- import { generateAgentsJson } from "./generators/agents-json.js";
6
- import { generateLlmsTxt } from "./generators/llms-txt.js";
7
- import { generateAgentsMd } from "./generators/agents-md.js";
8
- import { generateDashboard } from "../dashboard/generate.js";
9
-
10
- export async function fix(beforeResult, dir, rescanFn) {
11
- const failedChecks = getAllFailedChecks(beforeResult);
12
- const applied = [];
13
- const manual = [];
14
-
15
- for (const check of failedChecks) {
16
- const result = await generateFix(check, beforeResult, dir);
17
- if (result) {
18
- if (result.draft) {
19
- manual.push(result);
20
- } else {
21
- writeFix(result, dir);
22
- applied.push(result);
23
- }
24
- } else {
25
- manual.push({ description: check.fix, name: check.name });
26
- }
27
- }
28
-
29
- const afterResult = rescanFn ? await rescanFn() : null;
30
-
31
- printSummary(beforeResult, afterResult, applied, manual);
32
-
33
- if (afterResult && dir) {
34
- const dashPath = await generateDashboard(afterResult, dir, {
35
- beforeResult,
36
- });
37
- console.log(chalk.dim(` Dashboard: ${dashPath}\n`));
38
- }
39
-
40
- return { applied, manual, afterResult };
41
- }
42
-
43
- function printSummary(before, after, applied, manual) {
44
- const beforeScore = before.score;
45
- const afterScore = after ? after.score : null;
46
-
47
- console.log(chalk.bold("\n aeo-ready --fix\n"));
48
-
49
- if (afterScore !== null) {
50
- const delta = afterScore - beforeScore;
51
- const deltaStr =
52
- delta > 0
53
- ? chalk.green(` (+${delta})`)
54
- : delta < 0
55
- ? chalk.red(` (${delta})`)
56
- : "";
57
- const beforeGrade = gradeColor(before.grade)(`${beforeScore}`);
58
- const afterGrade = gradeColor(after.grade)(`${afterScore}`);
59
- console.log(
60
- ` ${beforeGrade}${chalk.dim("/100")} ${chalk.dim("->")} ${afterGrade}${chalk.dim("/100")}${deltaStr}\n`,
61
- );
62
-
63
- const bA = before.agentReadiness.score;
64
- const bV = before.aiVisibility.score;
65
- const aA = after.agentReadiness.score;
66
- const aV = after.aiVisibility.score;
67
- const dA = aA - bA;
68
- const dV = aV - bV;
69
- const fmtDelta = (d) =>
70
- d > 0 ? chalk.green(`+${d}`) : d < 0 ? chalk.red(`${d}`) : chalk.dim("—");
71
-
72
- console.log(
73
- chalk.dim(" Agent Readiness ") +
74
- `${bA} -> ${aA} ${fmtDelta(dA)}` +
75
- chalk.dim(" can agents find, read, and use your site?"),
76
- );
77
- console.log(
78
- chalk.dim(" AI Visibility ") +
79
- `${bV} -> ${aV} ${fmtDelta(dV)}` +
80
- chalk.dim(" how accurately do AI engines describe you?"),
81
- );
82
-
83
- if (bA < 25) {
84
- console.log(
85
- chalk.dim(
86
- "\n Agent Readiness is low — AI engines can't cite what they can't read.",
87
- ),
88
- );
89
- console.log(chalk.dim(" Fix the agent side first. Visibility follows."));
90
- }
91
- } else {
92
- console.log(
93
- ` ${chalk.dim("Score:")} ${beforeScore}/100 (${before.grade})\n`,
94
- );
95
- }
96
-
97
- if (applied.length > 0) {
98
- console.log(chalk.bold("\n We fixed:"));
99
- for (const fix of applied) {
100
- console.log(
101
- chalk.green(` + ${fix.file}`) + chalk.dim(` — ${fix.description}`),
102
- );
103
- }
104
- }
105
-
106
- const humanTasks = manual.filter((m) => m.description);
107
- if (humanTasks.length > 0) {
108
- console.log(chalk.bold("\n You still need to:"));
109
- for (const task of humanTasks.slice(0, 6)) {
110
- console.log(chalk.dim(` - ${task.description}`));
111
- }
112
- if (humanTasks.length > 6) {
113
- console.log(chalk.dim(` ... and ${humanTasks.length - 6} more`));
114
- }
115
- }
116
-
117
- console.log("");
118
- }
119
-
120
- function writeFix(fix, dir) {
121
- const filePath = join(dir, fix.file);
122
- const fileDir = dirname(filePath);
123
- if (!existsSync(fileDir)) mkdirSync(fileDir, { recursive: true });
124
-
125
- if (existsSync(filePath) && fix.merge) {
126
- const existing = readFileSync(filePath, "utf8");
127
- const merged = fix.merge(existing, fix.content);
128
- writeFileSync(filePath, merged);
129
- } else {
130
- writeFileSync(filePath, fix.content);
131
- }
132
- }
133
-
134
- async function generateFix(check, scanResult, dir) {
135
- switch (check.name) {
136
- case "robots.txt AI crawlers":
137
- return generateRobotsTxt(check, scanResult, dir);
138
- case "agents.json manifest":
139
- return generateAgentsJson(check, scanResult, dir);
140
- case "AGENTS.md / CLAUDE.md":
141
- return generateAgentsMd(check, scanResult, dir);
142
- case "llms.txt":
143
- return generateLlmsTxt(check, scanResult, dir);
144
- default:
145
- return null;
146
- }
147
- }
148
-
149
- function getAllFailedChecks(scanResult) {
150
- const checks = [];
151
- for (const scorecard of [
152
- scanResult.agentReadiness,
153
- scanResult.aiVisibility,
154
- ]) {
155
- for (const cat of Object.values(scorecard.categories)) {
156
- for (const check of cat.checks) {
157
- if (!check.passed) {
158
- checks.push(check);
159
- }
160
- }
161
- }
162
- }
163
- return checks;
164
- }
165
-
166
- function gradeColor(grade) {
167
- switch (grade) {
168
- case "A":
169
- return chalk.green;
170
- case "B":
171
- return chalk.cyan;
172
- case "C":
173
- return chalk.yellow;
174
- default:
175
- return chalk.red;
176
- }
177
- }