Download, analyze, monitor, and clone competitor ads from Facebook Ad Library. Includes brand asset management, design system capture, competitive intelligence pipeline, and interactive clone sessions with LLM-powered copy generation. Chain with nano-banana-pro for image generation and veo-video-gen for video production.
Resources
2Install
npx skillscat add cgk-platform/cgk/ad-library-dl Install via the SkillsCat registry.
Ad Library Downloader
Download, analyze, monitor, and clone competitor ads from Facebook Ad Library. Full competitive intelligence pipeline with brand asset management and design system capture.
Script Inventory
| # | Script | Purpose |
|---|---|---|
| 1 | ad_library_dl.py |
Download images/videos from Ad Library pages |
| 2 | analyze_competitor.py |
Gemini vision analysis + competitive intelligence brief |
| 2a | analyze_safe.sh |
Safe wrapper for analyze_competitor.py — always use this instead of calling directly |
| 3 | competitor_monitor.py |
Persistent monitoring with rank tracking and scaling detection |
| 3a | monitor_safe.sh |
Safe wrapper for competitor_monitor.py — always use this instead of calling directly |
| 4 | clone_competitor.py |
Interactive clone sessions (two-phase: plan → execute) |
| 5 | clone_preflight.sh |
Pre-flight checks before first clone run |
| 6 | brand_asset_store.py |
Brand product image catalog (SQLite) |
| 7 | brand_crawl.py |
Crawl website for product images + design system extraction |
| 8 | catalog_design_system.py |
Standalone design system capture (Playwright + Gemini) |
| 9 | product_catalog.py |
Product catalog management (pricing, descriptions, images) |
| 10 | ci_store.py |
Competitive intelligence data store (SQLite + optional ChromaDB) |
| 11 | sync_drive.py |
Sync local assets/briefs to Google Drive |
| 12 | brand_resolver.py |
Brand name to Ad Library URL resolver (Brave Search verification) |
| 13 | competitor_discover.py |
Multi-source competitor discovery pipeline |
All scripts use PEP 723 inline dependencies — uv run handles installation automatically.
1. Download Ad Media (ad_library_dl.py)
uv run {baseDir}/scripts/ad_library_dl.py "AD_LIBRARY_URL" --type images --limit 10
# Or use brand name instead of URL:
uv run {baseDir}/scripts/ad_library_dl.py --brand-name "Casper" --type images --limit 10With Drive upload, Slack notification, and offscreen browser:
uv run {baseDir}/scripts/ad_library_dl.py "AD_LIBRARY_URL" --type both --limit 20 --drive --slack --offscreen| Argument | Description | Default |
|---|---|---|
URL |
Facebook Ad Library URL (or use --brand-name) |
— |
--brand-name |
Brand name to resolve to URL (alternative to URL) | — |
--type |
images, videos, or both |
both |
--limit |
Max media files to download | 50 |
--output |
Output directory | ~/Downloads/ad-library/<brand>/<timestamp>/ |
--headed |
Visible browser (debugging) | headless |
--offscreen |
Headed but off-screen (avoids headless detection) | off |
--drive |
Upload to Google Drive | off |
--slack |
Post summary to Slack thread | off |
--purge-days N |
Auto-purge local sessions older than N days | 30 |
Files named by impression rank: rank-001-img-<hash>.jpg, rank-001-vid-<hash>.mp4.
2. Competitive Analysis (analyze_competitor.py)
Full Gemini-powered competitive intelligence pipeline:
uv run {baseDir}/scripts/analyze_competitor.py "AD_LIBRARY_URL" --limit 10 --slack --drive
# Or use brand name:
uv run {baseDir}/scripts/analyze_competitor.py --brand-name "Casper" --limit 10 --slack --drivePipeline: Download → Gemini vision per ad → Master CI brief → Google Doc → Slack.
| Argument | Description | Default |
|---|---|---|
URL |
Facebook Ad Library URL (or use --brand-name) |
— |
--brand-name |
Brand name to resolve to URL (alternative to URL) | — |
--limit |
Max ads to analyze | 10 |
--type |
images, videos, or both |
both |
--slack |
Post results to Slack | off |
--drive |
Upload media to Drive | off |
--model |
Gemini model for master analysis | gemini-3-pro-preview |
--flash-model |
Gemini model for individual analyses | gemini-3-flash-preview |
--offscreen |
Off-screen headed browser | headless |
--force |
Re-download and re-analyze all assets, ignoring dedup cache | off |
--no-persist |
Skip persistent storage (no catalog update, no MCP summary) | off |
Requires: GEMINI_API_KEY environment variable.
3. Competitor Monitoring (competitor_monitor.py)
Persistent tracking with rank change detection and scaling alerts:
uv run {baseDir}/scripts/competitor_monitor.py "AD_LIBRARY_URL" --limit 15 --monitor --slack
# Or use brand name:
uv run {baseDir}/scripts/competitor_monitor.py --brand-name "Casper" --limit 15 --monitor --slack
# Discover + monitor all competitors:
uv run {baseDir}/scripts/competitor_monitor.py --brand-name "Casper" --discover-competitors --monitor --slack| Argument | Description | Default |
|---|---|---|
URL |
Facebook Ad Library URL (or use --brand-name) |
— |
--brand-name |
Brand name to resolve to URL (alternative to URL) | — |
--discover-competitors |
Discover competitors first, then monitor all found | off |
--limit |
Max ads to scan | 15 |
--monitor |
Run full monitoring scan | off |
--slack |
Post results to Slack | off |
--threshold |
Scaling detection threshold (positions) | 3 |
--quiet |
Suppress non-essential output | off |
--force |
Force re-scan even if recently scanned | off |
--model |
Gemini model for analysis | gemini-3-pro-preview |
--flash-model |
Gemini model for per-asset analysis | gemini-3-flash-preview |
--type |
images, videos, or both |
both |
--list-brands |
List all monitored brands | — |
--status --brand X |
Show brand monitoring status | — |
--scaling --brand X |
Show scaling report for a brand | — |
Features: persistent Drive structure, rank history tracking, scaling detection, Drive file management with rank-prefixed names.
4. Clone Workflow (clone_competitor.py)
Interactive session workflow with human-in-the-loop decisions. Batch mode is disabled.
Interactive Clone Sessions (Preferred)
Step-by-step workflow with human-in-the-loop decisions:
# 1. Initialize session — loads catalog, generates initial briefs
bash {baseDir}/scripts/clone_safe.sh init --brand "BrandDir" --top 5 --type statics
# Or resolve brand URL from name:
# bash {baseDir}/scripts/clone_safe.sh init --brand "BrandDir" --brand-url-query "Competitor Name" --top 5 --type statics
# 2. Show competitor ad + analysis in Slack
bash {baseDir}/scripts/clone_safe.sh show-ad --session <ID> --index 0
# 3. Confirm which product to feature
bash {baseDir}/scripts/clone_safe.sh set-product --session <ID> --index 0 --product "Product Name"
# 4. Generate 3 copy variations via LLM
bash {baseDir}/scripts/clone_safe.sh copy-gen --session <ID> --index 0 [--count 3]
# 5. Select copy variation (1-3 or custom)
bash {baseDir}/scripts/clone_safe.sh set-copy --session <ID> --index 0 --choice 1
# Custom copy:
bash {baseDir}/scripts/clone_safe.sh set-copy --session <ID> --index 0 --choice custom --custom-headline "..." --custom-secondary "..." --custom-cta "..."
# 6. Review generation plan (validates ALL prior steps)
bash {baseDir}/scripts/clone_safe.sh plan --session <ID> --index 0
# 7. Execute image generation (synchronous, ~5-10 min)
bash {baseDir}/scripts/clone_safe.sh execute --session <ID> --index 0
# Retry after failure:
bash {baseDir}/scripts/clone_safe.sh execute --session <ID> --index 0 --force
# Skip an ad:
bash {baseDir}/scripts/clone_safe.sh skip --session <ID> --index 0
# Skip all remaining unprocessed ads (bulk shortcut):
bash {baseDir}/scripts/clone_safe.sh skip-all --session <ID>
# Check session progress:
bash {baseDir}/scripts/clone_safe.sh session-status --session <ID>
# List all active sessions:
bash {baseDir}/scripts/clone_safe.sh list-sessionsSubcommand summary:
| Subcommand | Purpose | Prerequisites |
|---|---|---|
init |
Create session, load catalog, generate briefs | — |
show-ad |
Upload competitor image to Slack, display analysis | — |
set-product |
Record confirmed product for ad | show-ad |
copy-gen |
Generate LLM copy variations (Opus → Flash fallback) | set-product |
set-copy |
Record user's copy selection | copy-gen |
plan |
Validate all steps + write generation plan | set-copy |
execute |
Read plan + run image generation (synchronous, ~5-10 min) | plan |
skip |
Skip ad, move to next | — |
skip-all |
Skip all remaining unprocessed ads | — |
session-status |
Show per-ad step completion, verify PID status | — |
list-sessions |
List active/expired sessions | — |
Init flags:
| Flag | Description | Default |
|---|---|---|
--brand |
Brand directory name in catalog (required) | — |
--top |
Clone top N assets by rank | 5 |
--type |
statics or videos |
statics |
--assets |
Comma-separated asset hashes (overrides --top) | — |
--no-match |
Disable brand asset matching | off |
set-copy flags:
| Flag | Description | Default |
|---|---|---|
--session |
Session ID (required) | — |
--index |
Ad index (required) | — |
--choice |
1, 2, 3, or custom (required) |
— |
--custom-headline |
Custom headline text (with --choice custom) |
"" |
--custom-secondary |
Custom secondary body text (with --choice custom) |
"" |
--custom-cta |
Custom CTA text (with --choice custom) |
"" |
Sessions stored at workspace/.clone-sessions/cln-*.json (mode 0600, 4hr TTL by default, thread-affine). Override TTL via CLONE_SESSION_TTL env var (seconds), e.g. CLONE_SESSION_TTL=28800 for 8 hours.
5. Pre-Flight Check (clone_preflight.sh)
Run before first clone to verify brand setup:
bash {baseDir}/scripts/clone_preflight.shChecks: brand identity exists, product images available, logo present, catalog health, competitors directory populated. Reports [OK], [WARN], [CRIT] for each check. Fix [CRIT] issues before cloning.
6. Brand Asset Catalog (brand_asset_store.py)
SQLite catalog of brand product images, logos, and creative assets:
# Add images (with optional metadata flags)
uv run {baseDir}/scripts/brand_asset_store.py add --type product-shot --product "Product Name" /path/to/image.jpg
# Bulk add directory
uv run {baseDir}/scripts/brand_asset_store.py add --type lifestyle /path/to/dir/
# Search assets (full-text search)
uv run {baseDir}/scripts/brand_asset_store.py search "chocolate shake jar"
# List with filters
uv run {baseDir}/scripts/brand_asset_store.py list --type product-shot
# Catalog stats
uv run {baseDir}/scripts/brand_asset_store.py stats
# Import from product-images.json
uv run {baseDir}/scripts/brand_asset_store.py import-json /path/to/product-images.json
# Import images from a directory
uv run {baseDir}/scripts/brand_asset_store.py import-dir /path/to/dir/ --product "Product Name"
# Catalog health check
uv run {baseDir}/scripts/brand_asset_store.py health
uv run {baseDir}/scripts/brand_asset_store.py health --jsonAsset types: product-shot, lifestyle, packaging, logo-variant, ugc, texture, model, ingredient, before-after, hero, flat-lay, other
add flags:
| Flag | Description | Default |
|---|---|---|
files |
Image files or directories (positional, required) | — |
--type |
Asset type (see list above) | other |
--source |
crawled, uploaded, or generated |
uploaded |
--ownership |
ours or competitor |
ours |
--product |
Product name | — |
--collection |
Collection name | — |
--tags |
Comma-separated tags | — |
--no-analyze |
Skip Gemini analysis | off |
import-dir flags:
| Flag | Description | Default |
|---|---|---|
directory |
Directory containing images (positional, required) | — |
--product |
Product name (default: inferred from dir name) | — |
--type |
Asset type | product-shot |
--no-analyze |
Skip Gemini analysis | off |
7. Website Crawler (brand_crawl.py)
Crawl website for product images and design system extraction:
# Product image crawl (default)
uv run {baseDir}/scripts/brand_crawl.py --depth 2 --slack
# Explicit URL
uv run {baseDir}/scripts/brand_crawl.py --url "https://example.com" --all-pages --limit 100
# Design system extraction — captures CSS tokens, fonts, colors
uv run {baseDir}/scripts/brand_crawl.py --url "https://example.com" --design-system| Argument | Description | Default |
|---|---|---|
--url |
Website URL to crawl | BRAND_WEBSITE_URL env var |
--depth |
Max crawl depth | 2 |
--min-size |
Min image dimension (px) | 300 |
--limit |
Max images to discover | 200 |
--all-pages |
Crawl all pages (not just product pages) | off |
--slack |
Post progress to Slack | off |
--design-system |
Extract CSS tokens, fonts, colors → write to brand/ | off |
8. Design System Capture (catalog_design_system.py)
Standalone design system extraction using Playwright and Gemini:
uv run {baseDir}/scripts/catalog_design_system.py <url>Captures: color palette, typography stack, spacing tokens, component patterns. Outputs structured markdown to colors.md, typography.md, design-rules.md.
| Argument | Description | Default |
|---|---|---|
url |
Website URL to capture (positional, required) | — |
--depth |
Max crawl depth | 2 |
--slack |
Post summary to Slack | off |
9. Product Catalog (product_catalog.py)
Manage product pricing, descriptions, and image associations for LLM copy generation:
uv run {baseDir}/scripts/product_catalog.py --url "https://example.com" --brand "BrandDir"
uv run {baseDir}/scripts/product_catalog.py --url "https://example.com" --limit 100 --no-images
uv run {baseDir}/scripts/product_catalog.py --from-landing-pages
uv run {baseDir}/scripts/product_catalog.py --slack| Flag | Description | Default |
|---|---|---|
--url |
Storefront URL to scrape | — |
--brand |
Brand directory name | ours |
--limit |
Max products to scrape | 50 |
--no-images |
Skip image download | off |
--slack |
Post progress to Slack | off |
--from-landing-pages |
Scrape from saved landing pages instead of live URL | off |
10. CI Data Store (ci_store.py)
SQLite + optional ChromaDB store for competitive intelligence data:
# List monitored brands
uv run {baseDir}/scripts/ci_store.py brands
# Show brand status
uv run {baseDir}/scripts/ci_store.py status <brand>
# Scaling report for a brand
uv run {baseDir}/scripts/ci_store.py scaling <brand> [--threshold N]
# Search analyses by keyword
uv run {baseDir}/scripts/ci_store.py search "hook technique" [--brand BrandDir] [--limit N]
# Rank history for a specific asset
uv run {baseDir}/scripts/ci_store.py history <asset_hash>
# Backfill ChromaDB from existing analyses
uv run {baseDir}/scripts/ci_store.py backfill
# Force re-migration from catalog.json
uv run {baseDir}/scripts/ci_store.py migrateSubcommand summary:
| Subcommand | Purpose |
|---|---|
brands |
List all monitored brands |
status <brand> |
Show brand monitoring status and asset counts |
scaling <brand> |
Show scaling report for a brand |
search <query> |
Full-text search across analyses |
history <hash> |
Rank history for a specific asset |
backfill |
Backfill ChromaDB from existing SQLite analyses |
migrate |
Force re-migration from catalog.json |
Stores: asset analyses, clone records, rank history, scaling events. Used by clone_competitor.py for clone persistence and by competitor_monitor.py for rank tracking.
11. Drive Sync (sync_drive.py)
Sync local assets, briefs, and creatives to Google Drive:
uv run {baseDir}/scripts/sync_drive.py --brand "BrandDir"
uv run {baseDir}/scripts/sync_drive.py --dry-run
uv run {baseDir}/scripts/sync_drive.py --sync-only
uv run {baseDir}/scripts/sync_drive.py --reconcile| Flag | Description | Default |
|---|---|---|
--brand |
Filter to a specific brand directory name | all brands |
--dry-run |
Preview changes without applying | off |
--sync-only |
Phase 1 only: catalog → ci.db (skip Drive upload) | off |
--reconcile |
Phase 1 + 3: match existing Drive files (skip Phase 2 upload) | off |
Syncs: competitor ad media, analysis docs, clone briefs, generated creatives. Creates/updates the Drive folder structure under Competitor Ads/<Brand>/.
12. Brand Resolver (brand_resolver.py)
Resolve brand names to Facebook Ad Library URLs with optional Brave Search verification:
# Resolve a brand name to Ad Library URL
uv run {baseDir}/scripts/brand_resolver.py "Casper"
# JSON output only
uv run {baseDir}/scripts/brand_resolver.py "Casper" --json
# Also discover competitors
uv run {baseDir}/scripts/brand_resolver.py "Casper" --competitors --limit 10
# With category context
uv run {baseDir}/scripts/brand_resolver.py "Casper" --competitors --category "mattress" --limit 10| Argument | Description | Default |
|---|---|---|
brand |
Brand name to resolve (positional, required) | -- |
--country |
Country code for Ad Library | US |
--json |
JSON output only | off |
--competitors |
Also discover competitors | off |
--category |
Category context for competitor discovery | -- |
--limit |
Max competitors to return | 10 |
Returns {brand_name, page_id, page_name, url, confidence_score, source}. Confidence: high (exact match + page ID), medium (exact match), low (no Brave key or no match).
Optional: BRAVE_SEARCH_API_KEY env var enables verification and page ID extraction. Without it, returns a constructed URL with low confidence.
13. Competitor Discovery (competitor_discover.py)
Multi-source competitor discovery pipeline:
# Discover competitors for a brand
uv run {baseDir}/scripts/competitor_discover.py "Casper" --limit 10 --slack
# Category-based discovery
uv run {baseDir}/scripts/competitor_discover.py --category "bedding" --limit 10
# With auto-monitor command output
uv run {baseDir}/scripts/competitor_discover.py "Casper" --auto-monitor
# JSON output
uv run {baseDir}/scripts/competitor_discover.py "Casper" --json| Argument | Description | Default |
|---|---|---|
brand_name |
Source brand name (positional, optional) | -- |
--category |
Product category for discovery | -- |
--limit |
Max competitors to discover | 10 |
--slack |
Post results to Slack thread | off |
--use-brave |
Use Brave Search API | on if BRAVE_SEARCH_API_KEY set |
--auto-monitor |
Print competitor_monitor.py commands for each brand |
off |
--json |
JSON output only | off |
Stores results to workspace/brand/competitors/discovered.json. Merges with existing discoveries on subsequent runs.
Requires: BRAVE_SEARCH_API_KEY environment variable.
Workflow: Zero-URL Workflow
No need to find Facebook Ad Library URLs manually. Just use brand names:
# 1. Discover competitors
uv run {baseDir}/scripts/competitor_discover.py "Casper" --limit 5 --slack
# 2. Analyze a competitor by name
uv run {baseDir}/scripts/analyze_competitor.py --brand-name "Purple" --limit 10 --slack --drive
# 3. Monitor a competitor by name
uv run {baseDir}/scripts/competitor_monitor.py --brand-name "Purple" --limit 15 --monitor --slack
# 4. Discover + monitor all competitors in one command
uv run {baseDir}/scripts/competitor_monitor.py --brand-name "Casper" --discover-competitors --monitor --slack
# 5. Download competitor ads by name
uv run {baseDir}/scripts/ad_library_dl.py --brand-name "Purple" --type both --limit 20Workflow: End-to-End Clone Pipeline
- Crawl —
brand_crawl.pyto build brand asset catalog - Pre-flight —
clone_preflight.shto verify brand setup - Monitor —
competitor_monitor.pyto download + analyze competitor ads - Clone —
clone_competitor.py initfor interactive session - Generate — Pixel receives briefs with
--input-imageproduct references
Notes
- Clone generation uses two-phase architecture:
clone_competitor.py planvalidates all steps and writes a plan file, thenclone_competitor.py executereads the plan and runs_generate_ads_internal.shdirectly.generate_ads_safe.sh --category clonehard-rejects with a redirect message. NEVER callgenerate_ads_safe.shor_generate_ads_internal.shdirectly for clone workflows. - Default browser mode is headless. Use
--offscreenfor agent/automated use. - Requires
yt-dlpfor video download fallback. - Playwright Chromium auto-installs on first run.
- Facebook CDN URLs are publicly downloadable without auth.
- Images <100x100px skipped (icons/avatars).
- Google Drive upload requires
gogCLI. - Slack posting reads
SLACK_CHANNEL_ID+SLACK_THREAD_TSfrom env. - All scripts use PEP 723 inline dependencies —
uv runhandles installation automatically.