Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,27 @@
"maintainer",
"gtapps"
]
},
{
"name": "seo-hermit",
"source": "./plugins/seo-hermit",
"description": "SEO/site-health layer for claude-code-hermit — weekly Search Console, broken-link, and Core Web Vitals deltas, with regression-to-commit correlation and drafted fixes",
"version": "0.0.1",
"category": "seo",
"author": {
"name": "gtapps",
"url": "https://github.com/gtapps"
},
"license": "MIT",
"homepage": "https://github.com/gtapps/claude-code-hermit",
"repository": "https://github.com/gtapps/claude-code-hermit",
"keywords": [
"seo",
"search-console",
"core-web-vitals",
"site-health",
"gtapps"
]
}
]
}
42 changes: 42 additions & 0 deletions .github/workflows/test-seo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Test SEO Hermit

on:
push:
branches: [main]
paths:
- 'plugins/seo-hermit/**'
- '.github/workflows/test-seo.yml'
- 'package.json'
- 'bun.lock'
- 'tsconfig.json'
pull_request:
paths:
- 'plugins/seo-hermit/**'
- '.github/workflows/test-seo.yml'
- 'package.json'
- 'bun.lock'
- 'tsconfig.json'

jobs:
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: '1.3.11'
- run: bun install --frozen-lockfile
- run: bunx tsc

test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: plugins/seo-hermit
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: '1.3.11'
- name: Run test suite
run: bash tests/run-all.sh
6 changes: 6 additions & 0 deletions plugins/seo-hermit/.claude-plugin/hermit-meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"required_core_version": ">=1.2.14",
"requires": {
"claude-code-hermit": ">=1.2.14"
}
}
25 changes: 25 additions & 0 deletions plugins/seo-hermit/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "seo-hermit",
"version": "0.0.1",
"description": "SEO/site-health layer for claude-code-hermit — weekly Search Console, broken-link, and Core Web Vitals deltas, with regression-to-commit correlation and drafted fixes",
"author": {
"name": "gtapps",
"url": "https://github.com/gtapps"
},
"repository": "https://github.com/gtapps/claude-code-hermit",
"homepage": "https://github.com/gtapps/claude-code-hermit/tree/main/plugins/seo-hermit",
"license": "MIT",
"keywords": [
"seo",
"search-console",
"core-web-vitals",
"site-health",
"gtapps"
],
"dependencies": [
{
"name": "claude-code-hermit",
"version": "^1.2.14"
}
]
}
14 changes: 14 additions & 0 deletions plugins/seo-hermit/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Secrets — never commit
.env
.env.*
.claude.local/

# Claude Code local state (when developing the plugin against a local target project)
.claude/
.claude-code-hermit/

# OS / IDE
.DS_Store
.obsidian/
*.swp
*~
12 changes: 12 additions & 0 deletions plugins/seo-hermit/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Changelog — seo-hermit

## [Unreleased]

### Added
- **Scaffold + hatch** — new domain plugin `seo-hermit`. One-time `/seo-hermit:hatch` wizard collects the site URL, sitemap URL, a Google Search Console service-account JSON path, and an optional PageSpeed Insights key, then verifies GSC access with a live `searchAnalytics.query` round-trip. Requires `claude-code-hermit` ≥1.2.14.
- **`scripts/site-api.ts`** — zero-dependency Bun client for the Search Console and PageSpeed Insights APIs. Ships the `check` (credential probe: `missing`/`invalid`/`unreachable`/`ok`) and `search-analytics` subcommands. Service-account auth is a JWT-bearer flow signed with `node:crypto` — no npm packages.
- **`site-health-weekly` routine + ledger** — weekly `/seo-hermit:site-health-check` pulls Search Console deltas, link-checks the sitemap, samples Core Web Vitals via PageSpeed Insights, and inspects a rotating budget of URLs for index status. Everything diffs against `state/site-health-ledger.json`; only changes are reported. A quiet week is one line.
- **Judgment layer** — `/seo-hermit:site-regression-triage` correlates a regression with recent commits in a locally-configured site repo; `/seo-hermit:site-draft-fix` drafts mechanical fixes (broken internal links, stale sitemap entries, missing redirects/meta tags, CLS image dimensions) on a branch, behind an explicit approval gate. Read-only toward Google; write-only toward the operator's own repo.

