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.cjs
CHANGED
|
@@ -286,10 +286,12 @@ async function buildContext(url, opts = {}) {
|
|
|
286
286
|
"Site appears to be JS-rendered (sparse body + SPA root element). Re-run with --render for accurate results."
|
|
287
287
|
);
|
|
288
288
|
}
|
|
289
|
-
const [robotsRaw, llmsRaw, llmsFullRaw] = await Promise.all([
|
|
289
|
+
const [robotsRaw, llmsRaw, llmsFullRaw, skillMdRaw, agentPermissionsRaw] = await Promise.all([
|
|
290
290
|
fetchText(`${origin}/robots.txt`, opts),
|
|
291
291
|
fetchText(`${origin}/llms.txt`, opts),
|
|
292
|
-
fetchText(`${origin}/llms-full.txt`, opts)
|
|
292
|
+
fetchText(`${origin}/llms-full.txt`, opts),
|
|
293
|
+
fetchText(`${origin}/skill.md`, opts),
|
|
294
|
+
fetchText(`${origin}/agent-permissions.json`, opts)
|
|
293
295
|
]);
|
|
294
296
|
let sitemapUrl = null;
|
|
295
297
|
const robots = robotsRaw ? parseRobots(robotsRaw) : null;
|
|
@@ -297,6 +299,13 @@ async function buildContext(url, opts = {}) {
|
|
|
297
299
|
if (!sitemapUrl) sitemapUrl = `${origin}/sitemap.xml`;
|
|
298
300
|
const sitemapRaw = await fetchText(sitemapUrl, opts);
|
|
299
301
|
const sitemap = sitemapRaw ? parseSitemap(sitemapRaw) : null;
|
|
302
|
+
let agentPermissions = null;
|
|
303
|
+
if (agentPermissionsRaw && agentPermissionsRaw.trim().length > 0) {
|
|
304
|
+
try {
|
|
305
|
+
agentPermissions = JSON.parse(agentPermissionsRaw);
|
|
306
|
+
} catch {
|
|
307
|
+
}
|
|
308
|
+
}
|
|
300
309
|
return {
|
|
301
310
|
url,
|
|
302
311
|
finalUrl,
|
|
@@ -311,7 +320,9 @@ async function buildContext(url, opts = {}) {
|
|
|
311
320
|
jsonLd: extractJsonLd($),
|
|
312
321
|
renderMode,
|
|
313
322
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
314
|
-
warnings
|
|
323
|
+
warnings,
|
|
324
|
+
skillMd: skillMdRaw && skillMdRaw.trim().length > 0 ? skillMdRaw : null,
|
|
325
|
+
agentPermissions
|
|
315
326
|
};
|
|
316
327
|
}
|
|
317
328
|
|
|
@@ -320,10 +331,11 @@ function defineRule(rule) {
|
|
|
320
331
|
return rule;
|
|
321
332
|
}
|
|
322
333
|
var CATEGORY_WEIGHTS = {
|
|
323
|
-
crawler:
|
|
324
|
-
"structured-data":
|
|
325
|
-
citation:
|
|
326
|
-
content:
|
|
334
|
+
crawler: 20,
|
|
335
|
+
"structured-data": 25,
|
|
336
|
+
citation: 20,
|
|
337
|
+
content: 15,
|
|
338
|
+
aeo: 20
|
|
327
339
|
};
|
|
328
340
|
|
|
329
341
|
// src/engine.ts
|
|
@@ -337,7 +349,8 @@ async function runRules(ctx, rules, opts = {}) {
|
|
|
337
349
|
crawler: { score: 0, weight: weights.crawler, results: [] },
|
|
338
350
|
"structured-data": { score: 0, weight: weights["structured-data"], results: [] },
|
|
339
351
|
citation: { score: 0, weight: weights.citation, results: [] },
|
|
340
|
-
content: { score: 0, weight: weights.content, results: [] }
|
|
352
|
+
content: { score: 0, weight: weights.content, results: [] },
|
|
353
|
+
aeo: { score: 0, weight: weights.aeo, results: [] }
|
|
341
354
|
};
|
|
342
355
|
for (const rule of rules) {
|
|
343
356
|
if (onlySet && !onlySet.has(rule.id) && (!rule.stableId || !onlySet.has(rule.stableId))) continue;
|
|
@@ -1927,12 +1940,130 @@ var contentRules = [
|
|
|
1927
1940
|
externalCitationsRule
|
|
1928
1941
|
];
|
|
1929
1942
|
|
|
1943
|
+
// src/rules/aeo/skill-md.ts
|
|
1944
|
+
var aeoSkillMdRule = defineRule({
|
|
1945
|
+
id: "aeo.skill-md",
|
|
1946
|
+
stableId: "aeo.skill-md",
|
|
1947
|
+
category: "aeo",
|
|
1948
|
+
group: "opportunity",
|
|
1949
|
+
weight: 3,
|
|
1950
|
+
impact: "high",
|
|
1951
|
+
effort: "low",
|
|
1952
|
+
docsUrl: "https://github.com/BaRam-OSS/geo-checker/blob/main/docs/rules.md#aeoskill-md",
|
|
1953
|
+
title: "skill.md is present",
|
|
1954
|
+
title_ko: "skill.md \uD30C\uC77C \uC874\uC7AC \uC5EC\uBD80",
|
|
1955
|
+
description: "A /skill.md file describes site capabilities so AI agents know what this site can do for them.",
|
|
1956
|
+
run(ctx) {
|
|
1957
|
+
if (ctx.skillMd !== null) {
|
|
1958
|
+
return {
|
|
1959
|
+
status: "pass",
|
|
1960
|
+
score: 1,
|
|
1961
|
+
rationale: "skill.md found at site root.",
|
|
1962
|
+
rationale_ko: "skill.md\uAC00 \uC0AC\uC774\uD2B8 \uB8E8\uD2B8\uC5D0 \uC874\uC7AC\uD569\uB2C8\uB2E4."
|
|
1963
|
+
};
|
|
1964
|
+
}
|
|
1965
|
+
return {
|
|
1966
|
+
status: "warn",
|
|
1967
|
+
score: 0,
|
|
1968
|
+
rationale: "No /skill.md found. Add one to describe your site capabilities to AI agents.",
|
|
1969
|
+
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.",
|
|
1970
|
+
fixHint: "Create /skill.md listing what services, products, and capabilities this site offers."
|
|
1971
|
+
};
|
|
1972
|
+
}
|
|
1973
|
+
});
|
|
1974
|
+
|
|
1975
|
+
// src/rules/aeo/agent-permissions.ts
|
|
1976
|
+
var aeoAgentPermissionsRule = defineRule({
|
|
1977
|
+
id: "aeo.agent-permissions",
|
|
1978
|
+
stableId: "aeo.agent-permissions",
|
|
1979
|
+
category: "aeo",
|
|
1980
|
+
group: "opportunity",
|
|
1981
|
+
weight: 3,
|
|
1982
|
+
impact: "medium",
|
|
1983
|
+
effort: "low",
|
|
1984
|
+
docsUrl: "https://github.com/BaRam-OSS/geo-checker/blob/main/docs/rules.md#aeoagent-permissions",
|
|
1985
|
+
title: "agent-permissions.json is present",
|
|
1986
|
+
title_ko: "agent-permissions.json \uD30C\uC77C \uC874\uC7AC \uC5EC\uBD80",
|
|
1987
|
+
description: "Declares explicit read/summarize/cite/train permissions for AI agents.",
|
|
1988
|
+
run(ctx) {
|
|
1989
|
+
if (ctx.agentPermissions !== null) {
|
|
1990
|
+
return {
|
|
1991
|
+
status: "pass",
|
|
1992
|
+
score: 1,
|
|
1993
|
+
rationale: "agent-permissions.json found at site root.",
|
|
1994
|
+
rationale_ko: "agent-permissions.json\uC774 \uC0AC\uC774\uD2B8 \uB8E8\uD2B8\uC5D0 \uC874\uC7AC\uD569\uB2C8\uB2E4.",
|
|
1995
|
+
evidence: ctx.agentPermissions
|
|
1996
|
+
};
|
|
1997
|
+
}
|
|
1998
|
+
return {
|
|
1999
|
+
status: "warn",
|
|
2000
|
+
score: 0,
|
|
2001
|
+
rationale: "No /agent-permissions.json found. Add one to declare AI agent access policy.",
|
|
2002
|
+
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.",
|
|
2003
|
+
fixHint: "Create /agent-permissions.json with read, summarize, cite, and train permission flags."
|
|
2004
|
+
};
|
|
2005
|
+
}
|
|
2006
|
+
});
|
|
2007
|
+
|
|
2008
|
+
// src/rules/aeo/token-length.ts
|
|
2009
|
+
var THRESHOLD_OPTIMAL = 15e3;
|
|
2010
|
+
var THRESHOLD_MAX = 25e3;
|
|
2011
|
+
var aeoTokenLengthRule = defineRule({
|
|
2012
|
+
id: "aeo.token-length",
|
|
2013
|
+
stableId: "aeo.token-length",
|
|
2014
|
+
category: "aeo",
|
|
2015
|
+
group: "diagnostic",
|
|
2016
|
+
weight: 4,
|
|
2017
|
+
impact: "medium",
|
|
2018
|
+
effort: "medium",
|
|
2019
|
+
docsUrl: "https://github.com/BaRam-OSS/geo-checker/blob/main/docs/rules.md#aeotoken-length",
|
|
2020
|
+
title: "Content token length within AI agent limits",
|
|
2021
|
+
title_ko: "\uCF58\uD150\uCE20 \uD1A0\uD070 \uC218 AI \uC5D0\uC774\uC804\uD2B8 \uAD8C\uC7A5 \uBC94\uC704",
|
|
2022
|
+
description: "Pages under 15K tokens are optimal for AI agents (per Addy Osmani's AEO guidance).",
|
|
2023
|
+
run(ctx) {
|
|
2024
|
+
const text = ctx.$("body").text();
|
|
2025
|
+
const tokenEstimate = Math.round(text.length / 3);
|
|
2026
|
+
const evidence = { tokenEstimate, thresholds: { optimal: THRESHOLD_OPTIMAL, max: THRESHOLD_MAX } };
|
|
2027
|
+
if (tokenEstimate <= THRESHOLD_OPTIMAL) {
|
|
2028
|
+
return {
|
|
2029
|
+
status: "pass",
|
|
2030
|
+
score: 1,
|
|
2031
|
+
rationale: `Estimated ~${tokenEstimate.toLocaleString()} tokens \u2014 within optimal range.`,
|
|
2032
|
+
rationale_ko: `\uC608\uC0C1 \uD1A0\uD070 \uC218 ~${tokenEstimate.toLocaleString()} \u2014 \uAD8C\uC7A5 \uBC94\uC704(15K) \uC774\uB0B4\uC785\uB2C8\uB2E4.`,
|
|
2033
|
+
evidence
|
|
2034
|
+
};
|
|
2035
|
+
}
|
|
2036
|
+
if (tokenEstimate <= THRESHOLD_MAX) {
|
|
2037
|
+
return {
|
|
2038
|
+
status: "warn",
|
|
2039
|
+
score: 0.5,
|
|
2040
|
+
rationale: `Estimated ~${tokenEstimate.toLocaleString()} tokens \u2014 exceeds 15K recommendation.`,
|
|
2041
|
+
rationale_ko: `\uC608\uC0C1 \uD1A0\uD070 \uC218 ~${tokenEstimate.toLocaleString()} \u2014 15K \uAD8C\uC7A5\uCE58\uB97C \uCD08\uACFC\uD569\uB2C8\uB2E4.`,
|
|
2042
|
+
fixHint: "Consider splitting content into shorter, focused pages.",
|
|
2043
|
+
evidence
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
return {
|
|
2047
|
+
status: "fail",
|
|
2048
|
+
score: 0,
|
|
2049
|
+
rationale: `Estimated ~${tokenEstimate.toLocaleString()} tokens \u2014 exceeds 25K agent processing limit.`,
|
|
2050
|
+
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.`,
|
|
2051
|
+
fixHint: "Split this page into multiple focused pages under 15K tokens.",
|
|
2052
|
+
evidence
|
|
2053
|
+
};
|
|
2054
|
+
}
|
|
2055
|
+
});
|
|
2056
|
+
|
|
2057
|
+
// src/rules/aeo/index.ts
|
|
2058
|
+
var aeoRules = [aeoSkillMdRule, aeoAgentPermissionsRule, aeoTokenLengthRule];
|
|
2059
|
+
|
|
1930
2060
|
// src/rules/index.ts
|
|
1931
2061
|
var defaultRules = [
|
|
1932
2062
|
...crawlerRules,
|
|
1933
2063
|
...structuredDataRules,
|
|
1934
2064
|
...citationRules,
|
|
1935
|
-
...contentRules
|
|
2065
|
+
...contentRules,
|
|
2066
|
+
...aeoRules
|
|
1936
2067
|
];
|
|
1937
2068
|
|
|
1938
2069
|
// src/config.ts
|
|
@@ -2128,7 +2259,8 @@ var CATEGORY_LABELS = {
|
|
|
2128
2259
|
crawler: "AI Crawler Access",
|
|
2129
2260
|
"structured-data": "Structured Data",
|
|
2130
2261
|
citation: "Citation Signals",
|
|
2131
|
-
content: "Content Structure"
|
|
2262
|
+
content: "Content Structure",
|
|
2263
|
+
aeo: "AEO Stack"
|
|
2132
2264
|
};
|
|
2133
2265
|
function scoreBadge(score) {
|
|
2134
2266
|
const color = score >= 85 ? "brightgreen" : score >= 60 ? "yellow" : "red";
|
|
@@ -2423,7 +2555,8 @@ var CATEGORY_LABELS2 = {
|
|
|
2423
2555
|
crawler: "AI Crawler Access",
|
|
2424
2556
|
"structured-data": "Structured Data",
|
|
2425
2557
|
citation: "Citation Signals",
|
|
2426
|
-
content: "Content Structure"
|
|
2558
|
+
content: "Content Structure",
|
|
2559
|
+
aeo: "AEO Stack"
|
|
2427
2560
|
};
|
|
2428
2561
|
function colorScore(score) {
|
|
2429
2562
|
if (score >= 85) return import_kleur.default.green().bold(`${score}`);
|
|
@@ -2523,7 +2656,8 @@ var CATEGORY_LABELS3 = {
|
|
|
2523
2656
|
crawler: "AI Crawler Access",
|
|
2524
2657
|
"structured-data": "Structured Data",
|
|
2525
2658
|
citation: "Citation Signals",
|
|
2526
|
-
content: "Content Structure"
|
|
2659
|
+
content: "Content Structure",
|
|
2660
|
+
aeo: "AEO Stack"
|
|
2527
2661
|
};
|
|
2528
2662
|
var IMPACT_ORDER = {
|
|
2529
2663
|
critical: 4,
|
|
@@ -2606,13 +2740,15 @@ function partitionResults(report) {
|
|
|
2606
2740
|
crawler: [],
|
|
2607
2741
|
"structured-data": [],
|
|
2608
2742
|
citation: [],
|
|
2609
|
-
content: []
|
|
2743
|
+
content: [],
|
|
2744
|
+
aeo: []
|
|
2610
2745
|
};
|
|
2611
2746
|
const passed = {
|
|
2612
2747
|
crawler: [],
|
|
2613
2748
|
"structured-data": [],
|
|
2614
2749
|
citation: [],
|
|
2615
|
-
content: []
|
|
2750
|
+
content: [],
|
|
2751
|
+
aeo: []
|
|
2616
2752
|
};
|
|
2617
2753
|
for (const cat of Object.keys(report.categories)) {
|
|
2618
2754
|
for (const r of report.categories[cat].results) {
|