This skill should be used when the user asks to "validate external URLs", "check for link rot", "screenshot URLs", "archive external links", or wants to verify and document external web resources...
Validate all external URLs in the codebase and capture screenshots for documentation.
External URLs in documentation, code comments, and config files can become stale (link rot). This skill:
Find all external URLs in the codebase:
# Search for URLs in common file types
uv run grep -rEoh 'https?://[^\s<>")\]]+' . \
--include='*.md' --include='*.py' --include='*.json' \
--include='*.yaml' --include='*.yml' --include='*.toml' \
--include='*.txt' --include='*.rst' \
2>/dev/null | sort -u
Alternatively, use the Grep tool with pattern: https?://[^\s<>")\]]+
Filter URLs:
Create screenshots directory:
mkdir -p .claude/url-screenshots
Validate and screenshot each URL:
For each URL, use the WebFetch tool to:
For screenshots, use Playwright (preferred) or Puppeteer:
# Install playwright if needed
uv add --dev playwright
uv run playwright install chromium
Create a screenshot script at .claude/skills/validate-urls/screenshot.py:
#!/usr/bin/env python3
"""Capture screenshots of URLs for documentation."""
import asyncio
import hashlib
import json
import sys
from pathlib import Path
from playwright.async_api import async_playwright
async def screenshot_url(url: str, output_dir: Path) -> dict:
"""Take a screenshot of a URL and return metadata."""
url_hash = hashlib.md5(url.encode()).hexdigest()[:12]
filename = f"{url_hash}.png"
filepath = output_dir / filename
result = {
"url": url,
"filename": filename,
"status": "pending",
"title": None,
"error": None
}
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page(viewport={"width": 1280, "height": 720})
try:
response = await page.goto(url, timeout=30000)
result["status"] = "success" if response.ok else f"http_{response.status}"
result["title"] = await page.title()
await page.screenshot(path=str(filepath), full_page=False)
except Exception as e:
result["status"] = "error"
result["error"] = str(e)
finally:
await browser.close()
return result
async def main(urls: list[str], output_dir: str):
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
results = []
for url in urls:
print(f"Processing: {url}")
result = await screenshot_url(url, output_path)
results.append(result)
# Save manifest
manifest_path = output_path / "manifest.json"
with open(manifest_path, "w") as f:
json.dump(results, f, indent=2)
return results
if __name__ == "__main__":
urls = sys.argv[1:]
if not urls:
print("Usage: screenshot.py <url1> <url2> ...")
sys.exit(1)
asyncio.run(main(urls, ".claude/url-screenshots"))
Run the screenshot capture:
uv run python .claude/skills/validate-urls/screenshot.py \
"https://example1.com" "https://example2.com" ...
Generate the report at .claude/url-screenshots/README.md:
# URL Screenshot Archive
Generated: YYYY-MM-DD HH:MM
This archive documents external URLs referenced in the codebase.
## URLs by Domain
### example.com
| URL | Status | Screenshot | Title |
| ------------------------ | ------ | --------------- | ---------- |
| https://example.com/page | OK |  | Page Title |
### another-domain.org
...
## Summary
- Total URLs: X
- Valid: X
- Broken: X
- Redirected: X
## Broken URLs
| URL | Location in Codebase | Error |
| --- | -------------------- | ------------- |
| ... | file.md:42 | 404 Not Found |
Report findings to user:
.claude/url-screenshots/
āāā README.md # Generated report with URL table
āāā manifest.json # Machine-readable URL metadata
āāā abc123def456.png # Screenshots (named by URL hash)
āāā ...
When invoking, the user may specify:
--include-assets: Include CDN/font/icon URLs (default: excluded)--skip-screenshots: Only validate URLs, don't capture screenshots--path <dir>: Only scan specific directory--update: Re-validate and re-screenshot all URLs (default: skip existing)