### Upgrade Instructions
New plugin — no in-place migration. Install with `claude plugin install seo-hermit@claude-code-hermit`, then run `/seo-hermit:hatch` in the target project.
104 changes: 104 additions & 0 deletions plugins/seo-hermit/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# seo-hermit

An SEO/site-health domain layer for `claude-code-hermit`: a weekly watcher over Google Search
Console, broken links, and Core Web Vitals, with a judgment layer that correlates regressions with
site-repo commits and drafts mechanical fixes.

## This Repo is a Plugin

This repo is structured as a Claude Code plugin. It is NOT a standalone project — it gets installed
into other projects via:

```
claude plugin marketplace add gtapps/claude-code-hermit
claude plugin install seo-hermit@claude-code-hermit --scope local
```

After install, run `/seo-hermit:hatch` in the target project. The core hermit
(`claude-code-hermit` ≥1.2.14) must be installed and hatched first — `hatch` will prompt if it isn't.

## Plugin Structure

- `skills/hatch/` — one-time setup wizard (`/seo-hermit:hatch`)
- `skills/site-health-check/` — weekly routine: pull deltas, diff the ledger, report changes
- `skills/site-regression-triage/` — correlate a flagged regression with recent site-repo commits
- `skills/site-draft-fix/` — draft a mechanical fix on a branch, behind an approval gate
- `scripts/site-api.ts` — zero-dep Bun client for Search Console + PageSpeed Insights
- `state-templates/CLAUDE-APPEND.md` — SEO Workflow block injected into the consumer's CLAUDE.md by `hatch`
- `docs/knowledge-schema.md` — work-product types and retention rules
- `settings.json` — pre-approved permissions for the site-api script and read-class git
- `.claude-plugin/plugin.json` / `hermit-meta.json` — manifests

## Hatch target routing

`/hatch` Step 6 reads `.claude-code-hermit/state/hatch-options.json` (`target` field) to route the
CLAUDE-APPEND block: `"local"` → `CLAUDE.local.md`, `"committed"` → `CLAUDE.md`. If no
`hatch-options.json` exists, it detects `core_install_scope` from `claude plugin list --json` and
stamps the canonical 5-field schema.

## Core Rules

- **Never Read the service-account JSON.** It holds an RSA private key. Check credential state with
`site-api.ts check` (self-reports `missing`/`invalid`/`unreachable`/`ok`); verify the key file's
*existence* only.
- **Credentials never touch a command line.** `site-api.ts` loads them from `.env` /
`SEO_HERMIT_GSC_CREDENTIALS`. The four `SEO_HERMIT_*` var names deliberately avoid the
`TOKEN`/`SECRET`/`API_KEY` deny substrings so `.env` can be read with the Read tool.
- **Read-only toward Google.** No writes to Search Console or search-facing config, ever.
- **Site-repo writes are approval-gated.** All fixes go through `site-draft-fix`: draft on a branch,
show the diff, wait for approval, never push, never touch a protected branch.
- **URL Inspection is budgeted.** Per-URL index checks respect a weekly budget (~2,000/day/property
quota), rotating the sitemap via the ledger `inspect_cursor`.

## Memory Conventions

- `state/site-health-ledger.json` — rolling machine-readable diff state (search history, broken
links, CWV history, index sample, inspect cursor). Permanent, trimmed in place.
- `raw/` — ephemeral API pulls, aged out per `knowledge.raw_retention_days`.
- `compiled/` — durable dated reports (`site-health-*`, `site-triage-*`, `site-fix-*`).

Do NOT create subdirectories inside `raw/` or `compiled/`. Flat layout only (base hermit storage contract).

## `site-api.ts` subcommands

