How findable is a website on Google and in AI answer engines?
geocheck audits a site the way search engines and AI answer engines (ChatGPT search, Perplexity, Claude, Google AI Overviews) meet it: can their crawlers get in, is the content in the HTML they receive, is the business described as an entity, is there anything short and clear enough to quote. It gives an explainable score and a list of problems, each one a measurement with the page it was measured on.
npx geocheck example.comgeocheck https://www.example.com/ 2026-09-24
30 pages audited (sitemap and internal links), 35 URLs in sitemap
Score 82/100
Crawl & index ████████████████████ 100
On-page ███████████████░░░░░ 76
AI readiness ████████████░░░░░░░░ 60
Local ████████████████████ 100
Crawlers: 11 allowed, 1 blocked (PerplexityBot)
AI signals: 4 question headings, 2 answer blocks, llms.txt absent
Issues (1 high, 3 medium, 6 low, 1 info)
HIGH ai PerplexityBot is blocked in robots.txt: the site cannot be fetched to answer questions in that engine
MEDIUM onpage 3/12 images have no alt attribute /gallery
...
Zero runtime dependencies, Node 20+. Works on live sites and on a dev server
(http://localhost:4321), so you can check a site before it launches.
SEO tools are everywhere. Very few tell you whether an AI answer engine can read and cite your site, and most of what is written about "GEO" is advice you cannot verify. geocheck sticks to what can be measured from the outside and says so when it could not measure something: a robots.txt that did not load is reported as unknown, never as missing, and a site that does not answer stops the audit instead of producing a page of invented problems.
It started as an audit script used by an independent Italian web developer on client sites before launch and on prospects' sites before a first call.
| Category | Checks |
|---|---|
| Crawl & index | http to https redirect, www and apex answering as two sites, redirect chains, robots.txt, Googlebot and Bingbot rules, sitemap (and sitemap indexes), the status of every sitemap URL, URLs on another host, noindex pages listed in the sitemap, intentional indexing exclusions outside it, per-URL crawler restrictions |
| On-page | title, meta description, canonical, viewport, lang, H1, image alt, Open Graph image, thin pages, broken JSON-LD, duplicate titles and descriptions, canonical target errors, redirects and noindex, optional PageSpeed mobile score |
| AI readiness | robots.txt rules for AI search crawlers, training crawlers, and user-triggered fetchers, text that only exists after JavaScript runs, JSON-LD business entity on the home page, the same business split across different @ids, headings phrased as questions, 40-60 word answer paragraphs, llms.txt |
| Local | clickable phone or WhatsApp links, phone numbers that differ between the page and the JSON-LD |
Every rule has a stable id, a severity and a reason in docs/checks.md.
Blocking a training crawler (GPTBot, ClaudeBot, Google-Extended, CCBot, Applebot-Extended) is a legitimate choice and is never reported as a problem. Search crawlers (OAI-SearchBot, PerplexityBot, Claude-SearchBot) are checked separately from user-triggered fetchers (ChatGPT-User, Claude-User). Restrictions on user-triggered fetchers are informational, not a search indexing penalty. In particular, OpenAI states that ChatGPT-User does not determine Search eligibility, and robots.txt may not apply to user-initiated requests.
The robots parser combines repeated matching groups, supports * and $,
compares URL paths and queries using the most specific rule, and lets Allow
win ties. Site summaries distinguish full blocks from partial restrictions;
each audited URL is checked against search crawler rules too. These are
declared rules, not a live test of what a real bot can access. Server errors,
rate limits, and network failures leave rules unknown rather than claiming
that a robots.txt file is missing or that crawling is allowed.
Each category starts at 100 and loses points for every rule that fired: high 20, medium 8, low 3, info 0. A rule that fires on the whole site counts once; a rule that fires on pages costs between half and all of its weight, depending on the share of pages it affects. The overall score weights crawl 30%, on-page 25%, AI readiness 30%, local 15%.
It is a way to compare a site with itself over time, not a ranking factor. Nobody outside Google or OpenAI knows their weights, and geocheck does not pretend to.
geocheck <url> [options]| Option | Default | |
|---|---|---|
--format <text|md|json|html> |
text |
Output format |
-o, --output <file> |
Write the report to a file, print a short summary | |
--max-pages <n> |
30 |
Pages to parse |
--max-check <n> |
200 |
Sitemap URLs to status-check |
--phone-prefix <+cc> |
Country prefix for local numbers, e.g. +39, so 0123 456789 and +39 0123 456789 compare equal |
|
--fail-on <level> |
never |
Exit 1 if an issue at high, medium or low or above exists |
--min-score <n> |
Exit 1 if the overall score is below n |
|
--baseline <file> |
Compare with a previous JSON audit of the same origin | |
--fail-on-new <level> |
With a baseline, exit 1 for new or severity-escalated findings at high, medium, or low or above |
|
--pagespeed |
Add PageSpeed Insights mobile scores (reads PAGESPEED_API_KEY if set) |
|
--timeout <ms> |
15000 |
Per-request timeout |
--quiet |
No progress messages |
Exit codes: 0 done, 1 a threshold was hit, 2 the site did not answer
(nothing was checked), 3 wrong usage.
Pages are seeded from the sitemap, and internal links are also followed.
Without a usable sitemap, the home page is the crawl seed. The page and sitemap
status-check limits are reported as coverage limits, never as a complete crawl. On localhost it reads the local sitemap and maps its
URLs to the dev server, so a site can be checked before it has a domain.
geocheck https://example.com --format html -o report.htmlOpen the file in a browser, including offline. The report includes scores, findings, sampled pages, crawler rules, and coverage notes. Filter findings by severity or category, or search a page or rule. Without JavaScript, all findings remain visible. The file uses no external fonts, scripts, or styles; page content is escaped before embedding. Reports contain the audited site's URLs and contact findings, so review them before sharing.
geocheck https://example.com --format json -o baseline.json
geocheck https://example.com --baseline baseline.json --format html -o changes.html
geocheck https://example.com --baseline baseline.json --fail-on-new highComparison identifies new, resolved, unchanged, and changed findings using stable rule ids and locations. Message changes are kept separate from new findings. A missing or failed page is unverified, never a confirmed fix. Site-wide findings are also unverified when samples differ or are truncated. Missing optional measurements (such as PageSpeed) do not count as fixes. Comparisons require the same origin; reports from version 0.1.0 are accepted too.
--fail-on-new considers new findings and severity increases. Existing high
findings do not fail that gate. --fail-on and --min-score still apply to the
full current report independently. Baselines must be JSON, not HTML or Markdown.
- uses: Axeeh/geocheck@v0
with:
url: https://staging.example.com
fail-on: highThe action writes the Markdown report to the job summary. Inputs: url
(required), fail-on (default high), min-score, max-pages, version
(npm version, default latest).
import { runAudit, formatMarkdown } from "geocheck";
const result = await runAudit({ url: "https://example.com", maxPages: 50 });
console.log(result.score.overall, result.issues.filter((i) => i.severity === "high"));formatHtml, compareAudits, readBaseline, and evaluateRobots are also exported.
result has site (redirects, robots rules per crawler, sitemap, contacts,
AI measures), pages (everything measured per page), issues and score.
The JSON output is the same object; with --baseline it also includes comparison.
- It does not run JavaScript. That is on purpose: most AI crawlers do not either, so "the text only appears after JavaScript" is one of the findings.
- It does not ask an AI engine whether it cites you. That is the next thing on the roadmap, and until then a clean report is not proof of being cited.
- It does not read Google Search Console, the Google Business Profile or reviews. Local SEO beyond the site itself needs those.
- It does not fix anything. Every issue says what was measured and where.
- Field test: ask answer engines the questions a customer would ask ("bakery open on Sunday near Riverside") and report whether the site is cited, and who is cited instead. Bring your own API key.
- Standalone HTML report to send to a client
- Localized issue messages (Italian first)
- Compare two runs: what got fixed, what is new
-
hreflangand multi-language checks
pnpm install
pnpm test
pnpm run typecheck
pnpm run build
pnpm dev https://example.comRule tests use in-memory fake sites; CLI integration tests use a temporary loopback-only HTTP server. No Internet connection is needed. See CONTRIBUTING.md.