seo-geo — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited seo-geo (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Run /seo-geo with an argument to jump to a specific phase, or run without arguments for the full workflow.
Arguments: research, audit, gaps, geo, content, monitor, or a specific page path like /pricing.
Before running, detect the project context automatically:
metadataBase, canonical URLs, or NEXT_PUBLIC_ env vars). Ask the user if unclear.SEO_KEYWORDS.md in the project root. Create if missing.DATAFORSEO_CREDENTIALS env var with your Base64-encoded email:password. Get an account at dataforseo.com. Example: export DATAFORSEO_CREDENTIALS=$(echo -n '[email protected]:yourpassword' | base64)Ask the user for:
If any of these are already documented in CLAUDE.md, SEO_KEYWORDS.md, or memory, use those instead of asking.
Read SEO_KEYWORDS.md first. If it's stale (>30 days) or doesn't exist, run these API calls.
Pull search volume for target keywords via DataForSEO:
curl -s -X POST "https://api.dataforseo.com/v3/keywords_data/google_ads/search_volume/live" \
-H "Authorization: Basic $DATAFORSEO_CREDENTIALS" \
-H "Content-Type: application/json" \
-d '[{"keywords": [LIST], "location_code": 2840, "language_code": "en"}]'Expand with keyword suggestions:
curl -s -X POST "https://api.dataforseo.com/v3/dataforseo_labs/google/keyword_suggestions/live" \
-H "Authorization: Basic $DATAFORSEO_CREDENTIALS" \
-H "Content-Type: application/json" \
-d '[{"keyword": "SEED", "location_code": 2840, "language_code": "en", "limit": 50, "order_by": ["keyword_info.search_volume,desc"]}]'Get difficulty scores:
curl -s -X POST "https://api.dataforseo.com/v3/dataforseo_labs/google/bulk_keyword_difficulty/live" \
-H "Authorization: Basic $DATAFORSEO_CREDENTIALS" \
-H "Content-Type: application/json" \
-d '[{"keywords": [LIST], "location_code": 2840, "language_code": "en"}]'Classify intent:
curl -s -X POST "https://api.dataforseo.com/v3/dataforseo_labs/google/search_intent/live" \
-H "Authorization: Basic $DATAFORSEO_CREDENTIALS" \
-H "Content-Type: application/json" \
-d '[{"keywords": [LIST], "language_code": "en"}]'Pull non-brand keywords from competitors:
curl -s -X POST "https://api.dataforseo.com/v3/dataforseo_labs/google/ranked_keywords/live" \
-H "Authorization: Basic $DATAFORSEO_CREDENTIALS" \
-H "Content-Type: application/json" \
-d '[{"target": "COMPETITOR_DOMAIN", "location_code": 2840, "language_code": "en", "limit": 80, "order_by": ["keyword_data.keyword_info.search_volume,desc"]}]'Filter out brand terms in post-processing.
curl -s -X POST "https://api.dataforseo.com/v3/dataforseo_labs/google/ranked_keywords/live" \
-H "Authorization: Basic $DATAFORSEO_CREDENTIALS" \
-H "Content-Type: application/json" \
-d '[{"target": "SITE_DOMAIN", "location_code": 2840, "language_code": "en", "limit": 50, "order_by": ["ranked_serp_element.serp_item.rank_group,asc"]}]'curl -s -X POST "https://api.dataforseo.com/v3/serp/google/organic/task_post" \
-H "Authorization: Basic $DATAFORSEO_CREDENTIALS" \
-H "Content-Type: application/json" \
-d '[{"keyword":"TARGET","location_code":2840,"language_code":"en","depth":20}]'Retrieve after ~60s:
curl -s "https://api.dataforseo.com/v3/serp/google/organic/task_get/advanced/TASK_ID" \
-H "Authorization: Basic $DATAFORSEO_CREDENTIALS"Output: Update SEO_KEYWORDS.md with fresh data. Summarize top opportunities.
The steps above find keywords you already know about. This step finds keywords you haven't thought of — adjacent topics, competitor ecosystems, and broader search intents that your product intersects with but doesn't directly target.
Process:
curl -s -X POST "https://api.dataforseo.com/v3/dataforseo_labs/google/keyword_suggestions/live" \
-H "Authorization: Basic $DATAFORSEO_CREDENTIALS" \
-H "Content-Type: application/json" \
-d '[{"keyword": "COMPETITOR_NAME", "location_code": 2840, "language_code": "en", "limit": 40, "order_by": ["keyword_info.search_volume,desc"]}]'Run this for each major competitor. Look for "[competitor] alternative", "[competitor] vs", "[competitor] setup", "[competitor] pricing" patterns. These are high-intent keywords where you can insert yourself.
# Search volume for "[product] alternative" and "[product] vs [competitor]"
curl -s -X POST "https://api.dataforseo.com/v3/keywords_data/google_ads/search_volume/live" \
-H "Authorization: Basic $DATAFORSEO_CREDENTIALS" \
-H "Content-Type: application/json" \
-d '[{"keywords": ["PRODUCT alternative", "PRODUCT alternatives", "PRODUCT vs COMPETITOR1", "PRODUCT vs COMPETITOR2", "COMPETITOR1 alternative", "best CATEGORY 2026"], "location_code": 2840, "language_code": "en"}]'curl -s -X POST "https://api.dataforseo.com/v3/dataforseo_labs/google/keyword_suggestions/live" \
-H "Authorization: Basic $DATAFORSEO_CREDENTIALS" \
-H "Content-Type: application/json" \
-d '[{"keyword": "PRODUCT_NAME", "location_code": 2392, "language_code": "ja", "limit": 30, "order_by": ["keyword_info.search_volume,desc"]}]'Location codes: 2156 (China), 2392 (Japan), 2410 (South Korea), 2276 (Germany), 2724 (Spain).
Output: A "lateral opportunities" section in SEO_KEYWORDS.md with:
curl -s -X POST "https://api.dataforseo.com/v3/on_page/task_post" \
-H "Authorization: Basic $DATAFORSEO_CREDENTIALS" \
-H "Content-Type: application/json" \
-d '[{"target": "SITE_DOMAIN", "max_crawl_pages": 50, "max_crawl_depth": 3, "load_resources": true, "enable_javascript": true, "calculate_keyword_density": true, "validate_micromarkup": true, "check_spell": true}]'Get summary:
curl -s -X POST "https://api.dataforseo.com/v3/on_page/summary" \
-H "Authorization: Basic $DATAFORSEO_CREDENTIALS" \
-H "Content-Type: application/json" \
-d '[{"id": "TASK_ID"}]'Get page-level issues:
curl -s -X POST "https://api.dataforseo.com/v3/on_page/pages" \
-H "Authorization: Basic $DATAFORSEO_CREDENTIALS" \
-H "Content-Type: application/json" \
-d '[{"id": "TASK_ID", "limit": 50}]'For each page in the codebase, check:
| Check | What to verify |
|---|---|
<title> | 50-60 chars, primary keyword + brand, unique per page |
<meta description> | 140-160 chars, includes primary + secondary keyword |
| OG tags | og:title, og:description, og:image set and unique |
| Twitter card | twitter:card, twitter:title, twitter:description |
| Canonical URL | Set via alternates.canonical or <link rel="canonical"> |
| H1 | Exactly one per page |
| JSON-LD | Structured data present (Organization, FAQPage, SoftwareApplication, Article) |
| Internal links | Blog posts link to sign-up, pages cross-link |
| Image alt text | All images have descriptive alt attributes |
| robots.txt | Exists at public/robots.txt |
| Sitemap | Exists or generated by framework |
Rules:
[Page Name] — [Keyword Phrase] | BrandRead SEO_KEYWORDS.md and compare against existing pages. For each keyword cluster, identify whether a page targets it.
Look for high-volume, low-difficulty keywords with no targeting page. Common gaps:
For each gap, define: target keyword, page title, audience, word count, internal links.
GEO = Generative Engine Optimization. Making content appear in AI-generated answers (ChatGPT, Perplexity, Google AI Overviews, Claude).
For each page, score these factors (1-10):
| Factor | What to check |
|---|---|
| Clear definitions | Key terms defined in 25-50 word standalone blocks? |
| Quotable statements | Specific, citeable facts with sources? |
| Factual density | Stats with numbers, units, sources? |
| Q&A format | Content answers "What is X?" / "How does X work?" directly? |
| Authority signals | Expert credentials, citations, first-party data? |
| Structure | Tables, numbered lists, clear headings matching query intent? |
AI engine preferences:
| Engine | Priorities |
|---|---|
| Google AI Overview | Direct answer in first 150 words, tables, FAQ schema, JSON-LD |
| ChatGPT Browse | Specific facts, expert quotes, freshness, .edu/.gov trust |
| Perplexity | Freshness bias, quotable standalone statements, primary sources |
| Claude | Authoritative definitions, verifiable facts, reasoning transparency |
Every key concept needs a standalone definition block: Template: **[Term]** is [clear category] that [primary function], [key characteristic].
For pages targeting commercial/informational keywords:
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What is [term]?",
"acceptedAnswer": {
"@type": "Answer",
"text": "[25-50 word definition from the page]"
}
}
]
}{
"@context": "https://schema.org",
"@type": "Organization",
"name": "BRAND_NAME",
"url": "https://SITE_DOMAIN",
"description": "DESCRIPTION",
"email": "CONTACT_EMAIL"
}| ID | Standard | Check |
|---|---|---|
| C02 | Direct answer in first 150 words | Does the page answer its primary query immediately? |
| C04 | Key terms defined on first use | Are technical terms defined inline? |
| C09 | Structured FAQ | Does the page have a Q&A section? |
| O02 | Summary box / key takeaways | Is there a TL;DR? |
| O03 | Data in tables, not prose | Are comparisons in table format? |
| O05 | JSON-LD schema markup | Is structured data present? |
| R01 | 5+ precise data points with units | Are there specific numbers? |
| R04 | Claims backed by evidence | Is every claim sourced? |
| R07 | Full entity names | No "a company" — always use the brand name |
| E01 | Original first-party data | Are own benchmarks/stats shared? |
| Exp10 | Limitations acknowledged | Is scope stated honestly? |
| Ept08 | Reasoning transparency | Are choices explained? |
Re-run ranked_keywords query monthly. Compare against previous SEO_KEYWORDS.md.
Search for the brand in ChatGPT, Perplexity, Google AI Overviews using target keywords.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.