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/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CheerioAPI } from 'cheerio';
|
|
2
2
|
|
|
3
3
|
type Status = 'pass' | 'warn' | 'fail' | 'skip';
|
|
4
|
-
type Category = 'crawler' | 'structured-data' | 'citation' | 'content';
|
|
4
|
+
type Category = 'crawler' | 'structured-data' | 'citation' | 'content' | 'aeo';
|
|
5
5
|
interface RobotsRuleGroup {
|
|
6
6
|
userAgent: string;
|
|
7
7
|
allow: string[];
|
|
@@ -46,6 +46,8 @@ interface AuditContext {
|
|
|
46
46
|
renderMode: 'static' | 'rendered';
|
|
47
47
|
fetchedAt: string;
|
|
48
48
|
warnings: string[];
|
|
49
|
+
skillMd: string | null;
|
|
50
|
+
agentPermissions: unknown | null;
|
|
49
51
|
}
|
|
50
52
|
type Impact = 'critical' | 'high' | 'medium' | 'low';
|
|
51
53
|
type Effort = 'low' | 'medium' | 'high';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CheerioAPI } from 'cheerio';
|
|
2
2
|
|
|
3
3
|
type Status = 'pass' | 'warn' | 'fail' | 'skip';
|
|
4
|
-
type Category = 'crawler' | 'structured-data' | 'citation' | 'content';
|
|
4
|
+
type Category = 'crawler' | 'structured-data' | 'citation' | 'content' | 'aeo';
|
|
5
5
|
interface RobotsRuleGroup {
|
|
6
6
|
userAgent: string;
|
|
7
7
|
allow: string[];
|
|
@@ -46,6 +46,8 @@ interface AuditContext {
|
|
|
46
46
|
renderMode: 'static' | 'rendered';
|
|
47
47
|
fetchedAt: string;
|
|
48
48
|
warnings: string[];
|
|
49
|
+
skillMd: string | null;
|
|
50
|
+
agentPermissions: unknown | null;
|
|
49
51
|
}
|
|
50
52
|
type Impact = 'critical' | 'high' | 'medium' | 'low';
|
|
51
53
|
type Effort = 'low' | 'medium' | 'high';
|
package/dist/index.js
CHANGED
|
@@ -255,10 +255,12 @@ async function buildContext(url, opts = {}) {
|
|
|
255
255
|
"Site appears to be JS-rendered (sparse body + SPA root element). Re-run with --render for accurate results."
|
|
256
256
|
);
|
|
257
257
|
}
|
|
258
|
-
const [robotsRaw, llmsRaw, llmsFullRaw] = await Promise.all([
|
|
258
|
+
const [robotsRaw, llmsRaw, llmsFullRaw, skillMdRaw, agentPermissionsRaw] = await Promise.all([
|
|
259
259
|
fetchText(`${origin}/robots.txt`, opts),
|
|
260
260
|
fetchText(`${origin}/llms.txt`, opts),
|
|
261
|
-
fetchText(`${origin}/llms-full.txt`, opts)
|
|
261
|
+
fetchText(`${origin}/llms-full.txt`, opts),
|
|
262
|
+
fetchText(`${origin}/skill.md`, opts),
|
|
263
|
+
fetchText(`${origin}/agent-permissions.json`, opts)
|
|
262
264
|
]);
|
|
263
265
|
let sitemapUrl = null;
|
|
264
266
|
const robots = robotsRaw ? parseRobots(robotsRaw) : null;
|
|
@@ -266,6 +268,13 @@ async function buildContext(url, opts = {}) {
|
|
|
266
268
|
if (!sitemapUrl) sitemapUrl = `${origin}/sitemap.xml`;
|
|
267
269
|
const sitemapRaw = await fetchText(sitemapUrl, opts);
|
|
268
270
|
const sitemap = sitemapRaw ? parseSitemap(sitemapRaw) : null;
|
|
271
|
+
let agentPermissions = null;
|
|
272
|
+
if (agentPermissionsRaw && agentPermissionsRaw.trim().length > 0) {
|
|
273
|
+
try {
|
|
274
|
+
agentPermissions = JSON.parse(agentPermissionsRaw);
|
|
275
|
+
} catch {
|
|
276
|
+
}
|
|
277
|
+
}
|
|
269
278
|
return {
|
|
270
279
|
url,
|
|
271
280
|
finalUrl,
|
|
@@ -280,7 +289,9 @@ async function buildContext(url, opts = {}) {
|
|
|
280
289
|
jsonLd: extractJsonLd($),
|
|
281
290
|
renderMode,
|
|
282
291
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
283
|
-
warnings
|
|
292
|
+
warnings,
|
|
293
|
+
skillMd: skillMdRaw && skillMdRaw.trim().length > 0 ? skillMdRaw : null,
|
|
294
|
+
agentPermissions
|
|
284
295
|
};
|
|
285
296
|
}
|
|
286
297
|
|
|
@@ -289,10 +300,11 @@ function defineRule(rule) {
|
|
|
289
300
|
return rule;
|
|
290
301
|
}
|
|
291
302
|
var CATEGORY_WEIGHTS = {
|
|
292
|
-
crawler:
|
|
293
|
-
"structured-data":
|
|
294
|
-
citation:
|
|
295
|
-
content:
|
|
303
|
+
crawler: 20,
|
|
304
|
+
"structured-data": 25,
|
|
305
|
+
citation: 20,
|
|
306
|
+
content: 15,
|
|
307
|
+
aeo: 20
|
|
296
308
|
};
|
|
297
309
|
|
|
298
310
|
// src/engine.ts
|
|
@@ -306,7 +318,8 @@ async function runRules(ctx, rules, opts = {}) {
|
|
|
306
318
|
crawler: { score: 0, weight: weights.crawler, results: [] },
|
|
307
319
|
"structured-data": { score: 0, weight: weights["structured-data"], results: [] },
|
|
308
320
|
citation: { score: 0, weight: weights.citation, results: [] },
|
|
309
|
-
content: { score: 0, weight: weights.content, results: [] }
|
|
321
|
+
content: { score: 0, weight: weights.content, results: [] },
|
|
322
|
+
aeo: { score: 0, weight: weights.aeo, results: [] }
|
|
310
323
|
};
|
|
311
324
|
for (const rule of rules) {
|
|
312
325
|
if (onlySet && !onlySet.has(rule.id) && (!rule.stableId || !onlySet.has(rule.stableId))) continue;
|
|
@@ -1896,12 +1909,130 @@ var contentRules = [
|
|
|
1896
1909
|
externalCitationsRule
|
|
1897
1910
|
];
|
|
1898
1911
|
|
|
1912
|
+
// src/rules/aeo/skill-md.ts
|
|
1913
|
+
var aeoSkillMdRule = defineRule({
|
|
1914
|
+
id: "aeo.skill-md",
|
|
1915
|
+
stableId: "aeo.skill-md",
|
|
1916
|
+
category: "aeo",
|
|
1917
|
+
group: "opportunity",
|
|
1918
|
+
weight: 3,
|
|
1919
|
+
impact: "high",
|
|
1920
|
+
effort: "low",
|
|
1921
|
+
docsUrl: "https://github.com/BaRam-OSS/geo-checker/blob/main/docs/rules.md#aeoskill-md",
|
|
1922
|
+
title: "skill.md is present",
|
|
1923
|
+
title_ko: "skill.md \uD30C\uC77C \uC874\uC7AC \uC5EC\uBD80",
|
|
1924
|
+
description: "A /skill.md file describes site capabilities so AI agents know what this site can do for them.",
|
|
1925
|
+
run(ctx) {
|
|
1926
|
+
if (ctx.skillMd !== null) {
|
|
1927
|
+
return {
|
|
1928
|
+
status: "pass",
|
|
1929
|
+
score: 1,
|
|
1930
|
+
rationale: "skill.md found at site root.",
|
|
1931
|
+
rationale_ko: "skill.md\uAC00 \uC0AC\uC774\uD2B8 \uB8E8\uD2B8\uC5D0 \uC874\uC7AC\uD569\uB2C8\uB2E4."
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
return {
|
|
1935
|
+
status: "warn",
|
|
1936
|
+
score: 0,
|
|
1937
|
+
rationale: "No /skill.md found. Add one to describe your site capabilities to AI agents.",
|
|
1938
|
+
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.",
|
|
1939
|
+
fixHint: "Create /skill.md listing what services, products, and capabilities this site offers."
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
});
|
|
1943
|
+
|
|
1944
|
+
// src/rules/aeo/agent-permissions.ts
|
|
1945
|
+
var aeoAgentPermissionsRule = defineRule({
|
|
1946
|
+
id: "aeo.agent-permissions",
|
|
1947
|
+
stableId: "aeo.agent-permissions",
|
|
1948
|
+
category: "aeo",
|
|
1949
|
+
group: "opportunity",
|
|
1950
|
+
weight: 3,
|
|
1951
|
+
impact: "medium",
|
|
1952
|
+
effort: "low",
|
|
1953
|
+
docsUrl: "https://github.com/BaRam-OSS/geo-checker/blob/main/docs/rules.md#aeoagent-permissions",
|
|
1954
|
+
title: "agent-permissions.json is present",
|
|
1955
|
+
title_ko: "agent-permissions.json \uD30C\uC77C \uC874\uC7AC \uC5EC\uBD80",
|
|
1956
|
+
description: "Declares explicit read/summarize/cite/train permissions for AI agents.",
|
|
1957
|
+
run(ctx) {
|
|
1958
|
+
if (ctx.agentPermissions !== null) {
|
|
1959
|
+
return {
|
|
1960
|
+
status: "pass",
|
|
1961
|
+
score: 1,
|
|
1962
|
+
rationale: "agent-permissions.json found at site root.",
|
|
1963
|
+
rationale_ko: "agent-permissions.json\uC774 \uC0AC\uC774\uD2B8 \uB8E8\uD2B8\uC5D0 \uC874\uC7AC\uD569\uB2C8\uB2E4.",
|
|
1964
|
+
evidence: ctx.agentPermissions
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
return {
|
|
1968
|
+
status: "warn",
|
|
1969
|
+
score: 0,
|
|
1970
|
+
rationale: "No /agent-permissions.json found. Add one to declare AI agent access policy.",
|
|
1971
|
+
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.",
|
|
1972
|
+
fixHint: "Create /agent-permissions.json with read, summarize, cite, and train permission flags."
|
|
1973
|
+
};
|
|
1974
|
+
}
|
|
1975
|
+
});
|
|
1976
|
+
|
|
1977
|
+
// src/rules/aeo/token-length.ts
|
|
1978
|
+
var THRESHOLD_OPTIMAL = 15e3;
|
|
1979
|
+
var THRESHOLD_MAX = 25e3;
|
|
1980
|
+
var aeoTokenLengthRule = defineRule({
|
|
1981
|
+
id: "aeo.token-length",
|
|
1982
|
+
stableId: "aeo.token-length",
|
|
1983
|
+
category: "aeo",
|
|
1984
|
+
group: "diagnostic",
|
|
1985
|
+
weight: 4,
|
|
1986
|
+
impact: "medium",
|
|
1987
|
+
effort: "medium",
|
|
1988
|
+
docsUrl: "https://github.com/BaRam-OSS/geo-checker/blob/main/docs/rules.md#aeotoken-length",
|
|
1989
|
+
title: "Content token length within AI agent limits",
|
|
1990
|
+
title_ko: "\uCF58\uD150\uCE20 \uD1A0\uD070 \uC218 AI \uC5D0\uC774\uC804\uD2B8 \uAD8C\uC7A5 \uBC94\uC704",
|
|
1991
|
+
description: "Pages under 15K tokens are optimal for AI agents (per Addy Osmani's AEO guidance).",
|
|
1992
|
+
run(ctx) {
|
|
1993
|
+
const text = ctx.$("body").text();
|
|
1994
|
+
const tokenEstimate = Math.round(text.length / 3);
|
|
1995
|
+
const evidence = { tokenEstimate, thresholds: { optimal: THRESHOLD_OPTIMAL, max: THRESHOLD_MAX } };
|
|
1996
|
+
if (tokenEstimate <= THRESHOLD_OPTIMAL) {
|
|
1997
|
+
return {
|
|
1998
|
+
status: "pass",
|
|
1999
|
+
score: 1,
|
|
2000
|
+
rationale: `Estimated ~${tokenEstimate.toLocaleString()} tokens \u2014 within optimal range.`,
|
|
2001
|
+
rationale_ko: `\uC608\uC0C1 \uD1A0\uD070 \uC218 ~${tokenEstimate.toLocaleString()} \u2014 \uAD8C\uC7A5 \uBC94\uC704(15K) \uC774\uB0B4\uC785\uB2C8\uB2E4.`,
|
|
2002
|
+
evidence
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
if (tokenEstimate <= THRESHOLD_MAX) {
|
|
2006
|
+
return {
|
|
2007
|
+
status: "warn",
|
|
2008
|
+
score: 0.5,
|
|
2009
|
+
rationale: `Estimated ~${tokenEstimate.toLocaleString()} tokens \u2014 exceeds 15K recommendation.`,
|
|
2010
|
+
rationale_ko: `\uC608\uC0C1 \uD1A0\uD070 \uC218 ~${tokenEstimate.toLocaleString()} \u2014 15K \uAD8C\uC7A5\uCE58\uB97C \uCD08\uACFC\uD569\uB2C8\uB2E4.`,
|
|
2011
|
+
fixHint: "Consider splitting content into shorter, focused pages.",
|
|
2012
|
+
evidence
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
2015
|
+
return {
|
|
2016
|
+
status: "fail",
|
|
2017
|
+
score: 0,
|
|
2018
|
+
rationale: `Estimated ~${tokenEstimate.toLocaleString()} tokens \u2014 exceeds 25K agent processing limit.`,
|
|
2019
|
+
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.`,
|
|
2020
|
+
fixHint: "Split this page into multiple focused pages under 15K tokens.",
|
|
2021
|
+
evidence
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
});
|
|
2025
|
+
|
|
2026
|
+
// src/rules/aeo/index.ts
|
|
2027
|
+
var aeoRules = [aeoSkillMdRule, aeoAgentPermissionsRule, aeoTokenLengthRule];
|
|
2028
|
+
|
|
1899
2029
|
// src/rules/index.ts
|
|
1900
2030
|
var defaultRules = [
|
|
1901
2031
|
...crawlerRules,
|
|
1902
2032
|
...structuredDataRules,
|
|
1903
2033
|
...citationRules,
|
|
1904
|
-
...contentRules
|
|
2034
|
+
...contentRules,
|
|
2035
|
+
...aeoRules
|
|
1905
2036
|
];
|
|
1906
2037
|
|
|
1907
2038
|
// src/config.ts
|
|
@@ -2097,7 +2228,8 @@ var CATEGORY_LABELS = {
|
|
|
2097
2228
|
crawler: "AI Crawler Access",
|
|
2098
2229
|
"structured-data": "Structured Data",
|
|
2099
2230
|
citation: "Citation Signals",
|
|
2100
|
-
content: "Content Structure"
|
|
2231
|
+
content: "Content Structure",
|
|
2232
|
+
aeo: "AEO Stack"
|
|
2101
2233
|
};
|
|
2102
2234
|
function scoreBadge(score) {
|
|
2103
2235
|
const color = score >= 85 ? "brightgreen" : score >= 60 ? "yellow" : "red";
|