geo-checker 0.3.1 → 0.3.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/dist/cli.cjs +150 -14
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +150 -14
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +142 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +142 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -263,10 +263,12 @@ async function buildContext(url, opts = {}) {
|
|
|
263
263
|
"Site appears to be JS-rendered (sparse body + SPA root element). Re-run with --render for accurate results."
|
|
264
264
|
);
|
|
265
265
|
}
|
|
266
|
-
const [robotsRaw, llmsRaw, llmsFullRaw] = await Promise.all([
|
|
266
|
+
const [robotsRaw, llmsRaw, llmsFullRaw, skillMdRaw, agentPermissionsRaw] = await Promise.all([
|
|
267
267
|
fetchText(`${origin}/robots.txt`, opts),
|
|
268
268
|
fetchText(`${origin}/llms.txt`, opts),
|
|
269
|
-
fetchText(`${origin}/llms-full.txt`, opts)
|
|
269
|
+
fetchText(`${origin}/llms-full.txt`, opts),
|
|
270
|
+
fetchText(`${origin}/skill.md`, opts),
|
|
271
|
+
fetchText(`${origin}/agent-permissions.json`, opts)
|
|
270
272
|
]);
|
|
271
273
|
let sitemapUrl = null;
|
|
272
274
|
const robots = robotsRaw ? parseRobots(robotsRaw) : null;
|
|
@@ -274,6 +276,13 @@ async function buildContext(url, opts = {}) {
|
|
|
274
276
|
if (!sitemapUrl) sitemapUrl = `${origin}/sitemap.xml`;
|
|
275
277
|
const sitemapRaw = await fetchText(sitemapUrl, opts);
|
|
276
278
|
const sitemap = sitemapRaw ? parseSitemap(sitemapRaw) : null;
|
|
279
|
+
let agentPermissions = null;
|
|
280
|
+
if (agentPermissionsRaw && agentPermissionsRaw.trim().length > 0) {
|
|
281
|
+
try {
|
|
282
|
+
agentPermissions = JSON.parse(agentPermissionsRaw);
|
|
283
|
+
} catch {
|
|
284
|
+
}
|
|
285
|
+
}
|
|
277
286
|
return {
|
|
278
287
|
url,
|
|
279
288
|
finalUrl,
|
|
@@ -288,7 +297,9 @@ async function buildContext(url, opts = {}) {
|
|
|
288
297
|
jsonLd: extractJsonLd($),
|
|
289
298
|
renderMode,
|
|
290
299
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
291
|
-
warnings
|
|
300
|
+
warnings,
|
|
301
|
+
skillMd: skillMdRaw && skillMdRaw.trim().length > 0 ? skillMdRaw : null,
|
|
302
|
+
agentPermissions
|
|
292
303
|
};
|
|
293
304
|
}
|
|
294
305
|
|
|
@@ -297,10 +308,11 @@ function defineRule(rule) {
|
|
|
297
308
|
return rule;
|
|
298
309
|
}
|
|
299
310
|
var CATEGORY_WEIGHTS = {
|
|
300
|
-
crawler:
|
|
301
|
-
"structured-data":
|
|
302
|
-
citation:
|
|
303
|
-
content:
|
|
311
|
+
crawler: 20,
|
|
312
|
+
"structured-data": 25,
|
|
313
|
+
citation: 20,
|
|
314
|
+
content: 15,
|
|
315
|
+
aeo: 20
|
|
304
316
|
};
|
|
305
317
|
|
|
306
318
|
// src/engine.ts
|
|
@@ -314,7 +326,8 @@ async function runRules(ctx, rules, opts = {}) {
|
|
|
314
326
|
crawler: { score: 0, weight: weights.crawler, results: [] },
|
|
315
327
|
"structured-data": { score: 0, weight: weights["structured-data"], results: [] },
|
|
316
328
|
citation: { score: 0, weight: weights.citation, results: [] },
|
|
317
|
-
content: { score: 0, weight: weights.content, results: [] }
|
|
329
|
+
content: { score: 0, weight: weights.content, results: [] },
|
|
330
|
+
aeo: { score: 0, weight: weights.aeo, results: [] }
|
|
318
331
|
};
|
|
319
332
|
for (const rule of rules) {
|
|
320
333
|
if (onlySet && !onlySet.has(rule.id) && (!rule.stableId || !onlySet.has(rule.stableId))) continue;
|
|
@@ -1904,12 +1917,130 @@ var contentRules = [
|
|
|
1904
1917
|
externalCitationsRule
|
|
1905
1918
|
];
|
|
1906
1919
|
|
|
1920
|
+
// src/rules/aeo/skill-md.ts
|
|
1921
|
+
var aeoSkillMdRule = defineRule({
|
|
1922
|
+
id: "aeo.skill-md",
|
|
1923
|
+
stableId: "aeo.skill-md",
|
|
1924
|
+
category: "aeo",
|
|
1925
|
+
group: "opportunity",
|
|
1926
|
+
weight: 3,
|
|
1927
|
+
impact: "high",
|
|
1928
|
+
effort: "low",
|
|
1929
|
+
docsUrl: "https://github.com/BaRam-OSS/geo-checker/blob/main/docs/rules.md#aeoskill-md",
|
|
1930
|
+
title: "skill.md is present",
|
|
1931
|
+
title_ko: "skill.md \uD30C\uC77C \uC874\uC7AC \uC5EC\uBD80",
|
|
1932
|
+
description: "A /skill.md file describes site capabilities so AI agents know what this site can do for them.",
|
|
1933
|
+
run(ctx) {
|
|
1934
|
+
if (ctx.skillMd !== null) {
|
|
1935
|
+
return {
|
|
1936
|
+
status: "pass",
|
|
1937
|
+
score: 1,
|
|
1938
|
+
rationale: "skill.md found at site root.",
|
|
1939
|
+
rationale_ko: "skill.md\uAC00 \uC0AC\uC774\uD2B8 \uB8E8\uD2B8\uC5D0 \uC874\uC7AC\uD569\uB2C8\uB2E4."
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
return {
|
|
1943
|
+
status: "warn",
|
|
1944
|
+
score: 0,
|
|
1945
|
+
rationale: "No /skill.md found. Add one to describe your site capabilities to AI agents.",
|
|
1946
|
+
rationale_ko: "/skill.md\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. AI \uC5D0\uC774\uC804\uD2B8\uAC00 \uC0AC\uC774\uD2B8 \uAE30\uB2A5\uC744 \uD30C\uC545\uD560 \uC218 \uC788\uB3C4\uB85D \uCD94\uAC00\uD558\uC138\uC694.",
|
|
1947
|
+
fixHint: "Create /skill.md listing what services, products, and capabilities this site offers."
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
});
|
|
1951
|
+
|
|
1952
|
+
// src/rules/aeo/agent-permissions.ts
|
|
1953
|
+
var aeoAgentPermissionsRule = defineRule({
|
|
1954
|
+
id: "aeo.agent-permissions",
|
|
1955
|
+
stableId: "aeo.agent-permissions",
|
|
1956
|
+
category: "aeo",
|
|
1957
|
+
group: "opportunity",
|
|
1958
|
+
weight: 3,
|
|
1959
|
+
impact: "medium",
|
|
1960
|
+
effort: "low",
|
|
1961
|
+
docsUrl: "https://github.com/BaRam-OSS/geo-checker/blob/main/docs/rules.md#aeoagent-permissions",
|
|
1962
|
+
title: "agent-permissions.json is present",
|
|
1963
|
+
title_ko: "agent-permissions.json \uD30C\uC77C \uC874\uC7AC \uC5EC\uBD80",
|
|
1964
|
+
description: "Declares explicit read/summarize/cite/train permissions for AI agents.",
|
|
1965
|
+
run(ctx) {
|
|
1966
|
+
if (ctx.agentPermissions !== null) {
|
|
1967
|
+
return {
|
|
1968
|
+
status: "pass",
|
|
1969
|
+
score: 1,
|
|
1970
|
+
rationale: "agent-permissions.json found at site root.",
|
|
1971
|
+
rationale_ko: "agent-permissions.json\uC774 \uC0AC\uC774\uD2B8 \uB8E8\uD2B8\uC5D0 \uC874\uC7AC\uD569\uB2C8\uB2E4.",
|
|
1972
|
+
evidence: ctx.agentPermissions
|
|
1973
|
+
};
|
|
1974
|
+
}
|
|
1975
|
+
return {
|
|
1976
|
+
status: "warn",
|
|
1977
|
+
score: 0,
|
|
1978
|
+
rationale: "No /agent-permissions.json found. Add one to declare AI agent access policy.",
|
|
1979
|
+
rationale_ko: "/agent-permissions.json\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. AI \uC5D0\uC774\uC804\uD2B8 \uC811\uADFC \uC815\uCC45\uC744 \uBA85\uC2DC\uD558\uB824\uBA74 \uCD94\uAC00\uD558\uC138\uC694.",
|
|
1980
|
+
fixHint: "Create /agent-permissions.json with read, summarize, cite, and train permission flags."
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
});
|
|
1984
|
+
|
|
1985
|
+
// src/rules/aeo/token-length.ts
|
|
1986
|
+
var THRESHOLD_OPTIMAL = 15e3;
|
|
1987
|
+
var THRESHOLD_MAX = 25e3;
|
|
1988
|
+
var aeoTokenLengthRule = defineRule({
|
|
1989
|
+
id: "aeo.token-length",
|
|
1990
|
+
stableId: "aeo.token-length",
|
|
1991
|
+
category: "aeo",
|
|
1992
|
+
group: "diagnostic",
|
|
1993
|
+
weight: 4,
|
|
1994
|
+
impact: "medium",
|
|
1995
|
+
effort: "medium",
|
|
1996
|
+
docsUrl: "https://github.com/BaRam-OSS/geo-checker/blob/main/docs/rules.md#aeotoken-length",
|
|
1997
|
+
title: "Content token length within AI agent limits",
|
|
1998
|
+
title_ko: "\uCF58\uD150\uCE20 \uD1A0\uD070 \uC218 AI \uC5D0\uC774\uC804\uD2B8 \uAD8C\uC7A5 \uBC94\uC704",
|
|
1999
|
+
description: "Pages under 15K tokens are optimal for AI agents (per Addy Osmani's AEO guidance).",
|
|
2000
|
+
run(ctx) {
|
|
2001
|
+
const text = ctx.$("body").text();
|
|
2002
|
+
const tokenEstimate = Math.round(text.length / 3);
|
|
2003
|
+
const evidence = { tokenEstimate, thresholds: { optimal: THRESHOLD_OPTIMAL, max: THRESHOLD_MAX } };
|
|
2004
|
+
if (tokenEstimate <= THRESHOLD_OPTIMAL) {
|
|
2005
|
+
return {
|
|
2006
|
+
status: "pass",
|
|
2007
|
+
score: 1,
|
|
2008
|
+
rationale: `Estimated ~${tokenEstimate.toLocaleString()} tokens \u2014 within optimal range.`,
|
|
2009
|
+
rationale_ko: `\uC608\uC0C1 \uD1A0\uD070 \uC218 ~${tokenEstimate.toLocaleString()} \u2014 \uAD8C\uC7A5 \uBC94\uC704(15K) \uC774\uB0B4\uC785\uB2C8\uB2E4.`,
|
|
2010
|
+
evidence
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
if (tokenEstimate <= THRESHOLD_MAX) {
|
|
2014
|
+
return {
|
|
2015
|
+
status: "warn",
|
|
2016
|
+
score: 0.5,
|
|
2017
|
+
rationale: `Estimated ~${tokenEstimate.toLocaleString()} tokens \u2014 exceeds 15K recommendation.`,
|
|
2018
|
+
rationale_ko: `\uC608\uC0C1 \uD1A0\uD070 \uC218 ~${tokenEstimate.toLocaleString()} \u2014 15K \uAD8C\uC7A5\uCE58\uB97C \uCD08\uACFC\uD569\uB2C8\uB2E4.`,
|
|
2019
|
+
fixHint: "Consider splitting content into shorter, focused pages.",
|
|
2020
|
+
evidence
|
|
2021
|
+
};
|
|
2022
|
+
}
|
|
2023
|
+
return {
|
|
2024
|
+
status: "fail",
|
|
2025
|
+
score: 0,
|
|
2026
|
+
rationale: `Estimated ~${tokenEstimate.toLocaleString()} tokens \u2014 exceeds 25K agent processing limit.`,
|
|
2027
|
+
rationale_ko: `\uC608\uC0C1 \uD1A0\uD070 \uC218 ~${tokenEstimate.toLocaleString()} \u2014 AI \uC5D0\uC774\uC804\uD2B8 \uCC98\uB9AC \uD55C\uACC4(25K)\uB97C \uCD08\uACFC\uD569\uB2C8\uB2E4.`,
|
|
2028
|
+
fixHint: "Split this page into multiple focused pages under 15K tokens.",
|
|
2029
|
+
evidence
|
|
2030
|
+
};
|
|
2031
|
+
}
|
|
2032
|
+
});
|
|
2033
|
+
|
|
2034
|
+
// src/rules/aeo/index.ts
|
|
2035
|
+
var aeoRules = [aeoSkillMdRule, aeoAgentPermissionsRule, aeoTokenLengthRule];
|
|
2036
|
+
|
|
1907
2037
|
// src/rules/index.ts
|
|
1908
2038
|
var defaultRules = [
|
|
1909
2039
|
...crawlerRules,
|
|
1910
2040
|
...structuredDataRules,
|
|
1911
2041
|
...citationRules,
|
|
1912
|
-
...contentRules
|
|
2042
|
+
...contentRules,
|
|
2043
|
+
...aeoRules
|
|
1913
2044
|
];
|
|
1914
2045
|
|
|
1915
2046
|
// src/config.ts
|
|
@@ -2105,7 +2236,8 @@ var CATEGORY_LABELS = {
|
|
|
2105
2236
|
crawler: "AI Crawler Access",
|
|
2106
2237
|
"structured-data": "Structured Data",
|
|
2107
2238
|
citation: "Citation Signals",
|
|
2108
|
-
content: "Content Structure"
|
|
2239
|
+
content: "Content Structure",
|
|
2240
|
+
aeo: "AEO Stack"
|
|
2109
2241
|
};
|
|
2110
2242
|
function scoreBadge(score) {
|
|
2111
2243
|
const color = score >= 85 ? "brightgreen" : score >= 60 ? "yellow" : "red";
|
|
@@ -2400,7 +2532,8 @@ var CATEGORY_LABELS2 = {
|
|
|
2400
2532
|
crawler: "AI Crawler Access",
|
|
2401
2533
|
"structured-data": "Structured Data",
|
|
2402
2534
|
citation: "Citation Signals",
|
|
2403
|
-
content: "Content Structure"
|
|
2535
|
+
content: "Content Structure",
|
|
2536
|
+
aeo: "AEO Stack"
|
|
2404
2537
|
};
|
|
2405
2538
|
function colorScore(score) {
|
|
2406
2539
|
if (score >= 85) return kleur.green().bold(`${score}`);
|
|
@@ -2500,7 +2633,8 @@ var CATEGORY_LABELS3 = {
|
|
|
2500
2633
|
crawler: "AI Crawler Access",
|
|
2501
2634
|
"structured-data": "Structured Data",
|
|
2502
2635
|
citation: "Citation Signals",
|
|
2503
|
-
content: "Content Structure"
|
|
2636
|
+
content: "Content Structure",
|
|
2637
|
+
aeo: "AEO Stack"
|
|
2504
2638
|
};
|
|
2505
2639
|
var IMPACT_ORDER = {
|
|
2506
2640
|
critical: 4,
|
|
@@ -2583,13 +2717,15 @@ function partitionResults(report) {
|
|
|
2583
2717
|
crawler: [],
|
|
2584
2718
|
"structured-data": [],
|
|
2585
2719
|
citation: [],
|
|
2586
|
-
content: []
|
|
2720
|
+
content: [],
|
|
2721
|
+
aeo: []
|
|
2587
2722
|
};
|
|
2588
2723
|
const passed = {
|
|
2589
2724
|
crawler: [],
|
|
2590
2725
|
"structured-data": [],
|
|
2591
2726
|
citation: [],
|
|
2592
|
-
content: []
|
|
2727
|
+
content: [],
|
|
2728
|
+
aeo: []
|
|
2593
2729
|
};
|
|
2594
2730
|
for (const cat of Object.keys(report.categories)) {
|
|
2595
2731
|
for (const r of report.categories[cat].results) {
|