| Subcommand | Purpose |
|------------|---------|
| `check` | Credential probe. Prints `missing`/`invalid`/`unreachable`/`ok` (+ optional `psi:<status>`). |
| `search-analytics --start --end [--dimensions] [--row-limit]` | Search Console query → `{ok,data:{rows,totals}}`. |
| `sitemap` / `link-check` / `psi` / `inspect` / `ledger` | Weekly-routine data pulls + the diff engine (see `site-health-check`). |

## Routines

The weekly `site-health-weekly` routine invokes the `site-health-check` **skill directly**
(`{"skill": "seo-hermit:site-health-check", ...}`), the core `weekly-review` pattern — not a
`prompt_file` drop. Routine logic lives in the shipped skill so it upgrades with the plugin and is
covered by `skill-structure.test.ts`; a dropped prompt file would be install-once and never receive
upgrades.

## Development

Test locally against a target project without publishing:

```
cd /path/to/target-project
claude --plugin-dir /path/to/plugins/seo-hermit
```

Under `--plugin-dir`, `${CLAUDE_PLUGIN_ROOT}` is NOT substituted — use the absolute plugin path in
commands. `${CLAUDE_PLUGIN_ROOT}` is never a runtime env var; the script self-resolves its own dir.

Run tests (from inside the plugin dir):

```
bash tests/run-all.sh # bun site-api + skill-structure tests
```

Typecheck from the repo root: `bunx tsc` (strict; `plugins/**/*.ts` is included).

## Development constraints

- `tests/skill-structure.test.ts` hardcodes the `SKILLS` array — add a skill → update that array.
- No runtime dependencies: `site-api.ts` imports only the standard library and Bun/`node:*` built-ins.
- The base hermit deny-patterns hook blocks any Bash arg containing `TOKEN`/`SECRET`/`API_KEY`. Keep
credential values off command lines.
- CLAUDE-APPEND must not restate `config.json` values (routine schedules, flags). Describe behaviors.
21 changes: 21 additions & 0 deletions plugins/seo-hermit/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 gtapps

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
72 changes: 72 additions & 0 deletions plugins/seo-hermit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# seo-hermit

