{"id":"2605fc19-1cc1-41e1-bbe5-f2b9e3ef3212","shortId":"arut6E","kind":"skill","title":"seo-drift","tagline":"Capture an SEO baseline snapshot for a domain or URL, then on later runs compare the current state and surface regressions. Tracks authority, traffic, keywords, backlinks, and on-page content. Three subcommands — `baseline`, `compare`, `history`. Use when the user asks for \"SEO d","description":"# SEO Drift\n\nGit for SEO. Capture a snapshot of a domain or URL's SEO state (\"baseline\"), then on later runs diff the current state against the baseline and surface regressions. Catches the things that get worse silently after a deploy, redesign, or content cull.\n\n> **Acknowledgements:** drift-as-an-SEO-skill framework originated in `claude-seo` by AgriciDaniel (with the original concept credited to Dan Colta, Pro Hub Challenge). MIT-licensed both directions; this implementation is independent but the framing is theirs.\n\n## Prerequisites\n\n- SE Ranking MCP server connected.\n- Claude's `WebFetch` tool available (for URL-mode page fingerprinting).\n- User provides: target domain or URL, plus a subcommand (`baseline`, `compare`, `history`).\n\n## Optional flags\n\n| Flag | Mode | Effect |\n|---|---|---|\n| `--no-firecrawl` | baseline, compare | Skip Firecrawl-based `<head>` + JSON-LD capture even when Firecrawl is installed (saves credits at the cost of canonical / robots / og:* / JSON-LD diff coverage). |\n| `--skip-cwv` | baseline, compare | Skip the Google CrUX capture (step 4b) even when `google-api.json` is configured. Useful when you only care about content/structural drift, or when CrUX rate-limit concerns outweigh CWV coverage. Mirrors theirs at `seo-drift/SKILL.md:107, 131`. |\n| `--baseline-id <n>` | compare | Compare against a specific baseline by ID rather than the most recent. |\n| `--limit <n>` | history | Cap the number of historical entries shown. |\n\n## Subcommands\n\n### `baseline <target>`\nCapture the current SEO state and write it to a snapshot file. No diff produced.\n\n### `compare <target>`\nLoad the most recent baseline for the target. Capture the current state. Diff. Produce `DRIFT-REPORT.md`.\n\n### `history <target>`\nList all stored baselines for the target with their dates and key metrics (DA, traffic, keyword count). No diff produced.\n\n## Process\n\n### baseline mode\n\n1. **Validate target.** Determine if domain or URL. Domain = `example.com`; URL = anything starting with `http(s)://`.\n   - **SSRF protection (URL mode).** If target is a URL, validate via `python3 -c \"from scripts.google_auth import validate_url; import sys; sys.exit(0 if validate_url('{target}') else 1)\"` (or import `validate_url` directly in any helper script). Reject loopback (127.0.0.1, ::1, localhost), private IP ranges (10/8, 172.16/12, 192.168/16), link-local (169.254/16), and Google metadata endpoints. If validation fails, abort with a clear error and don't proceed to fetch — feeding an unvalidated URL into Firecrawl / WebFetch / Google APIs would risk SSRF against internal services. Mirrors theirs at `seo-drift/SKILL.md:97`.\n2. **Preflight.** See `skills/seo-firecrawl/references/preflight.md` for the canonical 3-stage preflight (credit balance, Firecrawl availability, Google APIs). Skill-specific notes:\n   - Estimated SE Ranking cost for this skill: typical baseline costs ~10–20 SE Ranking credits depending on whether step 4 (URL-mode page snapshot) is included.\n   - Firecrawl: optional with WebFetch fallback, +1 Firecrawl credit per URL if available (URL mode). When available, the snapshot also captures `<head>` + JSON-LD content so canonical / robots / og:* / JSON-LD changes are detectable on diff. Without it the snapshot is partial. Pass `--no-firecrawl` to skip Firecrawl even when available (saves credits at the cost of diff coverage).\n   - Google APIs: tier 0 unlocks CrUX p75 LCP/INP/CLS capture (origin in domain mode, URL in URL mode); tier 1 (URL mode only) additionally captures URL Inspection state (`indexStatusVerdict`, `googleCanonical`, `lastCrawlTime`) so subsequent compares can flag field-data and indexation drift. See `skills/seo-google/references/cross-skill-integration.md` § \"seo-drift\" for the full recipe.\n3. **Domain snapshot** (always):\n   - `DATA_getDomainOverviewWorldwide` — DA, traffic, organic + paid keyword counts.\n   - `DATA_getDomainKeywords` — top 100 organic keywords with positions.\n   - `DATA_getBacklinksSummary` — backlinks total, referring domains total.\n   - `DATA_getBacklinksRefDomains` — top 20 referring domains with authority.\n4. **Page snapshot** (if target is a URL): `WebFetch` (always) + `mcp__firecrawl-mcp__firecrawl_scrape` (when available)\n   - **WebFetch** (free): extract `<title>`, all `<h1..h6>`, lang, word count, internal-link count, image count, body markdown for prose-level diff.\n   - **Firecrawl** (1 Firecrawl credit per URL) — recovers `<head>` and `<script>` content WebFetch strips:\n     - From `metadata`: canonical URL, robots meta, og:title, og:description, og:image, twitter:card.\n     - From returned `html`: every `<script type=\"application/ld+json\">` block. Capture both detected `@type`s and a hash of the full block content (so any schema-content change is detected on diff, not just type-list changes).\n   - **If Firecrawl unavailable (or `--no-firecrawl` passed):** only WebFetch fields enter the fingerprint. `BASELINE.md` notes: `Snapshot fields recovered via WebFetch only — canonical, robots, og:*, twitter:*, and JSON-LD changes will not be detected on subsequent compares. Install Firecrawl for full coverage.`\n   - Compute a fingerprint hash of the captured fields.\n   - Also capture page authority: `DATA_getPageAuthority`.\n4b. **Google field-data snapshot** *(only if google-api.json is present AND `--skip-cwv` not set)*\n   - Tier 0 (always when configured): `python3 scripts/pagespeed_check.py \"{target}\" --crux-only --json` (URL mode) or `python3 scripts/pagespeed_check.py \"https://{domain}\" --crux-only --json` (domain mode, origin-level CrUX). Store the resulting p75 LCP / INP / CLS / FCP / TTFB and the source label (\"URL\" or \"origin\").\n   - Tier 0 (always when configured): `python3 scripts/crux_history.py \"{target_or_origin}\" --json` for the 25-week trend window snapshot — store as `crux_history_baseline`. Subsequent compares can detect drift against the most recent week.\n   - Tier 1 (URL mode only): `python3 scripts/gsc_inspect.py \"{target_url}\" --site-url \"{config.default_property}\" --json`. Store `indexStatusVerdict`, `coverageState`, `googleCanonical`, `userCanonical`, `lastCrawlTime`.\n   - If `--skip-cwv` was passed, skip this step entirely and store `null` for `cwv` / `crux_history` fields. The compare-mode rules then surface \"Field-data drift: skipped — `--skip-cwv` flag passed at baseline.\"\n   - If CrUX returns insufficient data, store `null` for the affected metrics and continue.\n5. **Write snapshot file** `seo-drift-{target-slug}-{YYYYMMDD}/snapshot.json`.\n6. **Update index** `seo-drift-{target-slug}/baselines.json` — append `{date, snapshot_path}` entry.\n\n### compare mode\n\n1. **Validate target + locate latest baseline** in `seo-drift-{target-slug}/baselines.json`.\n   - **SSRF protection (URL mode).** Same `validate_url()` check as baseline mode. Refuses to fetch private/loopback/metadata addresses.\n   - If no baseline exists, fall through to baseline mode and tell the user to come back later.\n2. **Capture current state** (same data as baseline mode).\n3. **Diff** each metric using `references/drift-thresholds.md`:\n   - Domain authority: ±5 = yellow, ±10 = red.\n   - Estimated organic traffic: ±20% = yellow, ±50% = red.\n   - Organic keyword count: ±10% = yellow, ±30% = red.\n   - Top-3 keyword count: ±15% = yellow, ±40% = red.\n   - Top-100 keyword churn: any high-volume drop = red.\n   - Net referring domains: -5 to -20 = yellow, <-20 = red.\n   - Page-level (URL mode): any change to canonical / robots / lang / H1 = red; title or meta description change = yellow; schema types added/removed = yellow; og:* / twitter:* changes = yellow.\n   - **Firecrawl-dependent diff caveat:** canonical / robots / og:* / twitter:* / JSON-LD diffs require both baseline and current snapshots to have been captured with Firecrawl. If either snapshot was WebFetch-only, those fields surface as `not comparable — Firecrawl-only fields missing from {baseline | current} snapshot` rather than as a green pass.\n   - **Google-data drift** *(only if both snapshots have Google fields)*:\n     - LCP p75 increased ≥20% → red.\n     - INP p75 increased ≥20% → red.\n     - CLS p75 increased ≥0.05 absolute → yellow.\n     - FCP / TTFB p75 increased ≥30% → yellow.\n     - Inspection status changed from `INDEXED` to anything else → red.\n     - `googleCanonical` changed → yellow.\n     - `lastCrawlTime` >60 days old → yellow.\n     - **Caveat:** if either snapshot lacks Google fields (creds were missing at one capture), surface `Field-data / indexation drift: not comparable — Google fields missing from {baseline | current} snapshot.`\n4. **Synthesise** `DRIFT-REPORT.md` — red findings first, then yellow, then green/positive deltas. End with a \"what to investigate first\" recommendation.\n\n### history mode\n\n1. Load `baselines.json`.\n2. For each entry, render a one-row summary: date, DA, traffic, keyword count, top-3 count.\n3. Write `HISTORY.md` with the table.\n\n## Output format\n\n### baseline mode\n`seo-drift-{target-slug}-{YYYYMMDD}/`:\n```\nseo-drift-{target-slug}-{YYYYMMDD}/\n├── snapshot.json            (the captured state)\n└── BASELINE.md              (one-page human summary of what was captured)\n```\n\n### compare mode\n`seo-drift-{target-slug}-{YYYYMMDD}/`:\n```\nseo-drift-{target-slug}-{YYYYMMDD}/\n├── DRIFT-REPORT.md              (synthesised: red/yellow/green changes — primary deliverable; inlines 01-domain-deltas, 02-keyword-churn, 03-backlink-deltas, 04-page-deltas as sections)\n└── evidence/\n    ├── baseline-snapshot.json   (the prior reference — kept for replay)\n    ├── current-snapshot.json    (today's state — kept for replay)\n    ├── 01-domain-deltas.md      (DA, traffic, keyword count changes — raw step output)\n    ├── 02-keyword-churn.md      (top-100 entries/exits)\n    ├── 03-backlink-deltas.md    (new + lost backlinks/domains)\n    └── 04-page-deltas.md        (URL mode only: HTML fingerprint diff)\n```\n\nTop-level: `DRIFT-REPORT.md` only. The four delta step files are inlined into named sections in DRIFT-REPORT.md; `evidence/` keeps the raw delta dumps and both snapshot JSONs so a future re-diff or audit can replay against them.\n\n`DRIFT-REPORT.md` shape:\n\n```markdown\n# Drift Report: {target}\n> Baseline: {baseline date} · Current: {today's date}\n\n## RED — investigate today\n- {finding} ({severity rationale})\n- ...\n\n## YELLOW — investigate this week\n- {finding}\n- ...\n\n## GREEN — positive deltas\n- {finding}\n- ...\n\n## Field-data drift (CrUX + URL Inspection)\n- LCP p75: {baseline} → {current} ms ({Δ%}) {↑ red / ↑ yellow / stable / ↓ green}\n- INP p75: {baseline} → {current} ms ({Δ%}) {…}\n- CLS p75: {baseline} → {current} ({Δ absolute}) {…}\n- Indexation status: {baseline INDEXED → current EXCLUDED} (URL mode)\n- googleCanonical: {baseline → current} (if changed)\n- (Or: `Field-data / indexation drift: not configured` / `not comparable — missing from {snapshot}`)\n\n## What to investigate first\n1. {prioritised action with reasoning}\n2. ...\n```\n\n### history mode\n`seo-drift-{target-slug}-{YYYYMMDD}/HISTORY.md`:\n\n```markdown\n# History: {target}\n\n| Date | DA | Traffic | Keywords | Top-3 |\n|---|---|---|---|---|\n| 2026-04-27 | 42 | 18,500/mo | 1,247 | 89 |\n| 2026-03-15 | 41 | 17,200/mo | 1,213 | 85 |\n| ...\n```\n\n## Tips\n\n- Respect rate limit: 10 req/sec. Baseline runs 4–6 sequential calls; pace easily.\n- Call `DATA_getCreditBalance` before running. Domain baseline ~10–15 SE Ranking credits; URL baseline ~15–20 SE Ranking credits + 1 Firecrawl credit; compare ~20–30 SE Ranking credits + 1 Firecrawl credit (current-state capture).\n- Snapshot storage is **local-only** in v0.4.0. If your team needs shared baselines, point everyone at the same `seo-drift-{target-slug}/` directory in a shared filesystem or commit it to a private repo. Baselines are JSON — git-friendly.\n- Baseline cadence: monthly is the natural rhythm because SE Ranking's history endpoints have monthly granularity. Weekly is too noisy for backlink data. Document recommended cadence in handoff to your team.\n- For deploy-time \"did anything break in the last hour\" use cases, the URL-mode page-fingerprint half is the workhorse — that doesn't depend on monthly data.\n- Don't auto-disavow or auto-fix anything based on drift findings. The skill diagnoses; humans decide.\n- **Authority-history all-zeros caveat:** if `DATA_getPageAuthorityHistory` (URL mode) or `DATA_getDomainAuthorityHistory` returns flat-zero values across the window, treat as \"insufficient history\" — don't compute a delta or surface a regression based on missing data. Cross-check the current-value endpoint (`DATA_getPageAuthority` / `DATA_getDomainOverviewWorldwide`) — if the current value is meaningful but history is flat, surface that as a data-quality flag in `DRIFT-REPORT.md` rather than fabricating a trend.\n- Cost of doing nothing: silent regressions. Cost of running monthly: ~15 credits. Run monthly.","tags":["seo","drift","skills","seranking","agent-skills","ai-search","anthropic","backlinks","claude","claude-code","claude-plugin","claude-skills"],"capabilities":["skill","source-seranking","skill-seo-drift","topic-agent-skills","topic-ai-search","topic-anthropic","topic-backlinks","topic-claude","topic-claude-code","topic-claude-plugin","topic-claude-skills","topic-content-brief","topic-ga4","topic-keyword-research","topic-mcp"],"categories":["seo-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/seranking/seo-skills/seo-drift","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add seranking/seo-skills","source_repo":"https://github.com/seranking/seo-skills","install_from":"skills.sh"}},"qualityScore":"0.454","qualityRationale":"deterministic score 0.45 from registry signals: · indexed on github topic:agent-skills · 9 github stars · SKILL.md body (12,844 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:08:36.134Z","embedding":null,"createdAt":"2026-05-18T13:14:09.562Z","updatedAt":"2026-05-18T19:08:36.134Z","lastSeenAt":"2026-05-18T19:08:36.134Z","tsv":"'+1':490 '/12':389 '/16':391,396 '/skill.md':240,436 '0':363,548 '1':325,369,382,563,669 '10':468 '10/8':387 '100':610 '107':241 '127.0.0.1':381 '131':242 '169.254':395 '172.16':388 '192.168':390 '2':438 '20':469,625 '3':445,595 '4':477,630 '4b':210 '97':437 'abort':404 'acknowledg':93 'addit':567 'agricidaniel':107 'also':503 'alway':598,639 'anyth':336 'api':423,453,546 'ask':44 'auth':356 'author':26,629 'avail':143,451,496,500,536,647 'backlink':29,617 'balanc':449 'base':175 'baselin':7,37,64,75,159,170,202,244,251,269,290,305,323,466 'baseline-id':243 'bodi':661 'c':353 'canon':191,444,510 'cap':261 'captur':4,53,179,208,270,294,504,553,568 'care':220 'catch':79 'challeng':118 'chang':516 'claud':104,139 'claude-seo':103 'clear':407 'colta':115 'compar':18,38,160,171,203,246,247,285,577 'concept':111 'concern':230 'configur':215 'connect':138 'content':34,91,508 'content/structural':222 'cost':189,461,467,541 'count':318,606,654,658,660 'coverag':198,233,544 'credit':112,186,448,472,492,538,671 'crux':207,226,550 'cull':92 'current':20,71,272,296 'cwv':201,232 'd':47 'da':315,601 'dan':114 'data':582,599,607,615,622 'date':311 'depend':473 'deploy':88 'detect':518 'determin':328 'diff':69,197,283,298,320,520,543,667 'direct':123,374 'domain':11,58,153,330,333,556,596,620,627 'drift':3,49,95,223,239,435,585,590 'drift-as-an-seo-skil':94 'drift-report.md':300 'effect':166 'els':368 'endpoint':400 'entri':266 'error':408 'estim':458 'even':180,211,534 'example.com':334 'extract':650 'fail':403 'fallback':489 'feed':415 'fetch':414 'field':581 'field-data':580 'file':281 'fingerprint':149 'firecrawl':169,174,182,420,450,485,491,530,533,642,644,668,670 'firecrawl-bas':173 'firecrawl-mcp':641 'flag':163,164,579 'frame':130 'framework':100 'free':649 'full':593 'get':83 'getbacklinksrefdomain':623 'getbacklinkssummari':616 'getdomainkeyword':608 'getdomainoverviewworldwid':600 'git':50 'googl':206,398,422,452,545 'google-api.json':213 'googlecanon':573 'helper':377 'histor':265 'histori':39,161,260,301 'http':339 'hub':117 'id':245,253 'imag':659 'implement':125 'import':357,360,371 'includ':484 'independ':127 'index':584 'indexstatusverdict':572 'inspect':570 'instal':184 'intern':428,656 'internal-link':655 'ip':385 'json':177,195,506,514 'json-ld':176,194,505,513 'key':313 'keyword':28,317,605,612 'lang':652 'lastcrawltim':574 'later':16,67 'lcp/inp/cls':552 'ld':178,196,507,515 'level':666 'licens':121 'limit':229,259 'link':393,657 'link-loc':392 'list':302 'load':286 'local':394 'localhost':383 'loopback':380 'markdown':662 'mcp':136,640,643 'metadata':399 'metric':314 'mirror':234,430 'mit':120 'mit-licens':119 'mode':147,165,324,344,480,498,557,561,565 'no-firecrawl':167,528 'note':457 'number':263 'og':193,512 'on-pag':31 'option':162,486 'organ':603,611 'origin':101,110,554 'outweigh':231 'p75':551 'page':33,148,481,631 'paid':604 'partial':526 'pass':527 'per':493,672 'plus':156 'posit':614 'preflight':439,447 'prerequisit':133 'privat':384 'pro':116 'proceed':412 'process':322 'produc':284,299,321 'prose':665 'prose-level':664 'protect':342 'provid':151 'python3':352 'rang':386 'rank':135,460,471 'rate':228 'rate-limit':227 'rather':254 'recent':258,289 'recip':594 'recov':674 'redesign':89 'refer':619,626 'regress':24,78 'reject':379 'risk':425 'robot':192,511 'run':17,68 'save':185,537 'scrape':645 'script':378 'scripts.google':355 'se':134,459,470 'see':440,586 'seo':2,6,46,48,52,62,98,105,238,273,434,589 'seo-drift':1,237,433,588 'server':137 'servic':429 'shown':267 'silent':85 'skill':99,455,464 'skill-seo-drift' 'skill-specif':454 'skills/seo-firecrawl/references/preflight.md':441 'skills/seo-google/references/cross-skill-integration.md':587 'skip':172,200,204,532 'skip-cwv':199 'snapshot':8,55,280,482,502,524,597,632 'source-seranking' 'specif':250,456 'ssrf':341,426 'stage':446 'start':337 'state':21,63,72,274,297,571 'step':209,476 'store':304 'subcommand':36,158,268 'subsequ':576 'surfac':23,77 'sys':361 'sys.exit':362 'target':152,293,308,327,346,367,634 'thing':81 'three':35 'tier':547,562 'tool':142 'top':609,624 'topic-agent-skills' 'topic-ai-search' 'topic-anthropic' 'topic-backlinks' 'topic-claude' 'topic-claude-code' 'topic-claude-plugin' 'topic-claude-skills' 'topic-content-brief' 'topic-ga4' 'topic-keyword-research' 'topic-mcp' 'total':618,621 'track':25 'traffic':27,316,602 'typic':465 'unlock':549 'unvalid':417 'url':13,60,146,155,332,335,343,349,359,366,373,418,479,494,497,558,560,564,569,637,673 'url-mod':145,478 'use':40,216 'user':43,150 'valid':326,350,358,365,372,402 'via':351 'webfetch':141,421,488,638,648 'whether':475 'without':521 'word':653 'wors':84 'would':424 'write':276","prices":[{"id":"9a80ee57-03b8-4a05-9954-87d81a39a3a9","listingId":"2605fc19-1cc1-41e1-bbe5-f2b9e3ef3212","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"seranking","category":"seo-skills","install_from":"skills.sh"},"createdAt":"2026-05-18T13:14:09.562Z"}],"sources":[{"listingId":"2605fc19-1cc1-41e1-bbe5-f2b9e3ef3212","source":"github","sourceId":"seranking/seo-skills/seo-drift","sourceUrl":"https://github.com/seranking/seo-skills/tree/main/skills/seo-drift","isPrimary":false,"firstSeenAt":"2026-05-18T13:14:09.562Z","lastSeenAt":"2026-05-18T19:08:36.134Z"}],"details":{"listingId":"2605fc19-1cc1-41e1-bbe5-f2b9e3ef3212","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"seranking","slug":"seo-drift","github":{"repo":"seranking/seo-skills","stars":9,"topics":["agent-skills","ai-search","answer-engine-optimization","anthropic","backlinks","claude","claude-code","claude-plugin","claude-skills","content-brief","ga4","generative-engine-optimization","keyword-research","mcp","mcp-server","search-console","seo","seo-tools","seranking","site-audit"],"license":"mit","html_url":"https://github.com/seranking/seo-skills","pushed_at":"2026-05-11T20:07:40Z","description":"Claude SEO Skills — production Claude Agent Skills for the SE Ranking MCP server. Content briefs, AI Search share of voice, audits, backlink gaps, keyword clusters, schema, sitemap, GEO, and more.","skill_md_sha":"7e1742287284c6e2f7809d800064fb3dd55adc93","skill_md_path":"skills/seo-drift/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/seranking/seo-skills/tree/main/skills/seo-drift"},"layout":"multi","source":"github","category":"seo-skills","frontmatter":{"name":"seo-drift","description":"Capture an SEO baseline snapshot for a domain or URL, then on later runs compare the current state and surface regressions. Tracks authority, traffic, keywords, backlinks, and on-page content. Three subcommands — `baseline`, `compare`, `history`. Use when the user asks for \"SEO drift\", \"baseline this site\", \"did anything break\", \"SEO regression check\", \"compare before and after\", \"deployment check\", or \"monthly SEO snapshot\"."},"skills_sh_url":"https://skills.sh/seranking/seo-skills/seo-drift"},"updatedAt":"2026-05-18T19:08:36.134Z"}}