A Claude Code skill that runs /weekly-report pulls live SERP data for your target keywords, compares against last week, and formats a concise dashboard in your terminal. The skill runs as a single command, costs $0.005 per keyword checked, and produces a snapshot you can share with your team or paste into Slack.
Prerequisites
- Claude Code CLI installed
- Python 3.8+
- A Scavio API key from scavio.dev
- Target keywords to track
Walkthrough
Step 1: Design the weekly report data model
Define what data the report needs and how to fetch it.
import os, requests, json, sqlite3
from datetime import datetime, timedelta
API_KEY = os.environ['SCAVIO_API_KEY']
SH = {'x-api-key': API_KEY, 'Content-Type': 'application/json'}
KEYWORDS = ['serp api', 'search api python', 'web scraping api',
'ai agent search tool', 'mcp search server']
MY_DOMAIN = 'scavio.dev'
db = sqlite3.connect('weekly_report.db')
db.execute('''CREATE TABLE IF NOT EXISTS weekly (
keyword TEXT, position INTEGER, has_ao INTEGER,
brand_cited INTEGER, checked_at TEXT
)''')
db.commit()
def check_keyword(keyword):
data = requests.post('https://api.scavio.dev/api/v1/search',
headers=SH, json={'query': keyword, 'country_code': 'us',
'include_ai_overview': True}).json()
organic = data.get('organic_results', [])
pos = next((r['position'] for r in organic if MY_DOMAIN in r.get('link', '')), None)
has_ao = bool(data.get('ai_overview'))
ao_text = json.dumps(data.get('ai_overview', {})).lower()
cited = MY_DOMAIN in ao_text if has_ao else False
db.execute('INSERT INTO weekly VALUES (?,?,?,?,?)',
(keyword, pos or 0, int(has_ao), int(cited), datetime.now().isoformat()))
db.commit()
return {'keyword': keyword, 'position': pos, 'has_ao': has_ao, 'cited': cited}
print('Weekly report data model ready.')Step 2: Build the report generation logic
Create the weekly report that compares current vs previous data.
def get_previous_week(keyword):
week_ago = (datetime.now() - timedelta(days=7)).isoformat()
row = db.execute(
'SELECT position, has_ao, brand_cited FROM weekly WHERE keyword=? AND checked_at < ? ORDER BY checked_at DESC LIMIT 1',
(keyword, week_ago)).fetchone()
return {'position': row[0], 'has_ao': bool(row[1]), 'cited': bool(row[2])} if row else None
def weekly_report():
results = []
for kw in KEYWORDS:
current = check_keyword(kw)
previous = get_previous_week(kw)
change = None
if previous and current['position'] and previous['position']:
change = previous['position'] - current['position']
results.append({**current, 'change': change, 'previous': previous})
return results
def format_report(results):
now = datetime.now().strftime('%Y-%m-%d')
lines = []
lines.append(f'Weekly SEO Report - {now}')
lines.append(f'Domain: {MY_DOMAIN}')
lines.append(f'Keywords: {len(results)}')
lines.append('')
lines.append(f'{"Keyword":30} | {"Pos":>4} | {"Chg":>4} | {"AO":>3} | {"Cited":>5}')
lines.append('-' * 60)
for r in results:
pos = f'#{r["position"]}' if r['position'] else '-'
chg = f'{r["change"]:+d}' if r['change'] is not None else 'NEW'
ao = 'Y' if r['has_ao'] else 'N'
cited = 'Y' if r['cited'] else 'N'
lines.append(f'{r["keyword"]:30} | {pos:>4} | {chg:>4} | {ao:>3} | {cited:>5}')
return '\n'.join(lines)
results = weekly_report()
print(format_report(results))Step 3: Add summary metrics
Calculate aggregated metrics for the weekly snapshot.
def summary_metrics(results):
ranked = [r for r in results if r['position']]
improved = [r for r in results if r['change'] and r['change'] > 0]
declined = [r for r in results if r['change'] and r['change'] < 0]
ao_count = sum(1 for r in results if r['has_ao'])
cited_count = sum(1 for r in results if r['cited'])
avg_pos = sum(r['position'] for r in ranked) / len(ranked) if ranked else 0
cost = len(results) * 0.005
lines = []
lines.append(f'\nSummary:')
lines.append(f' Ranked keywords: {len(ranked)}/{len(results)}')
lines.append(f' Average position: {avg_pos:.1f}')
lines.append(f' Improved: {len(improved)} | Declined: {len(declined)}')
lines.append(f' AI Overview coverage: {ao_count}/{len(results)} ({ao_count/len(results)*100:.0f}%)')
lines.append(f' Brand cited in AO: {cited_count}/{len(results)} ({cited_count/len(results)*100:.0f}%)')
lines.append(f' Report cost: ${cost:.3f}')
return '\n'.join(lines)
print(summary_metrics(results))Step 4: Package as a runnable script
Create the script that Claude Code skill can execute.
def run_weekly_report():
"""Main entry point for /weekly-report skill."""
print('Generating weekly SEO report...')
print(f'Checking {len(KEYWORDS)} keywords for {MY_DOMAIN}\n')
results = weekly_report()
report = format_report(results)
summary = summary_metrics(results)
output = f'{report}{summary}'
print(output)
# Save to file for reference
filename = f'report_{datetime.now().strftime("%Y%m%d")}.txt'
with open(filename, 'w') as f:
f.write(output)
print(f'\nSaved to {filename}')
return output
# Run: python weekly_report.py
# Or as Claude Code skill: /weekly-report
run_weekly_report()Python Example
import os, requests, json
SH = {'x-api-key': os.environ['SCAVIO_API_KEY'], 'Content-Type': 'application/json'}
def report(keywords, domain):
for kw in keywords:
data = requests.post('https://api.scavio.dev/api/v1/search',
headers=SH, json={'query': kw, 'country_code': 'us', 'include_ai_overview': True}).json()
pos = next((r['position'] for r in data.get('organic_results', [])
if domain in r.get('link', '')), None)
ao = bool(data.get('ai_overview'))
print(f'{kw:30} | #{pos or "-":>3} | AO: {"Y" if ao else "N"}')
print(f'Cost: ${len(keywords) * 0.005:.3f}')
report(['serp api', 'search api python'], 'scavio.dev')JavaScript Example
const SH = { 'x-api-key': process.env.SCAVIO_API_KEY, 'Content-Type': 'application/json' };
async function report(keywords, domain) {
for (const kw of keywords) {
const data = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST', headers: SH,
body: JSON.stringify({ query: kw, country_code: 'us', include_ai_overview: true })
}).then(r => r.json());
const pos = (data.organic_results || []).find(r => (r.link||'').includes(domain))?.position;
console.log(`${kw.padEnd(30)} | #${pos || '-'} | AO: ${data.ai_overview ? 'Y' : 'N'}`);
}
}
report(['serp api', 'search api python'], 'scavio.dev').catch(console.error);Expected Output
Generating weekly SEO report...
Checking 5 keywords for scavio.dev
Weekly SEO Report - 2026-05-19
Domain: scavio.dev
Keywords: 5
Keyword | Pos | Chg | AO | Cited
------------------------------------------------------------
serp api | #4 | +1 | Y | Y
search api python | #7 | -2 | Y | N
web scraping api | #6 | 0 | N | N
ai agent search tool | #3 | +3 | Y | Y
mcp search server | #5 | NEW | Y | N
Summary:
Ranked keywords: 5/5
Average position: 5.0
Improved: 2 | Declined: 1
AI Overview coverage: 4/5 (80%)
Brand cited in AO: 2/5 (40%)
Report cost: $0.025