A weekly SEO/site-health watcher for [claude-code-hermit](https://github.com/gtapps/claude-code-hermit).

It pulls Google Search Console deltas, checks the sitemap for broken links, samples Core Web Vitals
via PageSpeed Insights, and inspects a rotating budget of URLs for index status — then reports **only
what changed** since last week. When a metric regresses, it correlates the regression with recent
commits in your site's repo and can draft the mechanical fix on a branch for your approval.

## Positioning (read this first)

The raw monitoring here is commodity — cheap incumbents and a cron + link-checker cover most of it,
and the signals move weekly, not hourly. seo-hermit earns its keep in the **judgment layer**:
deciding which of many Search Console warnings matter, tying a Core Web Vitals regression to a
specific deploy, and drafting the fix when your site's repo is available locally. If you just want
raw dashboards, use the incumbents. If you want a hermit that watches, correlates, and drafts, this
is that.

## What runs weekly

1. Search Console click/impression/position deltas (current week vs prior, ~3-day data lag applied).
2. Sitemap link-check (bounded concurrency, HEAD-first).
3. Core Web Vitals (LCP / INP / CLS) for the homepage + top pages, via PageSpeed Insights (optional).
4. URL Inspection index status for a rotating budget of sitemap URLs.

All of it diffs against `state/site-health-ledger.json`. A quiet week is one line; a week with
changes gets a compact report and a channel brief.

## Requirements

- Core `claude-code-hermit` ≥1.2.14, installed and hatched.
- A **Google Search Console** property and a **service account** with read access to it.
- Optionally, a **PageSpeed Insights** API key (enables the Core Web Vitals checks).

### Search Console setup

1. In Google Cloud console, create/pick a project and enable the **Search Console API**.
2. Create a **service account**, download its JSON key, save it to `.claude.local/gsc-service-account.json`.
3. In Search Console, add the service account's `client_email` as a **user** on your property.

## Install

```
claude plugin marketplace add gtapps/claude-code-hermit
claude plugin install seo-hermit@claude-code-hermit --scope local
```

Then run `/seo-hermit:hatch` in the target project.

## Environment (`.env`)

| Variable | Required | Meaning |
|----------|----------|---------|
| `SEO_HERMIT_SITE_URL` | yes | GSC property — `sc-domain:example.com` or `https://www.example.com/`. |
| `SEO_HERMIT_SITEMAP_URL` | yes | Full sitemap URL. |
| `SEO_HERMIT_GSC_CREDENTIALS` | yes | Path to the service-account JSON (keep it in `.claude.local/`). |
| `SEO_HERMIT_PSI_KEY` | no | PageSpeed Insights API key. Blank → CWV checks skipped. |

The variable names deliberately avoid `TOKEN`/`SECRET`/`API_KEY` so the base hermit's deny-patterns
hook doesn't block routine reads of `.env`.

## Safety posture

- **Read-only toward Google.** No writes to Search Console or any search-facing config.
- **Fix-drafting is approval-gated.** `site-draft-fix` only ever drafts on a branch inside your
configured local site repo, shows the full diff, and waits for your approval. It never pushes and
never touches a protected branch. The closed set of fixes it will draft: broken internal links
(on a confirmed slug rename), stale static-sitemap entries, missing redirects for renamed slugs,
empty `<title>`/meta-description on a regressed page, and missing image width/height behind a CLS
regression.
- **Quota-aware.** URL Inspection is capped to a weekly budget (default 20 URLs), rotating through
the sitemap across weeks (Google's quota is ~2,000/day/property).
37 changes: 37 additions & 0 deletions plugins/seo-hermit/docs/knowledge-schema.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# seo-hermit knowledge schema

Work-product and raw-capture types this plugin produces. Frontmatter on every `compiled/`
artifact: `title` (quoted), `type` (one of the keys below), `created` (ISO 8601 with offset),
`tags` (array), plus optional domain fields. Storage is flat — no subdirectories in `raw/` or
`compiled/` (base hermit storage contract).

## Work Products

- **site-health** — weekly site-health report. Only the metrics that changed since the last run;
a quiet week is a single line. Producer: `seo-hermit:site-health-check`. Location:
`compiled/site-health-<YYYY-MM-DD>.md`. Frontmatter carries `verdict: quiet | changes`.
- **site-triage** — regression-to-commit correlation for a flagged week: suspect commits, confidence
tier, whether a mechanical fix exists. Producer: `seo-hermit:site-regression-triage`. Location:
`compiled/site-triage-<YYYY-MM-DD>.md`.
- **site-fix** — record of a drafted mechanical fix: branch name, diff summary, approval outcome.
Producer: `seo-hermit:site-draft-fix`. Location: `compiled/site-fix-<YYYY-MM-DD>.md`.

## Raw Captures

Retention: 14 days (base hermit `knowledge.raw_retention_days`).

- **site-health-snapshot** — the assembled weekly snapshot fed to the ledger diff.
`raw/site-health-snapshot-<date>.json`.
- **gsc-search-analytics** — raw Search Console rows for the current + prior window.
`raw/gsc-search-analytics-<date>.json`.
- **site-linkcheck** — sitemap link-check results. `raw/site-linkcheck-<date>.json`.
- **site-psi** — PageSpeed Insights field/lab metrics per sampled page. `raw/site-psi-<date>.json`.
- **site-inspect** — URL Inspection index-status results for the rotating budget.
`raw/site-inspect-<date>.json`.

## State

- **site-health-ledger** — `state/site-health-ledger.json`. The rolling machine-readable ledger the
weekly routine diffs against: search history (12 weeks), broken-link set with first/last-seen,
CWV history per page (8 entries), index status sample, and the `inspect_cursor` for URL rotation.
Permanent (trimmed in place, never archived).
Loading
Loading