Skip to content
Back to skills

Seo Agent

BSecurity

Daily SEO audit-and-fix loop for a website you own, driven by one config file per site. Crawls the live site with free stdlib Python scripts (technical crawl, indexability, internal-link graph with day-over-day regression diff, JSON-LD validity, AI-crawler access, Core Web Vitals via Lighthouse/PageSpeed), pulls real queries from Google Search Console with your own credentials, ranks issues by impact vs effort, ships safe fixes as pull requests (and optional CMS patches) behind a human review...

  • 15 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
researchpythongobashgitapi

Works with

  • claude code
  • cli
  • api

Security analysis

B84/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 15 files and shows the line behind each finding

Scanned November 7, 2026

npx -y skills add jsath/claude-skills --skill seo-agent --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Seo Agent?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Seo Agent
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/jsath-seo-agent/badge)](https://www.skillsdirectory.com/skills/jsath-seo-agent)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: seo-agent
description: "Daily SEO audit-and-fix loop for a website you own, driven by one config file per site. Crawls the live site with free stdlib Python scripts (technical crawl, indexability, internal-link graph with day-over-day regression diff, JSON-LD validity, AI-crawler access, Core Web Vitals via Lighthouse/PageSpeed), pulls real queries from Google Search Console with your own credentials, ranks issues by impact vs effort, ships safe fixes as pull requests (and optional CMS patches) behind a human review gate, and writes a dated report. Use when asked to run an SEO audit, a technical SEO audit, find SEO quick wins, check indexing or AI crawlability, or run the daily SEO check: /seo-agent <slug> or /seo-agent <slug> technical. Requires Claude Code (runs scripts, git, gh); never merges, never direct-pushes."
---

# SEO Agent

One engine, one config per site. Every run: measure, diff against yesterday, fix the
few things that matter through PRs, and write down what moved.

## Requirements

- Claude Code with Bash. Python 3 (scripts are stdlib only, except `gsc.py`: `pip install google-auth`).
- Optional: Chrome/Chromium (for `render_check.py`), `npm i -g lighthouse` (for `cwv.py`), `gh` CLI (PRs and preview-host discovery).
- A site config at `sites/<slug>.md`. Copy `sites/example.md` and fill the placeholders.
- GSC is optional but turns guesses into measurements. Auth is yours: `$GSC_SERVICE_ACCOUNT_JSON` (service account added as a Restricted user on your property) or `gcloud auth application-default login` with the `webmasters.readonly` scope.

All commands below run from this skill folder (`S=scripts`). Output goes to
`~/.claude/seo-reports/` (reports) and `~/.claude/seo-state/` (snapshots).

## Modes

| mode | trigger | does |
|---|---|---|
| daily (default) | `/seo-agent <slug>` | steps 1-6, lean and diff-driven |
| technical | `/seo-agent <slug> technical`, "deep audit", weekly, or after any routing/middleware/sitemap/layout change | full checklist in `references/technical-seo.md` |
| audit-only | the user says read-only | steps 1-4 and 6; every fix becomes a proposal with an owner; no branches, PRs or CMS writes |

## Steps

### 1. Load the config
Read `sites/<slug>.md` first. If it does not exist, stop and offer to create it from
`sites/example.md` (ask for domain, repo, build command, GSC property). Never redo
anything under `known_wins`.

### 2. Measure (free, no paid APIs)
Run in this order; each writes machine-readable output the report uses.

```bash
python3 $S/linkgraph.py <domain>                  # link graph + orphans, snapshot, diff vs last run
python3 $S/crawl.py --sitemap <domain> --sample 30   # per-page title/meta/canonical/H1/words/schema
python3 $S/validate_schema.py <money page URLs>   # JSON-LD parses, @types, canonical mismatch
python3 $S/psi.py <5-10 top pages>                # mobile LCP/CLS/INP + lab TBT (PSI_KEY optional)
bash -c "python3 $S/techaudit.py <domain> --out <dir> --max 3000 --thin 250 --depth-limit 4 \
  --preview-from-gh <owner/repo> $PROBES"         # when linkgraph shows new 4xx/orphans/lost schema
```

Use `bash -c` for anything with `$PROBES` (`--probe /api/ --probe /admin ...`): zsh does not word-split it and the probes silently vanish.

**GSC** (if configured): preflight under a 60 s cap, then snapshot every run.
```bash
perl -e 'alarm 60; exec @ARGV' python3 $S/gsc.py <property> preflight
python3 $S/gsc.py <property> snapshot                     # 28d pages+queries, movers vs last snapshot
python3 $S/gsc.py <property> searchanalytics --days 28 --dim query --top 200
```

Check `robots.txt` by hand if techaudit did not run: GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, Claude-SearchBot, PerplexityBot, Google-Extended, Applebot-Extended, CCBot must not be blocked unless that is a deliberate policy.

### 3. Research
- With GSC: queries at average position 11-30 with real impressions are the cheapest page-1 pushes. Start there.
- Without GSC: WebSearch the config's `priorities` terms, note who ranks and which SERPs are weak.
- Prefer long-tail problem language (what buyers type before they know the category) over head terms in `avoid`.

### 4. Prioritise
Build one table: `issue | impact H/M/L | effort S/M/L | evidence (URL + observed value) | action`.

Rank in this order:
1. Linkgraph regressions (inlink drops, lost schema, new orphans, title drift). Highest confidence, usually small.
2. Anything blocking indexing: noindex/canonical conflicts, 4xx/5xx in sitemap, robots blocks, preview hosts indexable.
3. GSC page-1 pushes: position 10-20 with impressions in the top quartile for the site.
4. Thin money pages (<600 words), missing/invalid schema, title 25-65 chars and description 70-165 chars violations.
5. CWV failures at p75: LCP >2.5 s, CLS >0.1, INP >200 ms (field data only; lab is diagnosis).

Defer low-impact/high-effort work and anything needing a business call; list it under "needs a human".

### 5. Ship fixes (review gate)
**Code** (only if `repo_path` is set):
- Branch `fix/seo-<topic>` off `default_branch`. One concern per PR.
- Run the config's `build`; never push a failing build.
- `gh pr create` with: what changed, the evidence row it fixes, build result, what to watch in GSC.
- Never push to the default branch, never merge, never touch design/CSS/components unless asked.

**CMS** (dispatch on `cms.driver`; token from the env var named in `token_env`):
- Patch scalar fields only: title, meta description, excerpt. Do not rewrite rich-text bodies unless asked.
- Hard gate before every patch: `python3 $S/lint_brand.py "<new text>" <each brand_voice.forbidden item>`. Exit 1 means rewrite and lint again.
- `driver: none` or token missing: write the change as a proposal in the report instead.

**Optional**: after shipping changed URLs, `python3 $S/indexnow.py <domain> <key> <urls>` (Bing and others; key file must be served at `/<key>.txt`).

### 6. Report
Write `~/.claude/seo-reports/<slug>-<YYYY-MM-DD>.md` (format below). Every 7th run
also write `<slug>-week-<YYYY-Www>.md`: what shipped vs what moved in GSC.

**Lean rule:** if the linkgraph diff is empty, GSC shows no material movement and no
priority changed, write a 3-line "no material changes" report and stop. Do not
manufacture work.

## Decision rules

- Numbers in reports: same source, same window length, before/after pairs via `--start/--end` around the change date. Never compare a UI export to an API pull or a 7-day window to a 28-day one.
- Lab vs field CWV never share one claim.
- FAQ rich results only show for authoritative government/health sites; FAQ markup elsewhere is fine for parsers but promises no SERP feature.
- Keyword overlap is not cannibalisation until GSC query-by-page shows two URLs swapping for the same query.
- A preview host (`*.vercel.app`, `*.netlify.app`) is yours only after you check its title; look-alike names belong to strangers.

## Failure handling

| problem | do this |
|---|---|
| GSC preflight fails or hangs | record the exact error, use the newest `~/.claude/seo-state/<host>/gsc-*.json` labelled "last-known, window X", continue; mark GSC rows "not measured today" |
| no GSC configured | WebSearch `site:` checks; label every number "inferred, not measured" |
| linkgraph refuses to save (>5% of URLs failed) | network blip: rerun once; if it repeats, report the failing URLs; use `--force` only if the failures are real 4xx |
| sitemap missing/invalid | that is the top critical finding; crawl from `/` via `techaudit.py` instead |
| Lighthouse/Chrome missing | use `psi.py` (Google-hosted) and say lab data came from PSI |
| `cwv.py` slow (~90 s/URL) | run it with `nohup ... &` and poll its log; long silent foreground commands can time out |
| build fails on a fix branch | fix it or drop the PR; never push red |

## Report format

```markdown
# SEO daily: example.com, 2026-03-04
Crawl: 412 URLs, 398 indexable, 0 5xx. Linkgraph: 2 regressions (below).

| issue | impact | effort | evidence | action |
|---|---|---|---|---|
| /pricing lost 6 inlinks | H | S | footer nav change, 9 -> 3 inlinks | PR #41 restores footer link |
| /blog/x missing Article schema | M | S | validate_schema: 0 JSON-LD | PR #42 |

Shipped: PR #41, PR #42 (build green). CMS: 1 title patch, lint_brand PASS.
GSC 28d vs last: /guide/y pos 14.2 -> 11.8, impressions 1,920 -> 2,410 (same window length).
Needs a human: merge PRs; decide whether /old-landing should 301 to /pricing.
Carry forward: page-1 push for "<query>"; thin /features page (310 words).
```

Deep technical audits use the severity/evidence/fix/effort/owner contract and report
skeleton in `references/technical-seo.md`.

## Running it daily

In-session: `/loop 24h /seo-agent <slug>` (only fires while Claude Code is open).
Unattended: `scripts/daily.sh <slug>` from cron on a server; see `references/deploy.md`.

Files in this skill

  • SKILL.md8.7 KB
  • references/deploy.md2.9 KB
  • references/technical-seo.md9.5 KB
  • scripts/crawl.py5.3 KB
  • scripts/cwv.py12.9 KB
  • scripts/daily.sh1.2 KB
  • scripts/gsc.py11.5 KB
  • scripts/indexnow.py2 KB
  • scripts/linkgraph.py11.2 KB
  • scripts/lint_brand.py1.9 KB
  • scripts/psi.py3 KB
  • scripts/render_check.py6.2 KB
  • scripts/techaudit.py53.4 KB
  • scripts/validate_schema.py2.9 KB
  • sites/example.md3 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.