agentsclimarketplace

Ad spy

Skill NovateStudioGit/novate-studio-skills/paid-growth/ad-spy

57 agent skills for Claude Code — creative production, paid growth, copywriting, ecommerce, email marketing & knowledge ops. By Novate Studio.

Install
npx -y skills add NovateStudioGit/novate-studio-skills --skill ad-spy

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Pull live Meta ads from the Ad Library for a competitor brand — page-ID-resolved, DCO-unwrapped, ranked by longevity, classified by creative format. Triggered by /ad-spy <brand> or "pull ads for X", "ad library teardown".

SKILL.md

5.9 KB, as published. Nobody here has run it

/ad-spy

Pull a competitor's live Meta / Facebook ads from the public Ad Library (via the Apify actor curious_coder/facebook-ads-library-scraper) and return a clean, ranked teardown. The official Meta Ad Library API only returns EU commercial ads, so scraping is required.

When to fire

  • /ad-spy <brand>
  • "pull ads for X"
  • "ad library teardown for Y"
  • "what's brand Z running on Meta right now"
  • "show me brand A's active Facebook ads"

NOT for: owned-account performance (use METAAGENT performance.py), single-reel IG teardowns (/analyze-reel), site/offer/revenue recon (/gambit), or LinkedIn / lead-gen prospect lists (/linkedin-scraper, /lead-scraper).

Inputs

  • brand (required) — display name OR a Meta Page ID (15+ digits → treated as page ID directly).
  • country (default US) — ISO code for the Ad Library country filter.
  • count (default 150) — max ads per page pull. Each page is hard-capped at 150 by the Ad Library; for advertisers with multiple pages, iterate page IDs.

Flow

  1. Resolve the page IDscripts/resolve_page_id.py does a keyword scrape, groups results by page_name, picks the best name match, and returns (page_id, matched_name, confidence). If brand is already a numeric ID, skip resolution. Confidence is high (substring match), med (prefix overlap), or low (no name overlap → flag in output).
  2. Pull by pagescripts/pull.py builds the view_all_page_id + search_type=page Ad Library URL and posts to the Apify actor. Saves raw JSON to out/<brand-slug>-<country>-page.json. Never pull by keyword for primary results — keyword returns mostly noise + mis-attributes generic names (Cadence → "Cadence OTC", Maple Diamonds → "Maple Cottage Designs", JAXXON → "JAXXON For Her").
  3. Parsescripts/parse.py walks each ad and extracts:
    • days_live from start_dateend_date (or now if active)
    • is_videoTrue if snapshot.videos or any cards[].video_*_url is set, else static
    • body_text — DCO-unwrap: prefer snapshot.cards[].body and .title over snapshot.body (which is the {{product.brand}} template). Also pull from extra_texts.
    • angle — slug from link_url (e.g. brand.co/pages/<slug>pcos, perimenopause, gut-health). Single richest persona signal.
    • cta, display_format, collation_count, snapshot_url, page_name, ad_archive_id
  4. Rank + classify:
    • Sort ads by days_live desc — long-runners = proven winners, 3-day clusters = active testing churn.
    • Group ads by angle (landing-page slug) — count, median days, max days per angle.
    • Compute creative mix — video % vs static %.
  5. Output:
    • Raw JSON → out/<brand-slug>-<country>-page.json
    • Parsed JSON → out/<brand-slug>-<country>-parsed.json
    • Print a compact readout: total active ads, creative mix, angles table, top-20 longest-running, sample real (non-template) copy.

Hard-won gotchas (DO NOT regress)

  1. Pull by page ID, not keyword. Keyword search returns ~60% relevant for unique brand names, far less for generic ones. Use resolve_page_id.py first.
  2. 150-ad cap per page pull. For multi-page advertisers (e.g. Grüns runs two: 107585658730958 + 738568902671176), iterate. If total_pulled >= 150, flag capped: true in output and recommend paginating.
  3. DCO copy lives in snapshot.cards[].body / .title, NOT snapshot.body. snapshot.body for DCO ads is the literal template string {{product.brand}}. ~98% of Grüns ads were DCO/SHOP_NOW.
  4. Video classification: check snapshot.videos OR any of video_preview_image_url / video_hd_url / video_sd_url / watermarked_video_sd_url on cards. If none → static (cards or images).
  5. days_live proxy: (end_date or now) - start_date) / 86400. Long-runners (60+ days) are the winning creatives; copy their angle/format.
  6. Landing-page slug = persona/angle. Group by link_url slug; the slug names the avatar Meta optimised toward.
  7. resolve_page_id.py mis-PICKS on short/generic brand names even after grouping (e.g. "Ridge" → "Ridge Belts" med, "Ridge Wallet" → "Ridge Belts"; only "The Ridge" → correct page, high). When confidence is med/low OR the matched name looks wrong, read all_pages_seen — the dominant count is usually the real brand — and re-resolve with that exact page name (or pass --page-id directly). Some brands won't resolve at all via keyword scrape (e.g. "Our Place" / "From Our Place" → only ad-noise, never the brand page); substitute or get the page ID by hand rather than burning pulls.
  8. --quiet is a parse.py flag ONLY, NOT pull.py. Passing --quiet to pull.py makes argparse error out and the pull silently fails. pull.py flags are --country --count --page-id --no-resolve --out-dir + the brand positional. To pull without resolution, use pull.py "<name>" --page-id <id> --no-resolve.

Auth

Reads APIFY_API_TOKEN (or APIFY_TOKEN) and META_AD_LIBRARY_ACTOR from ~/Desktop/AGENTS/METAAGENT/.env. Never echo the token value.

Output structure

~/Desktop/AGENTS/METAAGENT/out/
  <brand-slug>-<country>-page.json      # raw Apify items
  <brand-slug>-<country>-parsed.json    # cleaned + ranked

Readout printed to stdout includes:

  • Resolved page name + confidence flag
  • Total active ads + cap status
  • Creative mix (video vs static)
  • Angles table (slug | count | median days | max days)
  • Top-20 longest-running ads
  • Sample non-template copy from the top runners

Downstream

Output is consumed by the (future) /ad-brief skill, which distils hook + angle + persona into a structured creative brief and writes it into the ExampleBrand Notion META tracker at Status=Brief.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.