Imported from abdelkareemkobo/seo_otter (
AGENTS.md). Install upstream withnpx skills add abdelkareemkobo/seo_otter. Copyright stays with the author.
AGENTS.md — seootter
nbdev project — edit notebooks, not lib code
The real source is nbs/*.ipynb. Files under seootter/ are autogenerated (marked AUTOGENERATED! DO NOT EDIT!). Edit notebooks, then run:
nbdev_preview # renders + previews docs locally (Quarto server)
nbdev_export # re-export lib from notebooks
nbdev_test # runs notebook tests
Quick start
uv sync # install deps
cp .env.example .env # set SEEOOTTER_DB_URL, content paths (or just use .env)
seo-otter-add-website ... # register a site
seo-otter-sync sc-domain:example.com --days 90 # OAuth on first run
seo-otter-report 1 # full site report
CLI commands
All defined in seootter/cli.py as @call_parse decorated fns, registered as [project.scripts] in pyproject.toml:
sync, report, audit, rank, trend, top-pages, wins, canob, index-check, index-report, index-refresh, crawl-errors, compare, country-breakdown, schema-check, faq, add-website, list-websites, delete-website.
Key structure
| Path | Purpose |
|---|---|
nbs/ |
Source notebooks (edit here) |
seootter/ |
Autogenerated lib (do NOT edit directly) |
ui/main.py |
FastHTML web app (serves on port 5002) |
seootter/cli.py |
All CLI entrypoints |
seootter/gsc/ |
GSC queries, sync, filters |
seootter/report/ |
SEO report generation (articles, analysis, generator) |
seootter/content/ |
Content mapping/parsing helpers |
Env & config
SEEOOTTER_DB_URL— SQLite DB path (default in.env:nbs/data/seo.db)token.pickle— GSC OAuth token (gitignored)client_secrets.json— GSC OAuth creds in~/.config/seootter/(gitignored)- Content mapper per domain:
~/.config/seootter/mappers/{domain}/mapper.pyexportingget_url_file_mapping() -> dict[str, str]
Dev workflow
- Python >=3.12, uses
uv(lockfile:uv.lock) - CI:
answerdotai/workflows/nbdev3-ci@master(runsnbdev_test) - Deploy:
answerdotai/workflows/quarto-ghp3@master(GitHub Pages tohttps://abdelkareemkobo.github.io/seootter/) - Docs are built by Quarto from notebook exports to
_docs/(not committed tomain)
Style notes
- All CLI output uses
richfor formatted tables - GSC queries use
sc-domain:prefixed site URLs internally - Content mapping modes:
direct(Quarto),slug(Astro Latin),limax(Arabic filenames)
Session context (17 May 2026)
Bugs fixed
fetch_sitemap_urls(nbs/06_index_tracking.ipynb): returns[]onConnectError/TimeoutException/HTTPStatusErrorinstead of crashingload_or_setup_mapper(ui/main.py): catches exceptions from user mappers, shows error page instead of 500get_article_content(nbs/19_dspy_infer.ipynb): added missingFETCH = "::fetch::"constant
UX redesign (ui/main.py)
- DaisyUI drawer sidebar layout (global nav only, responsive)
- Navbar with mobile hamburger, theme dropdown, branding
- Footer with credit
- Page fadeIn transition animation
- HTMX loading indicators (
.htmx-indicator) - Favicons from Google (
www.google.com/s2/favicons?domain=...&sz=...) with lazy loading + JS fallback - Site cards with emoji type icons, hover lift/shadow
- Delete website button with DaisyUI modal confirmation
Theme system
- 6 themes: dim (default), light, dark, coffee, night, winter
- Toggle via
<details>dropdown in navbar - localStorage persistence +
DOMContentLoadedinit - Active theme shows checkmark via CSS
daisyui@5CDN only shipslight+dark; other 4 themes embedded as inline CSS vars inTHEME_STYLE
HTML structure fix
- Removed all duplicate/illegal H1s: 12 routes switched from
Titled("Title", H1(...))toTitle(...), Main(cls="container")(...) _body_wrapuses<Div>not<Main>to avoid nested<main>
