rabbot-seo

module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 20, 2026 License: AGPL-3.0

README ΒΆ

Rabbot-SEO

A little robot rabbit that watches your SEO so you don't have to β€” and pings your Slack the moment a page regresses.

Release Build Coverage Go Report Card Go Reference License Platforms

Rabbot-SEO is a single small Go binary that keeps an eye on your site around the clock. When something that quietly wrecks your search ranking changes β€” a page slips to noindex, a canonical vanishes, a title or <h1> mutates, a page starts throwing 5xx β€” Rabbot-SEO catches it on its next pass and tells you in Slack, usually long before Google does. It's self-hosted and dependency-free at runtime: state lives in a local SQLite file, there's no SaaS, no subscription, and nothing to run but Rabbot-SEO itself. And you barely need the terminal: connect it to Claude and run the whole thing just by chatting (see below).

I named it Rabbot-SEO β€” a rabbit crossed with a robot, keeping watch over your SEO. I've always had a soft spot for rabbits: my first pet was one, and these days one lives on my neck. 🐰

🐰 What it watches

Every recheck captures the whole SEO surface of a page β€” title, meta description, robots directives, canonical, hreflang, the full heading outline, link and image counts, Open Graph / Twitter / JSON-LD, word count, the redirect chain, and a computed indexability verdict β€” then diffs it against last time. Critical regressions (a noindex, a lost canonical, a 4xx/5xx, an indexability flip) alert immediately; smaller edits (title, description, h1) alert in real time too, up to a per-recipient hourly cap β€” once a noisy hour trips that cap, the overflow rolls into a periodic digest instead of stampeding your channel. That's the whole job: catch the change that costs you traffic, and say something.

See it in action

Example Slack alert from Rabbot-SEO
Doctor report for https://example.com
Verdict: GREEN
The SEO content is in the server's HTML β€” fully recoverable without a browser.

Preflight:
  homepage status: 200
  fetch class:     ok
  robots:          allowed (status 200)

Rendering check:
  render mode:     server_rendered (confidence: high)
  visible words:   1843
  script bytes:    20480

Coverage: ~1,200 pages Β· ~1.00 req/s Β· full pass β‰ˆ 20m Β· ~14.0 MB on disk (go faster by verifying ownership + raising speed). Rechecks every ~15m.

Two surfaces, on purpose

Most monitors hand you a dashboard and quietly make you the polling loop. Rabbot-SEO bets the other way β€” as agents get capable, the screen matters less β€” so it's built around two surfaces for humans, and everything else is optional:

  • Slack β€” it tells you. When a page regresses, the alert comes to you, in real time. No tab to keep open, nothing to remember to check.
  • An agent β€” you ask it. Connect Claude once over MCP and you run the whole monitor by chatting β€” "what changed this week?", "is the homepage still indexable?", "draw this 404's blast radius" β€” and it takes safe, confirmed actions.

The CLI is the bedrock both rest on, and there's a Grafana dashboard and a live TUI if you want a screen β€” but you shouldn't need one. Slack pushes; an agent answers.

πŸ€– The easy way β€” run it through Claude

You shouldn't have to babysit a CLI. Rabbot-SEO is built to be driven by an LLM β€” point Claude at it once, and from then on you just talk to your SEO monitor.

Set it up. Paste this into Claude Code (or Claude Desktop with shell access):

Set up Rabbot-SEO for me. Read https://github.com/roberto-grasiano/rabbot-seo/blob/main/docs/install-with-claude.md and follow it: detect my OS, install rabbot the best way for my system, get https://MY-SITE.com monitored, and connect yourself to it over MCP. Ask me anything you need.

Claude detects your environment, installs Rabbot-SEO through the right channel, gets your first site monitored, can set up the 24/7 service, and wires itself up over MCP β€” checking in with you at each step.

Then just ask. Once connected, you run the whole monitor from inside Claude:

"What changed on my site this week?" β€” "Add my staging site." β€” "Is the homepage still indexable?" β€” "Verify ownership of example.com." β€” "Mute that issue."

