@automatelab/ai-seo-mcp 0.1.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.
- package/LICENSE +21 -0
- package/README.md +217 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +145 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/config.d.ts +8 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +11 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/crawlers.json +6 -0
- package/dist/lib/entities.d.ts +17 -0
- package/dist/lib/entities.d.ts.map +1 -0
- package/dist/lib/entities.js +113 -0
- package/dist/lib/entities.js.map +1 -0
- package/dist/lib/fetch.d.ts +33 -0
- package/dist/lib/fetch.d.ts.map +1 -0
- package/dist/lib/fetch.js +190 -0
- package/dist/lib/fetch.js.map +1 -0
- package/dist/lib/html.d.ts +45 -0
- package/dist/lib/html.d.ts.map +1 -0
- package/dist/lib/html.js +166 -0
- package/dist/lib/html.js.map +1 -0
- package/dist/lib/llms-txt.d.ts +25 -0
- package/dist/lib/llms-txt.d.ts.map +1 -0
- package/dist/lib/llms-txt.js +149 -0
- package/dist/lib/llms-txt.js.map +1 -0
- package/dist/lib/robots.d.ts +26 -0
- package/dist/lib/robots.d.ts.map +1 -0
- package/dist/lib/robots.js +83 -0
- package/dist/lib/robots.js.map +1 -0
- package/dist/lib/schema-types.json +56 -0
- package/dist/lib/schema.d.ts +20 -0
- package/dist/lib/schema.d.ts.map +1 -0
- package/dist/lib/schema.js +311 -0
- package/dist/lib/schema.js.map +1 -0
- package/dist/lib/score.d.ts +32 -0
- package/dist/lib/score.d.ts.map +1 -0
- package/dist/lib/score.js +62 -0
- package/dist/lib/score.js.map +1 -0
- package/dist/tools/audit-canonical.d.ts +26 -0
- package/dist/tools/audit-canonical.d.ts.map +1 -0
- package/dist/tools/audit-canonical.js +122 -0
- package/dist/tools/audit-canonical.js.map +1 -0
- package/dist/tools/audit-page.d.ts +31 -0
- package/dist/tools/audit-page.d.ts.map +1 -0
- package/dist/tools/audit-page.js +221 -0
- package/dist/tools/audit-page.js.map +1 -0
- package/dist/tools/audit-schema.d.ts +34 -0
- package/dist/tools/audit-schema.d.ts.map +1 -0
- package/dist/tools/audit-schema.js +65 -0
- package/dist/tools/audit-schema.js.map +1 -0
- package/dist/tools/check-robots.d.ts +22 -0
- package/dist/tools/check-robots.d.ts.map +1 -0
- package/dist/tools/check-robots.js +142 -0
- package/dist/tools/check-robots.js.map +1 -0
- package/dist/tools/check-sitemap.d.ts +27 -0
- package/dist/tools/check-sitemap.d.ts.map +1 -0
- package/dist/tools/check-sitemap.js +214 -0
- package/dist/tools/check-sitemap.js.map +1 -0
- package/dist/tools/check-technical.d.ts +50 -0
- package/dist/tools/check-technical.d.ts.map +1 -0
- package/dist/tools/check-technical.js +209 -0
- package/dist/tools/check-technical.js.map +1 -0
- package/dist/tools/extract-entities.d.ts +35 -0
- package/dist/tools/extract-entities.d.ts.map +1 -0
- package/dist/tools/extract-entities.js +69 -0
- package/dist/tools/extract-entities.js.map +1 -0
- package/dist/tools/generate-llms-txt.d.ts +32 -0
- package/dist/tools/generate-llms-txt.d.ts.map +1 -0
- package/dist/tools/generate-llms-txt.js +157 -0
- package/dist/tools/generate-llms-txt.js.map +1 -0
- package/dist/tools/rewrite-for-aeo.d.ts +50 -0
- package/dist/tools/rewrite-for-aeo.d.ts.map +1 -0
- package/dist/tools/rewrite-for-aeo.js +119 -0
- package/dist/tools/rewrite-for-aeo.js.map +1 -0
- package/dist/tools/rewrite-for-geo.d.ts +51 -0
- package/dist/tools/rewrite-for-geo.d.ts.map +1 -0
- package/dist/tools/rewrite-for-geo.js +115 -0
- package/dist/tools/rewrite-for-geo.js.map +1 -0
- package/dist/tools/score-ai-overview-eligibility.d.ts +34 -0
- package/dist/tools/score-ai-overview-eligibility.d.ts.map +1 -0
- package/dist/tools/score-ai-overview-eligibility.js +186 -0
- package/dist/tools/score-ai-overview-eligibility.js.map +1 -0
- package/dist/tools/score-citation-worthiness.d.ts +51 -0
- package/dist/tools/score-citation-worthiness.d.ts.map +1 -0
- package/dist/tools/score-citation-worthiness.js +145 -0
- package/dist/tools/score-citation-worthiness.js.map +1 -0
- package/dist/tools/validate-llms-txt.d.ts +33 -0
- package/dist/tools/validate-llms-txt.d.ts.map +1 -0
- package/dist/tools/validate-llms-txt.js +62 -0
- package/dist/tools/validate-llms-txt.js.map +1 -0
- package/dist/types.d.ts +48 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -0
- package/src/lib/crawlers.json +6 -0
- package/src/lib/schema-types.json +56 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// robots.txt parser wrapper and AI crawler registry.
|
|
2
|
+
// robots-parser is a CommonJS module; import via createRequire for reliable NodeNext compat.
|
|
3
|
+
import { createRequire } from "module";
|
|
4
|
+
import { fetch as undiciFetch } from "undici";
|
|
5
|
+
import { POLITE_FETCH } from "./config.js";
|
|
6
|
+
import crawlersData from "./crawlers.json" with { type: "json" };
|
|
7
|
+
const _require = createRequire(import.meta.url);
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
|
|
9
|
+
const robotsParser = _require("robots-parser");
|
|
10
|
+
export const CRAWLERS = crawlersData;
|
|
11
|
+
/** All crawler user-agents we check (excludes robots_token_only which have no HTTP UA). */
|
|
12
|
+
export function getAllCrawlerAgents() {
|
|
13
|
+
return [
|
|
14
|
+
...CRAWLERS.training,
|
|
15
|
+
...CRAWLERS.search,
|
|
16
|
+
...CRAWLERS.user_triggered,
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Fetch raw robots.txt content. Does NOT use politeFetch to avoid recursion.
|
|
21
|
+
* Returns empty string if fetch fails.
|
|
22
|
+
*/
|
|
23
|
+
export async function fetchRobotsTxt(robotsUrl) {
|
|
24
|
+
try {
|
|
25
|
+
const response = await undiciFetch(robotsUrl, {
|
|
26
|
+
method: "GET",
|
|
27
|
+
headers: { "User-Agent": POLITE_FETCH.USER_AGENT },
|
|
28
|
+
signal: AbortSignal.timeout(POLITE_FETCH.TIMEOUT_MS),
|
|
29
|
+
redirect: "follow",
|
|
30
|
+
});
|
|
31
|
+
const text = await response.text();
|
|
32
|
+
console.error(`[robots] ${response.status} ${robotsUrl}`);
|
|
33
|
+
if (response.status === 404)
|
|
34
|
+
return "";
|
|
35
|
+
return text;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return "";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Parse robots.txt text and check if a URL is allowed for a given user-agent.
|
|
43
|
+
* Returns true (allowed) if robots.txt is empty or parse fails.
|
|
44
|
+
*/
|
|
45
|
+
export function checkRobotsAllowed(robotsText, url, userAgent) {
|
|
46
|
+
if (!robotsText)
|
|
47
|
+
return true;
|
|
48
|
+
try {
|
|
49
|
+
const robots = robotsParser(url, robotsText);
|
|
50
|
+
const result = robots.isAllowed(url, userAgent);
|
|
51
|
+
return result !== false;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Check the robots.txt posture for a specific crawler user-agent.
|
|
59
|
+
* Returns "allowed", "disallowed", or "not-mentioned".
|
|
60
|
+
*/
|
|
61
|
+
export function checkCrawlerStatus(robotsText, robotsUrl, userAgent) {
|
|
62
|
+
if (!robotsText)
|
|
63
|
+
return "not-mentioned";
|
|
64
|
+
try {
|
|
65
|
+
const robots = robotsParser(robotsUrl, robotsText);
|
|
66
|
+
// Check if explicitly disallowed
|
|
67
|
+
const isAllowed = robots.isAllowed("/", userAgent);
|
|
68
|
+
if (isAllowed === false)
|
|
69
|
+
return "disallowed";
|
|
70
|
+
// Check if explicitly mentioned (as opposed to just allowed by default)
|
|
71
|
+
// robots-parser returns undefined when not mentioned, false when disallowed, true when allowed
|
|
72
|
+
// We detect "not-mentioned" by checking if the agent appears anywhere in the robots.txt
|
|
73
|
+
const lowerText = robotsText.toLowerCase();
|
|
74
|
+
const agentLower = userAgent.toLowerCase();
|
|
75
|
+
if (!lowerText.includes(agentLower))
|
|
76
|
+
return "not-mentioned";
|
|
77
|
+
return "allowed";
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return "not-mentioned";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=robots.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"robots.js","sourceRoot":"","sources":["../../src/lib/robots.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[
|
|
2
|
+
"Article",
|
|
3
|
+
"BlogPosting",
|
|
4
|
+
"NewsArticle",
|
|
5
|
+
"FAQPage",
|
|
6
|
+
"Question",
|
|
7
|
+
"Answer",
|
|
8
|
+
"HowTo",
|
|
9
|
+
"HowToStep",
|
|
10
|
+
"HowToSection",
|
|
11
|
+
"HowToTool",
|
|
12
|
+
"HowToSupply",
|
|
13
|
+
"Organization",
|
|
14
|
+
"LocalBusiness",
|
|
15
|
+
"Person",
|
|
16
|
+
"Product",
|
|
17
|
+
"Offer",
|
|
18
|
+
"AggregateOffer",
|
|
19
|
+
"Review",
|
|
20
|
+
"AggregateRating",
|
|
21
|
+
"Rating",
|
|
22
|
+
"BreadcrumbList",
|
|
23
|
+
"ListItem",
|
|
24
|
+
"SoftwareApplication",
|
|
25
|
+
"WebApplication",
|
|
26
|
+
"MobileApplication",
|
|
27
|
+
"WebSite",
|
|
28
|
+
"WebPage",
|
|
29
|
+
"ItemPage",
|
|
30
|
+
"AboutPage",
|
|
31
|
+
"ContactPage",
|
|
32
|
+
"SearchResultsPage",
|
|
33
|
+
"ImageObject",
|
|
34
|
+
"VideoObject",
|
|
35
|
+
"AudioObject",
|
|
36
|
+
"MediaObject",
|
|
37
|
+
"Event",
|
|
38
|
+
"CreativeWork",
|
|
39
|
+
"Book",
|
|
40
|
+
"Course",
|
|
41
|
+
"Recipe",
|
|
42
|
+
"Dataset",
|
|
43
|
+
"SpeakableSpecification",
|
|
44
|
+
"SearchAction",
|
|
45
|
+
"EntryPoint",
|
|
46
|
+
"PostalAddress",
|
|
47
|
+
"GeoCoordinates",
|
|
48
|
+
"OpeningHoursSpecification",
|
|
49
|
+
"PriceSpecification",
|
|
50
|
+
"MonetaryAmount",
|
|
51
|
+
"ContactPoint",
|
|
52
|
+
"Brand",
|
|
53
|
+
"Intangible",
|
|
54
|
+
"Thing",
|
|
55
|
+
"Place"
|
|
56
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Finding } from "../types.js";
|
|
2
|
+
export declare const KNOWN_SCHEMA_TYPES: Set<string>;
|
|
3
|
+
export interface ParsedJsonLd {
|
|
4
|
+
raw: string;
|
|
5
|
+
parsed: Record<string, unknown>;
|
|
6
|
+
types: string[];
|
|
7
|
+
}
|
|
8
|
+
/** Extract @type value(s) as an array of strings. */
|
|
9
|
+
export declare function extractTypes(obj: Record<string, unknown>): string[];
|
|
10
|
+
/** Parse all JSON-LD blocks from HTML. Returns only successfully-parsed blocks. */
|
|
11
|
+
export declare function parseJsonLd(html: string): ParsedJsonLd[];
|
|
12
|
+
/** Extract a flat list of all schema types present. */
|
|
13
|
+
export declare function getAllSchemaTypes(blocks: ParsedJsonLd[]): string[];
|
|
14
|
+
/** Get missing priority schema types. */
|
|
15
|
+
export declare function getMissingPriorityTypes(foundTypes: string[]): string[];
|
|
16
|
+
/** Validate all parsed JSON-LD blocks and emit findings. */
|
|
17
|
+
export declare function validateJsonLd(blocks: ParsedJsonLd[]): Finding[];
|
|
18
|
+
/** Compute ai_citation_readiness_score from schema findings. */
|
|
19
|
+
export declare function computeSchemaScore(findings: Finding[]): number;
|
|
20
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/lib/schema.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,eAAO,MAAM,kBAAkB,aAA2C,CAAC;AAE3E,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAWD,qDAAqD;AACrD,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAMnE;AAED,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,EAAE,CAmBxD;AAED,uDAAuD;AACvD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,EAAE,CAMlE;AAKD,yCAAyC;AACzC,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAiBtE;AAED,4DAA4D;AAC5D,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,EAAE,CA8NhE;AAED,gEAAgE;AAChE,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAQ9D"}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
// JSON-LD extractor and Schema.org validator.
|
|
2
|
+
import { extractJsonLdBlocks } from "./html.js";
|
|
3
|
+
import schemaTypes from "./schema-types.json" with { type: "json" };
|
|
4
|
+
export const KNOWN_SCHEMA_TYPES = new Set(schemaTypes);
|
|
5
|
+
/** Flatten @graph arrays into individual objects. */
|
|
6
|
+
function flattenGraph(obj) {
|
|
7
|
+
const graph = obj["@graph"];
|
|
8
|
+
if (Array.isArray(graph)) {
|
|
9
|
+
return graph;
|
|
10
|
+
}
|
|
11
|
+
return [obj];
|
|
12
|
+
}
|
|
13
|
+
/** Extract @type value(s) as an array of strings. */
|
|
14
|
+
export function extractTypes(obj) {
|
|
15
|
+
const t = obj["@type"];
|
|
16
|
+
if (!t)
|
|
17
|
+
return [];
|
|
18
|
+
if (typeof t === "string")
|
|
19
|
+
return [t];
|
|
20
|
+
if (Array.isArray(t))
|
|
21
|
+
return t.filter((v) => typeof v === "string");
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
/** Parse all JSON-LD blocks from HTML. Returns only successfully-parsed blocks. */
|
|
25
|
+
export function parseJsonLd(html) {
|
|
26
|
+
const blocks = extractJsonLdBlocks(html);
|
|
27
|
+
const results = [];
|
|
28
|
+
for (const raw of blocks) {
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(raw);
|
|
31
|
+
const nodes = flattenGraph(parsed);
|
|
32
|
+
for (const node of nodes) {
|
|
33
|
+
results.push({
|
|
34
|
+
raw,
|
|
35
|
+
parsed: node,
|
|
36
|
+
types: extractTypes(node),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// skip malformed blocks
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return results;
|
|
45
|
+
}
|
|
46
|
+
/** Extract a flat list of all schema types present. */
|
|
47
|
+
export function getAllSchemaTypes(blocks) {
|
|
48
|
+
const all = new Set();
|
|
49
|
+
for (const b of blocks) {
|
|
50
|
+
for (const t of b.types)
|
|
51
|
+
all.add(t);
|
|
52
|
+
}
|
|
53
|
+
return Array.from(all);
|
|
54
|
+
}
|
|
55
|
+
const TIER1_TYPES = ["FAQPage", "HowTo", "Article", "BlogPosting", "NewsArticle", "Organization"];
|
|
56
|
+
const TIER2_TYPES = ["Product", "BreadcrumbList", "SoftwareApplication", "Person", "Review", "AggregateRating"];
|
|
57
|
+
/** Get missing priority schema types. */
|
|
58
|
+
export function getMissingPriorityTypes(foundTypes) {
|
|
59
|
+
const found = new Set(foundTypes);
|
|
60
|
+
const hasArticle = found.has("Article") || found.has("BlogPosting") || found.has("NewsArticle");
|
|
61
|
+
const missing = [];
|
|
62
|
+
for (const t of TIER1_TYPES) {
|
|
63
|
+
if (t === "Article" || t === "BlogPosting" || t === "NewsArticle") {
|
|
64
|
+
if (!hasArticle)
|
|
65
|
+
missing.push("Article/BlogPosting/NewsArticle");
|
|
66
|
+
break; // add once
|
|
67
|
+
}
|
|
68
|
+
if (!found.has(t))
|
|
69
|
+
missing.push(t);
|
|
70
|
+
}
|
|
71
|
+
// deduplicate
|
|
72
|
+
const uniqueMissing = Array.from(new Set(missing));
|
|
73
|
+
for (const t of TIER2_TYPES) {
|
|
74
|
+
if (!found.has(t))
|
|
75
|
+
uniqueMissing.push(t);
|
|
76
|
+
}
|
|
77
|
+
return uniqueMissing;
|
|
78
|
+
}
|
|
79
|
+
/** Validate all parsed JSON-LD blocks and emit findings. */
|
|
80
|
+
export function validateJsonLd(blocks) {
|
|
81
|
+
const findings = [];
|
|
82
|
+
for (const block of blocks) {
|
|
83
|
+
const obj = block.parsed;
|
|
84
|
+
// @context must be https
|
|
85
|
+
const ctx = obj["@context"];
|
|
86
|
+
if (typeof ctx === "string" && ctx === "http://schema.org") {
|
|
87
|
+
findings.push({
|
|
88
|
+
severity: "critical",
|
|
89
|
+
category: "schema",
|
|
90
|
+
where: `${block.types.join(",")}.@context`,
|
|
91
|
+
message: "@context uses http:// - must be https://schema.org.",
|
|
92
|
+
fix: 'Replace "http://schema.org" with "https://schema.org".',
|
|
93
|
+
estimated_impact: "medium",
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
// Article/BlogPosting/NewsArticle checks
|
|
97
|
+
if (block.types.some((t) => ["Article", "BlogPosting", "NewsArticle"].includes(t))) {
|
|
98
|
+
const author = obj["author"];
|
|
99
|
+
if (author === undefined) {
|
|
100
|
+
findings.push({
|
|
101
|
+
severity: "warning",
|
|
102
|
+
category: "schema",
|
|
103
|
+
where: `${block.types[0]}.author`,
|
|
104
|
+
message: "Article is missing an author property.",
|
|
105
|
+
fix: 'Add {"@type":"Person","name":"Author Name","url":"...","sameAs":["..."]}.',
|
|
106
|
+
estimated_impact: "high",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else if (typeof author === "string") {
|
|
110
|
+
findings.push({
|
|
111
|
+
severity: "critical",
|
|
112
|
+
category: "schema",
|
|
113
|
+
where: `${block.types[0]}.author`,
|
|
114
|
+
message: "author is a plain string value, not a Person node.",
|
|
115
|
+
fix: 'Replace with {"@type":"Person","name":"' + author + '","url":"https://...","sameAs":["..."]}.',
|
|
116
|
+
estimated_impact: "high",
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
else if (typeof author === "object" && author !== null) {
|
|
120
|
+
const authorObj = author;
|
|
121
|
+
if (!authorObj["sameAs"]) {
|
|
122
|
+
findings.push({
|
|
123
|
+
severity: "info",
|
|
124
|
+
category: "schema",
|
|
125
|
+
where: `${block.types[0]}.author.sameAs`,
|
|
126
|
+
message: "Person node has no sameAs links, reducing entity verification.",
|
|
127
|
+
fix: 'Add sameAs array with LinkedIn, Wikipedia, or official profile URLs.',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// sameAs must be external absolute URLs
|
|
133
|
+
const sameAs = obj["sameAs"];
|
|
134
|
+
if (sameAs) {
|
|
135
|
+
const saList = Array.isArray(sameAs)
|
|
136
|
+
? sameAs
|
|
137
|
+
: typeof sameAs === "string"
|
|
138
|
+
? [sameAs]
|
|
139
|
+
: [];
|
|
140
|
+
for (const sa of saList) {
|
|
141
|
+
if (typeof sa !== "string")
|
|
142
|
+
continue;
|
|
143
|
+
if (!sa.startsWith("http")) {
|
|
144
|
+
findings.push({
|
|
145
|
+
severity: "warning",
|
|
146
|
+
category: "schema",
|
|
147
|
+
where: `${block.types.join(",")}.sameAs`,
|
|
148
|
+
message: `sameAs value "${sa.substring(0, 60)}" is not an absolute URL.`,
|
|
149
|
+
fix: "Use absolute, external, dereferenceable URLs for sameAs values.",
|
|
150
|
+
estimated_impact: "medium",
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
try {
|
|
155
|
+
const parsed = new URL(sa);
|
|
156
|
+
// Check if it looks internal (no meaningful hostname)
|
|
157
|
+
if (parsed.hostname.length < 4) {
|
|
158
|
+
findings.push({
|
|
159
|
+
severity: "warning",
|
|
160
|
+
category: "schema",
|
|
161
|
+
where: `${block.types.join(",")}.sameAs`,
|
|
162
|
+
message: `sameAs value "${sa}" may be an internal or invalid URL.`,
|
|
163
|
+
fix: "Use external authoritative URLs (Wikipedia, Wikidata, LinkedIn, official brand sites).",
|
|
164
|
+
estimated_impact: "medium",
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
findings.push({
|
|
170
|
+
severity: "warning",
|
|
171
|
+
category: "schema",
|
|
172
|
+
where: `${block.types.join(",")}.sameAs`,
|
|
173
|
+
message: `sameAs value "${sa.substring(0, 60)}" is not a valid URL.`,
|
|
174
|
+
fix: "Replace with a valid absolute URL.",
|
|
175
|
+
estimated_impact: "medium",
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// FAQPage checks
|
|
182
|
+
if (block.types.includes("FAQPage")) {
|
|
183
|
+
const mainEntity = obj["mainEntity"];
|
|
184
|
+
if (!mainEntity) {
|
|
185
|
+
findings.push({
|
|
186
|
+
severity: "warning",
|
|
187
|
+
category: "schema",
|
|
188
|
+
where: "FAQPage.mainEntity",
|
|
189
|
+
message: "FAQPage is missing mainEntity (array of Question nodes).",
|
|
190
|
+
fix: 'Add mainEntity: [{"@type":"Question","name":"Q?","acceptedAnswer":{"@type":"Answer","text":"A"}}].',
|
|
191
|
+
estimated_impact: "high",
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
else if (Array.isArray(mainEntity)) {
|
|
195
|
+
const questions = mainEntity.map((q) => typeof q["name"] === "string" ? q["name"] : "");
|
|
196
|
+
const seen = new Set();
|
|
197
|
+
const duplicates = questions.filter((q) => {
|
|
198
|
+
if (seen.has(q))
|
|
199
|
+
return true;
|
|
200
|
+
seen.add(q);
|
|
201
|
+
return false;
|
|
202
|
+
});
|
|
203
|
+
if (duplicates.length > 0) {
|
|
204
|
+
findings.push({
|
|
205
|
+
severity: "critical",
|
|
206
|
+
category: "schema",
|
|
207
|
+
where: "FAQPage.mainEntity",
|
|
208
|
+
message: `FAQPage has ${duplicates.length} duplicate Question.name values.`,
|
|
209
|
+
fix: "Ensure each Question has a unique name value.",
|
|
210
|
+
estimated_impact: "medium",
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// HowTo checks
|
|
216
|
+
if (block.types.includes("HowTo")) {
|
|
217
|
+
const steps = obj["step"];
|
|
218
|
+
if (!steps || (Array.isArray(steps) && steps.length === 0)) {
|
|
219
|
+
findings.push({
|
|
220
|
+
severity: "warning",
|
|
221
|
+
category: "schema",
|
|
222
|
+
where: "HowTo.step",
|
|
223
|
+
message: "HowTo schema has no step array - provides no AI signal.",
|
|
224
|
+
fix: 'Add step: [{"@type":"HowToStep","name":"Step name","text":"Step description"}].',
|
|
225
|
+
estimated_impact: "medium",
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// Review rating checks
|
|
230
|
+
if (block.types.includes("Review")) {
|
|
231
|
+
const reviewRating = obj["reviewRating"];
|
|
232
|
+
if (reviewRating) {
|
|
233
|
+
const ratingValue = reviewRating["ratingValue"];
|
|
234
|
+
if (typeof ratingValue === "number") {
|
|
235
|
+
findings.push({
|
|
236
|
+
severity: "warning",
|
|
237
|
+
category: "schema",
|
|
238
|
+
where: "Review.reviewRating.ratingValue",
|
|
239
|
+
message: "ratingValue is a number - must be a numeric string.",
|
|
240
|
+
fix: 'Change ratingValue from a number to a string, e.g. "4.5" instead of 4.5.',
|
|
241
|
+
estimated_impact: "low",
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// Organization logo check
|
|
247
|
+
if (block.types.includes("Organization")) {
|
|
248
|
+
const logo = obj["logo"];
|
|
249
|
+
if (!logo) {
|
|
250
|
+
findings.push({
|
|
251
|
+
severity: "warning",
|
|
252
|
+
category: "schema",
|
|
253
|
+
where: "Organization.logo",
|
|
254
|
+
message: "Organization is missing a logo property.",
|
|
255
|
+
fix: 'Add logo: {"@type":"ImageObject","url":"https://example.com/logo.png"}.',
|
|
256
|
+
estimated_impact: "medium",
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
else if (typeof logo === "string") {
|
|
260
|
+
findings.push({
|
|
261
|
+
severity: "warning",
|
|
262
|
+
category: "schema",
|
|
263
|
+
where: "Organization.logo",
|
|
264
|
+
message: "Organization.logo is a string URL, not an ImageObject.",
|
|
265
|
+
fix: 'Replace with {"@type":"ImageObject","url":"<logo-url>"}.',
|
|
266
|
+
estimated_impact: "low",
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// Person sameAs check
|
|
271
|
+
if (block.types.includes("Person")) {
|
|
272
|
+
if (!obj["sameAs"]) {
|
|
273
|
+
findings.push({
|
|
274
|
+
severity: "info",
|
|
275
|
+
category: "schema",
|
|
276
|
+
where: "Person.sameAs",
|
|
277
|
+
message: "Person node has no sameAs - reduces E-E-A-T verification.",
|
|
278
|
+
fix: "Add sameAs links to LinkedIn, Twitter/X, Wikipedia, or other authority profiles.",
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// Unknown type check
|
|
283
|
+
for (const t of block.types) {
|
|
284
|
+
if (!KNOWN_SCHEMA_TYPES.has(t)) {
|
|
285
|
+
findings.push({
|
|
286
|
+
severity: "warning",
|
|
287
|
+
category: "schema",
|
|
288
|
+
where: "@type",
|
|
289
|
+
message: `Unrecognized Schema.org type: "${t}".`,
|
|
290
|
+
fix: "Verify the type name at schema.org and correct any typo or deprecated type name.",
|
|
291
|
+
estimated_impact: "low",
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return findings;
|
|
297
|
+
}
|
|
298
|
+
/** Compute ai_citation_readiness_score from schema findings. */
|
|
299
|
+
export function computeSchemaScore(findings) {
|
|
300
|
+
let score = 100;
|
|
301
|
+
for (const f of findings) {
|
|
302
|
+
if (f.severity === "critical")
|
|
303
|
+
score -= 20;
|
|
304
|
+
else if (f.severity === "warning")
|
|
305
|
+
score -= 8;
|
|
306
|
+
else
|
|
307
|
+
score -= 2;
|
|
308
|
+
}
|
|
309
|
+
return Math.max(0, score);
|
|
310
|
+
}
|
|
311
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/lib/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Finding } from "../types.js";
|
|
2
|
+
export type Grade = "A" | "B" | "C" | "D" | "F";
|
|
3
|
+
/** Derive a letter grade from a 0-100 numeric score. */
|
|
4
|
+
export declare function deriveGrade(score: number): Grade;
|
|
5
|
+
/**
|
|
6
|
+
* Weighted dimension score for audit_page.
|
|
7
|
+
* Weights: schema 25%, technical 20%, structure 20%, robots 10%,
|
|
8
|
+
* freshness 10%, authority 10%, entity_density 3%, sitemap 2%.
|
|
9
|
+
*/
|
|
10
|
+
export declare function computeWeightedScore(dimensions: {
|
|
11
|
+
schema: number;
|
|
12
|
+
technical: number;
|
|
13
|
+
structure: number;
|
|
14
|
+
robots: number;
|
|
15
|
+
freshness: number;
|
|
16
|
+
authority: number;
|
|
17
|
+
entity_density: number;
|
|
18
|
+
sitemap: number;
|
|
19
|
+
}): number;
|
|
20
|
+
/**
|
|
21
|
+
* Compute a simple score from a findings list.
|
|
22
|
+
* Starts at 100, deducts per finding severity.
|
|
23
|
+
* critical: -15, warning: -7, info: -2
|
|
24
|
+
*/
|
|
25
|
+
export declare function scoreFromFindings(findings: Finding[], baseScore?: number, weights?: {
|
|
26
|
+
critical: number;
|
|
27
|
+
warning: number;
|
|
28
|
+
info: number;
|
|
29
|
+
}): number;
|
|
30
|
+
/** Freshness score from a dateModified ISO string. 100 if within 90 days, decays linearly to 0 at 365 days. */
|
|
31
|
+
export declare function freshnessScore(dateModified: string | null): number;
|
|
32
|
+
//# sourceMappingURL=score.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"score.d.ts","sourceRoot":"","sources":["../../src/lib/score.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,MAAM,KAAK,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAEhD,wDAAwD;AACxD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,CAMhD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CAWT;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,OAAO,EAAE,EACnB,SAAS,SAAM,EACf,OAAO;;;;CAAwC,GAC9C,MAAM,CAQR;AAED,+GAA+G;AAC/G,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAUlE"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Grade derivation and dimension weighting formulas.
|
|
2
|
+
/** Derive a letter grade from a 0-100 numeric score. */
|
|
3
|
+
export function deriveGrade(score) {
|
|
4
|
+
if (score >= 90)
|
|
5
|
+
return "A";
|
|
6
|
+
if (score >= 75)
|
|
7
|
+
return "B";
|
|
8
|
+
if (score >= 60)
|
|
9
|
+
return "C";
|
|
10
|
+
if (score >= 45)
|
|
11
|
+
return "D";
|
|
12
|
+
return "F";
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Weighted dimension score for audit_page.
|
|
16
|
+
* Weights: schema 25%, technical 20%, structure 20%, robots 10%,
|
|
17
|
+
* freshness 10%, authority 10%, entity_density 3%, sitemap 2%.
|
|
18
|
+
*/
|
|
19
|
+
export function computeWeightedScore(dimensions) {
|
|
20
|
+
const score = dimensions.schema * 0.25 +
|
|
21
|
+
dimensions.technical * 0.2 +
|
|
22
|
+
dimensions.structure * 0.2 +
|
|
23
|
+
dimensions.robots * 0.1 +
|
|
24
|
+
dimensions.freshness * 0.1 +
|
|
25
|
+
dimensions.authority * 0.1 +
|
|
26
|
+
dimensions.entity_density * 0.03 +
|
|
27
|
+
dimensions.sitemap * 0.02;
|
|
28
|
+
return Math.round(Math.max(0, Math.min(100, score)));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Compute a simple score from a findings list.
|
|
32
|
+
* Starts at 100, deducts per finding severity.
|
|
33
|
+
* critical: -15, warning: -7, info: -2
|
|
34
|
+
*/
|
|
35
|
+
export function scoreFromFindings(findings, baseScore = 100, weights = { critical: 15, warning: 7, info: 2 }) {
|
|
36
|
+
let score = baseScore;
|
|
37
|
+
for (const f of findings) {
|
|
38
|
+
if (f.severity === "critical")
|
|
39
|
+
score -= weights.critical;
|
|
40
|
+
else if (f.severity === "warning")
|
|
41
|
+
score -= weights.warning;
|
|
42
|
+
else
|
|
43
|
+
score -= weights.info;
|
|
44
|
+
}
|
|
45
|
+
return Math.max(0, Math.min(100, score));
|
|
46
|
+
}
|
|
47
|
+
/** Freshness score from a dateModified ISO string. 100 if within 90 days, decays linearly to 0 at 365 days. */
|
|
48
|
+
export function freshnessScore(dateModified) {
|
|
49
|
+
if (!dateModified)
|
|
50
|
+
return 30; // unknown freshness - partial credit
|
|
51
|
+
const modified = new Date(dateModified).getTime();
|
|
52
|
+
const now = Date.now();
|
|
53
|
+
const ageMs = now - modified;
|
|
54
|
+
const ageDays = ageMs / (1000 * 60 * 60 * 24);
|
|
55
|
+
if (ageDays <= 90)
|
|
56
|
+
return 100;
|
|
57
|
+
if (ageDays >= 365)
|
|
58
|
+
return 0;
|
|
59
|
+
// linear decay from 100 at 90 days to 0 at 365 days
|
|
60
|
+
return Math.round(100 * (1 - (ageDays - 90) / (365 - 90)));
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=score.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"score.js","sourceRoot":"","sources":["../../src/lib/score.ts"],"names":[],"mappings":"AAAA,qDAAqD;AAMrD,wDAAwD;AACxD,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,GAAG,CAAC;IAC5B,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,GAAG,CAAC;IAC5B,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,GAAG,CAAC;IAC5B,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,GAAG,CAAC;IAC5B,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,UASpC;IACC,MAAM,KAAK,GACT,UAAU,CAAC,MAAM,GAAG,IAAI;QACxB,UAAU,CAAC,SAAS,GAAG,GAAG;QAC1B,UAAU,CAAC,SAAS,GAAG,GAAG;QAC1B,UAAU,CAAC,MAAM,GAAG,GAAG;QACvB,UAAU,CAAC,SAAS,GAAG,GAAG;QAC1B,UAAU,CAAC,SAAS,GAAG,GAAG;QAC1B,UAAU,CAAC,cAAc,GAAG,IAAI;QAChC,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;IAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;AACvD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAmB,EACnB,SAAS,GAAG,GAAG,EACf,OAAO,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE;IAE/C,IAAI,KAAK,GAAG,SAAS,CAAC;IACtB,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,QAAQ,KAAK,UAAU;YAAE,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC;aACpD,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS;YAAE,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;;YACvD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAC7B,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;AAC3C,CAAC;AAED,+GAA+G;AAC/G,MAAM,UAAU,cAAc,CAAC,YAA2B;IACxD,IAAI,CAAC,YAAY;QAAE,OAAO,EAAE,CAAC,CAAC,qCAAqC;IACnE,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC;IAClD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,KAAK,GAAG,GAAG,GAAG,QAAQ,CAAC;IAC7B,MAAM,OAAO,GAAG,KAAK,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9C,IAAI,OAAO,IAAI,EAAE;QAAE,OAAO,GAAG,CAAC;IAC9B,IAAI,OAAO,IAAI,GAAG;QAAE,OAAO,CAAC,CAAC;IAC7B,oDAAoD;IACpD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type HostDelayMap } from "../lib/fetch.js";
|
|
3
|
+
import type { Finding } from "../types.js";
|
|
4
|
+
export declare const auditCanonicalInputSchema: z.ZodObject<{
|
|
5
|
+
url: z.ZodString;
|
|
6
|
+
respect_robots: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
7
|
+
}, "strip", z.ZodTypeAny, {
|
|
8
|
+
url: string;
|
|
9
|
+
respect_robots: boolean;
|
|
10
|
+
}, {
|
|
11
|
+
url: string;
|
|
12
|
+
respect_robots?: boolean | undefined;
|
|
13
|
+
}>;
|
|
14
|
+
export type AuditCanonicalInput = z.infer<typeof auditCanonicalInputSchema>;
|
|
15
|
+
export interface CanonicalResult {
|
|
16
|
+
url: string;
|
|
17
|
+
final_url: string;
|
|
18
|
+
canonical_value: string | null;
|
|
19
|
+
is_self_referential: boolean;
|
|
20
|
+
is_cross_domain: boolean;
|
|
21
|
+
trailing_slash_consistent: boolean;
|
|
22
|
+
canonical_og_url_match: boolean;
|
|
23
|
+
findings: Finding[];
|
|
24
|
+
}
|
|
25
|
+
export declare function auditCanonical(input: AuditCanonicalInput, hostDelays?: HostDelayMap, robotsCache?: Map<string, string>): Promise<CanonicalResult>;
|
|
26
|
+
//# sourceMappingURL=audit-canonical.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-canonical.d.ts","sourceRoot":"","sources":["../../src/tools/audit-canonical.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEjE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,eAAO,MAAM,yBAAyB;;;;;;;;;EAGpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,eAAe,EAAE,OAAO,CAAC;IACzB,yBAAyB,EAAE,OAAO,CAAC;IACnC,sBAAsB,EAAE,OAAO,CAAC;IAChC,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,wBAAsB,cAAc,CAClC,KAAK,EAAE,mBAAmB,EAC1B,UAAU,CAAC,EAAE,YAAY,EACzB,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,OAAO,CAAC,eAAe,CAAC,CAqH1B"}
|