Rabbot-SEO answers with status, history, issues, and a cross-site digest, and takes safe, confirmed actions (every change asks first). It works against a local daemon or one on a VPS, over SSH. Full tool catalog β†’ Claude / MCP.

Rabbot-SEO speaks the open Model Context Protocol, so any MCP-capable client can connect β€” Claude is the paved path.

Prefer to drive it yourself?

Rabbot-SEO is one static binary with no runtime dependencies β€” install it and use the CLI directly. Pick your platform:

Platform Install
macOS / Linux β€” Homebrew brew install roberto-grasiano/rabbot-seo/rabbot
Windows β€” Scoop scoop bucket add rabbot-seo https://github.com/roberto-grasiano/scoop-rabbot-seo
scoop install rabbot
Linux / macOS β€” script curl -fsSL https://raw.githubusercontent.com/roberto-grasiano/rabbot-seo/main/install.sh | sh
Docker docker pull ghcr.io/roberto-grasiano/rabbot-seo:latest
From source β€” Go β‰₯ 1.26.3 go install github.com/roberto-grasiano/rabbot-seo/cmd/rabbot@latest

Then run rabbot init and answer three plain questions β€” what site do you want to watch, you're allowed to, right?, and a contact email β€” and Rabbot-SEO writes its config, starts watching (politely, throttled until you verify ownership), and shows you what it just read:

rabbot init
# ...or headless / CI, in one line:
rabbot init --contact-email you@you.example --site https://example.com --i-am-authorized --start

From any shell:

rabbot status                       # counts, queue, last-crawl time
rabbot inspect https://example.com  # everything Rabbot-SEO knows about one URL
rabbot history https://example.com  # the change log for a URL
rabbot report                       # a cross-site "what changed lately" digest

Confirm the install with rabbot version; pre-built packages ship with each release, Sigstore-signed with a per-archive SBOM (verify a download). The macOS/Windows binaries aren't OS-notarized, so Homebrew/Scoop clear Gatekeeper/SmartScreen for you. Full setup and config reference: docs/configuration.md.

Where to run Rabbot-SEO

Real-time monitoring quietly assumes an always-on box β€” Rabbot-SEO catches a regression on its next pass, so it has to be awake to pass. That shapes where to run it:

  • On your laptop β€” perfect for trying Rabbot-SEO, and a genuinely useful setup for watching a staging or local site (catch SEO regressions before they ship to production). But a laptop sleeps when you close the lid, and monitoring pauses with it β€” there's a gap whenever it's asleep. Great for evaluation and staging; not the home for 24/7 watching.
  • On an always-on box β€” to actually catch live regressions hours before Search Console does, give Rabbot-SEO a machine that never sleeps. It's one static binary with no runtime deps, so the choice is just whatever you already have:
Run target Good for Guide
πŸ–₯️ VPS (Hetzner / DigitalOcean / Hostinger) the simplest always-on box, no hardware docs/vps.md
πŸ“ Raspberry Pi a low-power home server, no cloud bill docs/raspberry-pi.md
🍎 Mac mini a quiet desktop you already own docs/mac-mini.md
🐳 Docker a container with restart: unless-stopped docker-compose.yml

Now vs after reboot: the wizard (rabbot init) starts monitoring now β€” a detached process that watches until you log out or the machine reboots. To keep it watching after a reboot, install it as a service: rabbot service install (systemd on Linux, a per-user LaunchAgent on macOS, the Service Manager on Windows). The wizard's "Keep watching 24/7" step does exactly this.

Get Slack alerts in 2 steps

  1. In Slack, add an Incoming Webhook and copy its URL (treat it like a password).
  2. Hand it to Rabbot-SEO, keeping the secret in your environment:
export RABBOT_SLACK_WEBHOOK='https://hooks.slack.com/services/XXX/YYY/ZZZ'
rabbot init --contact-email you@you.example --site https://example.com \
  --i-am-authorized --slack-webhook '${RABBOT_SLACK_WEBHOOK}'

That wires a Slack notifier plus a catch-all route and sends a test alert. The ${…} token is stored verbatim β€” the real URL stays in your environment and is never written to the config or logged. Want only the serious stuff, or per-site routing? See Slack routing. (Or just ask Claude: "set up Slack alerts for Rabbot-SEO.")

What else it does

  • Whole-site discovery β€” reads robots.txt, expands sitemap indexes (gzip-aware), and follows same-host links to catch orphans, all behind an SSRF check and a per-site page cap. β†’ crawl speed & coverage
  • Sitemap watching β€” rechecks each site's sitemap on a cadence and alerts when it breaks (a 200β†’4xx/5xx regression) or when its URL set shifts (added/dropped pages, with before/after counts and sample paths) β€” a partial fetch never masquerades as a mass drop.
  • Coverage reconciliation β€” reconciles what the sitemap declares against what's actually been crawled, surfacing declared-but-uncrawled and crawled-but-unlisted URLs, and alerts when that drift grows. Read it any time with rabbot sitemap coverage or the get_coverage tool.
  • Rich-result eligibility β€” validates the JSON-LD a page already ships against a versioned Rabbot profile that mostly mirrors Google's rich-result requirements but adds a few deliberate Rabbot policy checks stricter than Google's literal wording (e.g. it flags an Article that ships no headline β€” Google lists that as recommended, not required), so a deploy that drops offers from Product markup (with the @type set unchanged, invisible to a plain diff) pages critical within one recheck interval β€” hours before Search Console notices. Presence-driven: it never suggests adding markup you don't have. β†’ the missing-offers demo
  • Google Search Console (read-only, self-hosted) β€” connect a site to its Search Console property and Rabbot reads Google's own ground truth: whether a URL is indexed, which canonical Google chose vs the one you declared, and its search performance. It pages on only the high-signal disagreements β€” an index-status discrepancy or a Google-canonical mismatch β€” and correlates a change against the page's search performance after the fact; it deliberately never fires standalone traffic/ranking-drop noise. Your GSC data lands in your own SQLite file and never leaves the box. β†’ Search Console
  • Segments β€” name the slices that matter (/blog, /product) and every surface speaks in those terms: route /blog/** alerts to #content and /product/** to #growth, filter rabbot issues/report by segment, and scope an agent's questions through MCP. β†’ segments
  • Health score β€” rolls the impact points it already scores per issue into a per-site and per-segment 0–100 number recorded over time; importance-weighted, page-capped, and honest under cold start (it shows β€”, never a fake 100, until it has seen enough of the site). β†’ the scoring model
  • Adaptive cadence β€” each URL speeds up when it keeps changing and eases off while it's stable; importance orders the queue. β†’ crawl speed & coverage
  • Polite by design β€” a hard rate floor, robots.txt Crawl-delay always wins, and going faster than baseline requires proving you own the site. β†’ crawl speed & coverage
  • Prove ownership β€” rabbot verify (well-known file, DNS TXT, or meta tag) lifts the throttle to full speed. β†’ verification & crawler identity
  • Look before you leap β€” rabbot doctor <url> previews reachability, robots, an honest JS-rendering hint, and a crawl-size estimate. β†’ crawl speed & coverage
  • Recovers JS sites without a browser β€” it never renders JavaScript; it reads the hydration payloads frameworks ship in the HTML, grades each page's render mode, and reports what it genuinely can't see instead of faking it. β†’ recover, don't render
  • Stays small β€” a background retention sweep trims old snapshots so the SQLite file doesn't grow forever. β†’ configuration

Going further

Guide What's in it
🧭 Architecture the system on one page β€” how a recheck flows, the codemap, the invariants
πŸ€– Claude / MCP the full tool catalog; connect Claude locally or over SSH
πŸ”Ž Google Search Console Google's index status, chosen canonical, and search performance β€” read-only, on your box
🚫 Recover, don't render why the binary refuses to render JS β€” and recovers your content anyway
🐒 Crawl speed & coverage request rate, the page cap, the estimator, doctor, being a good neighbor
⚑ Performance & capacity measured cost per page, how many URLs fit on 1 vCPU / 1 GB, reproduce it yourself
πŸ” Verification & crawler identity prove ownership; how site owners see your crawler
πŸ–₯️ Run on a VPS Hetzner / DigitalOcean / Hostinger, with no inbound ports
πŸ“ Run on a Raspberry Pi 64-bit OS, USB-SSD for SQLite, systemd, sizing
🍎 Run on a Mac mini Homebrew, a per-user LaunchAgent, auto-login, no-sleep
πŸ“Š Self-observability /metrics, the provisioned Grafana dashboard
βš™οΈ Configuration precedence, Slack routing, reading changes, retention
πŸ”„ Updating Rabbot swap the binary, restart β€” data + config untouched; migrations auto-apply
πŸ“¦ Releasing how releases are cut

Build

make build   # CGO_ENABLED=0 static binary
make test    # go test -race ./...   (the gate)
make lint    # golangci-lint v2

Rabbot-SEO is a single static binary by design β€” CGO_ENABLED=0, pure-Go SQLite (modernc.org/sqlite), no cgo, ever. How the system fits together is one page: ARCHITECTURE.md. Engineering conventions and the roadmap live in CLAUDE.md.

License

AGPL-3.0. Free and open β€” and copyleft: anyone who runs a modified Rabbot-SEO as a network service has to share their source too, so it can't quietly become someone else's closed product.

Directories ΒΆ

Path Synopsis
cmd
rabbot command
Command rabbot is the entrypoint for the Rabbot-SEO real-time SEO monitoring gateway.
Command rabbot is the entrypoint for the Rabbot-SEO real-time SEO monitoring gateway.
internal
alerts
Package alerts is the incident state machine: it dedups, groups, throttles, and auto-closes incidents, suppressing SEO alerts when a fetch is not ok and instead maintaining operational monitoring_* incidents.
Package alerts is the incident state machine: it dedups, groups, throttles, and auto-closes incidents, suppressing SEO alerts when a fetch is not ok and instead maintaining operational monitoring_* incidents.
behavior
Package behavior is a synthetic behavioral golden suite that stress-tests the per-site-type SIGNAL/NOISE behavior of the Rabbot SEO change/regression engine.
Package behavior is a synthetic behavioral golden suite that stress-tests the per-site-type SIGNAL/NOISE behavior of the Rabbot SEO change/regression engine.
benchcorpus
Package benchcorpus is a deterministic, dependency-free synthetic-HTML generator.
Package benchcorpus is a deterministic, dependency-free synthetic-HTML generator.
cli
Package cli implements the cobra command tree.
Package cli implements the cobra command tree.
config
Package config defines the Config schema and loads/merges/validates it from defaults -> config.yaml -> RABBOT_ env -> CLI flags via koanf.
Package config defines the Config schema and loads/merges/validates it from defaults -> config.yaml -> RABBOT_ env -> CLI flags via koanf.
control
Package control implements the loopback HTTP control plane (server + client) with bearer-token auth, bound to 127.0.0.1 only.
Package control implements the loopback HTTP control plane (server + client) with bearer-token auth, bound to 127.0.0.1 only.
coverage
Package coverage estimates how long a full crawl pass takes and roughly how much disk it uses, from the resolved crawl budget.
Package coverage estimates how long a full crawl pass takes and roughly how much disk it uses, from the resolved crawl budget.
diff
Package diff compares a new snapshot to the latest stored snapshot and emits model.Change records, classifying main-content changes as cosmetic vs substantive via 64-bit SimHash Hamming distance.
Package diff compares a new snapshot to the latest stored snapshot and emits model.Change records, classifying main-content changes as cosmetic vs substantive via 64-bit SimHash Hamming distance.
discovery
Package discovery finds the pages of a site to monitor: recursive sitemap expansion (robots Sitemap: directive, index, gzip) and bounded same-host link-following.
Package discovery finds the pages of a site to monitor: recursive sitemap expansion (robots Sitemap: directive, index, gzip) and bounded same-host link-following.
fetcher
Package fetcher fetches URLs over net/http, capturing the full redirect chain, response timing, and headers, and classifies every response into a model.FetchClass (Β§5A).
Package fetcher fetches URLs over net/http, capturing the full redirect chain, response timing, and headers, and classifies every response into a model.FetchClass (Β§5A).
frontier
Package frontier enforces per-host politeness (rate + concurrency) and robots.txt allow/disallow/crawl-delay (RFC 9309, crawl-delay honored).
Package frontier enforces per-host politeness (rate + concurrency) and robots.txt allow/disallow/crawl-delay (RFC 9309, crawl-delay honored).
fsatomic
Package fsatomic provides a single crash-atomic, power-loss-durable file write used wherever Rabbot persists a config or secret-adjacent file.
Package fsatomic provides a single crash-atomic, power-loss-durable file write used wherever Rabbot persists a config or secret-adjacent file.
gsc
Package gsc is a small, hand-rolled Google Search Console API client for the three endpoints Rabbot needs β€” sites.list, searchAnalytics.query, and urlInspection.index.inspect β€” plus the two BYO auth flows (service-account JWT and OAuth2 installed-app refresh token).
Package gsc is a small, hand-rolled Google Search Console API client for the three endpoints Rabbot needs β€” sites.list, searchAnalytics.query, and urlInspection.index.inspect β€” plus the two BYO auth flows (service-account JWT and OAuth2 installed-app refresh token).
hostinfo
Package hostinfo answers one best-effort question: does this host look like a machine that sleeps? It is a pure-Go, build-tagged, dependency-light probe used only to decide whether `rabbot init` prints a one-line "this looks like a laptop" nudge at go-live.
Package hostinfo answers one best-effort question: does this host look like a machine that sleeps? It is a pure-Go, build-tagged, dependency-light probe used only to decide whether `rabbot init` prints a one-line "this looks like a laptop" nudge at go-live.
humanize
Package humanize holds tiny display-formatting helpers shared by leaf packages (cli, wizard) that must agree byte-for-byte on operator-facing strings.
Package humanize holds tiny display-formatting helpers shared by leaf packages (cli, wizard) that must agree byte-for-byte on operator-facing strings.
hydration
Package hydration locates and decodes the embedded framework state payloads that meta-framework pages ship in their initial HTML β€” Next.js __NEXT_DATA__ (JSON), Nuxt 3 __NUXT_DATA__ (the "devalue" array-with-index-references wire format), and React Server Components __next_f flight rows β€” and recovers the SEO signals (title, meta description, canonical, JSON-LD, prose) carried in them.
Package hydration locates and decodes the embedded framework state payloads that meta-framework pages ship in their initial HTML β€” Next.js __NEXT_DATA__ (JSON), Nuxt 3 __NUXT_DATA__ (the "devalue" array-with-index-references wire format), and React Server Components __next_f flight rows β€” and recovers the SEO signals (title, meta description, canonical, JSON-LD, prose) carried in them.
linkgraph
Package linkgraph is the A9 link-graph LITE service half: it maintains the internal-link edge set incrementally on the crawl path, derives the cross-URL monitor signals (page_orphaned, inlink_loss, click_depth_regression) from the edge delta and the periodic BFS sweep, and serves the bounded get_link_graph export.
Package linkgraph is the A9 link-graph LITE service half: it maintains the internal-link edge set incrementally on the crawl path, derives the cross-URL monitor signals (page_orphaned, inlink_loss, click_depth_regression) from the edge delta and the periodic BFS sweep, and serves the bounded get_link_graph export.
mcp
Package mcpsrv exposes the Rabbot-SEO Model Context Protocol (MCP) server over STDIO ONLY.
Package mcpsrv exposes the Rabbot-SEO Model Context Protocol (MCP) server over STDIO ONLY.
model
Package model holds the shared data entities, enums, and operational constants.
Package model holds the shared data entities, enums, and operational constants.
notify
Package notify defines the backend-agnostic Notifier interface, a route-aware registry/dispatcher, and the Slack Incoming-Webhook backend.
Package notify defines the backend-agnostic Notifier interface, a route-aware registry/dispatcher, and the Slack Incoming-Webhook backend.
obs
Package obs configures structured logging (slog), provides a rotating file-log writer (lumberjack) for callers that opt into file output, and defines the canonical log-attribute key constants.
Package obs configures structured logging (slog), provides a rotating file-log writer (lumberjack) for callers that opt into file output, and defines the canonical log-attribute key constants.
precheck
Package precheck performs an honest, pure-Go pre-flight for a URL: it reuses the existing fetcher.Doctor preflight (robots/egress/blocked/UA) and adds a calibrated HINT about whether a page's SEO content is visible to Rabbot without executing JavaScript.
Package precheck performs an honest, pure-Go pre-flight for a URL: it reuses the existing fetcher.Doctor preflight (robots/egress/blocked/UA) and adds a calibrated HINT about whether a page's SEO content is visible to Rabbot without executing JavaScript.
renderer
Package renderer defines the JS-rendering interface.
Package renderer defines the JS-rendering interface.
richresult
Package richresult validates a page's stored JSON-LD against a versioned Rabbot profile of rich-result eligibility checks.
Package richresult validates a page's stored JSON-LD against a versioned Rabbot profile of rich-result eligibility checks.
robotsmeta
Package robotsmeta is the single, dependency-free canonical parser for HTML meta-robots / X-Robots-Tag directive values.
Package robotsmeta is the single, dependency-free canonical parser for HTML meta-robots / X-Robots-Tag directive values.
rules
Package rules evaluates snapshots (and their diffs) against a default, zero-config SEO rule set, opening/closing model.Issue records keyed (url_id, rule_id) with importance-weighted impact_points.
Package rules evaluates snapshots (and their diffs) against a default, zero-config SEO rule set, opening/closing model.Issue records keyed (url_id, rule_id) with importance-weighted impact_points.
segments
Package segments compiles per-site URL classifiers from config and serves hot-path, in-memory membership lookups.
Package segments compiles per-site URL classifiers from config and serves hot-path, in-memory membership lookups.
serpwidth
Package serpwidth measures the *rendered pixel width* of a title or meta description against the desktop SERP reference font, so callers can decide whether text will fit a result's container instead of counting characters.
Package serpwidth measures the *rendered pixel width* of a title or meta description against the desktop SERP reference font, so callers can decide whether text will fit a result's container instead of counting characters.
setup
internal/setup/coverageplan.go
internal/setup/coverageplan.go
store
Package store owns all SQLite access: a single-writer pool plus a read pool, the contract PRAGMAs via a connection hook, and embedded migrations.
Package store owns all SQLite access: a single-writer pool plus a read pool, the contract PRAGMAs via a connection hook, and embedded migrations.
supervisor
Package supervisor owns the process lifecycle as a native OS service via kardianos/service, holds the root context, and runs the (empty in M0) scheduler loop.
Package supervisor owns the process lifecycle as a native OS service via kardianos/service, holds the root context, and runs the (empty in M0) scheduler loop.
urlx
Package urlx is a tiny, dependency-free helper for host-scoped URL comparison.
Package urlx is a tiny, dependency-free helper for host-scoped URL comparison.
verify
Package verify implements proof-of-control verification for monitored sites.
Package verify implements proof-of-control verification for monitored sites.
wizard
Package wizard is the interactive, terminal-UI onboarding front-end for `rabbot init`.
Package wizard is the interactive, terminal-UI onboarding front-end for `rabbot init`.
scripts
bench/corpussite command
Command corpussite is the LOOPBACK-ONLY synthetic origin for the B3 capacity harness (docs/PERFORMANCE.md).
Command corpussite is the LOOPBACK-ONLY synthetic origin for the B3 capacity harness (docs/PERFORMANCE.md).
bench/rsschart command
Command rsschart renders a capacity CSV (as written by scripts/bench/ capacity.sh: header "elapsed_s,vmrss_kb,db_bytes") into a small, deterministic SVG line chart at assets/capacity-rss.svg.
Command rsschart renders a capacity CSV (as written by scripts/bench/ capacity.sh: header "elapsed_s,vmrss_kb,db_bytes") into a small, deterministic SVG line chart at assets/capacity-rss.svg.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL