meta-ad-spy

Competitive intelligence skill for spying on competitor ads using Meta's Ad Library. Use this skill whenever the user wants to: research competitor Facebook/Instagram ads, analyze ad strategies, extract ad creatives or copy, find how long ads have been running, scout ad spend patterns, monitor industry advertising trends, or build any kind of competitor ad intelligence report. Triggers on phrases like "check competitor ads", "what ads is [brand] running", "spy on ads", "Facebook ad library", "Meta ad library", "scrape ads", "monitor ads", "ad intelligence", "ad research", or any request to analyze advertising strategies on Meta platforms. Always use this skill even if the user just mentions they want to understand what a competitor is doing on Facebook or Instagram.

3,891 stars

Best use case

meta-ad-spy is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Competitive intelligence skill for spying on competitor ads using Meta's Ad Library. Use this skill whenever the user wants to: research competitor Facebook/Instagram ads, analyze ad strategies, extract ad creatives or copy, find how long ads have been running, scout ad spend patterns, monitor industry advertising trends, or build any kind of competitor ad intelligence report. Triggers on phrases like "check competitor ads", "what ads is [brand] running", "spy on ads", "Facebook ad library", "Meta ad library", "scrape ads", "monitor ads", "ad intelligence", "ad research", or any request to analyze advertising strategies on Meta platforms. Always use this skill even if the user just mentions they want to understand what a competitor is doing on Facebook or Instagram.

Teams using meta-ad-spy should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/meta-ad-spy/SKILL.md --create-dirs "https://raw.githubusercontent.com/openclaw/skills/main/skills/abhishekj9621/meta-ad-spy/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/meta-ad-spy/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How meta-ad-spy Compares

Feature / Agentmeta-ad-spyStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Competitive intelligence skill for spying on competitor ads using Meta's Ad Library. Use this skill whenever the user wants to: research competitor Facebook/Instagram ads, analyze ad strategies, extract ad creatives or copy, find how long ads have been running, scout ad spend patterns, monitor industry advertising trends, or build any kind of competitor ad intelligence report. Triggers on phrases like "check competitor ads", "what ads is [brand] running", "spy on ads", "Facebook ad library", "Meta ad library", "scrape ads", "monitor ads", "ad intelligence", "ad research", or any request to analyze advertising strategies on Meta platforms. Always use this skill even if the user just mentions they want to understand what a competitor is doing on Facebook or Instagram.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

Related Guides

SKILL.md Source

# Meta Ad Spy — Competitor Ad Intelligence Skill

A two-phase skill for extracting and analyzing competitor ads from Meta platforms.

## Architecture Overview

```
Phase 1: Playwright Scraper (No API key needed)
  └── facebook.com/ads/library → Ad creatives, copy, status, platforms, dates
  
Phase 2: Meta Graph API (Requires access token)
  └── graph.facebook.com/v23.0/ads_archive → Spend ranges, impressions, demographics
  
Analysis Layer: Claude synthesizes insights from both sources
```

---

## PHASE 1: Playwright Scraper

**When to use**: Always as the first step, or when user has no API token.  
**What it gets**: Ad creatives (image/video URLs), ad copy, CTA text, page name, start date, active status, platforms (Facebook/Instagram), ad format (carousel, video, static).  
**What it can't get**: Spend ranges, impressions, demographic breakdown (those need Phase 2).

### Setup

```bash
pip install playwright --break-system-packages
playwright install chromium
pip install asyncio --break-system-packages
```

### Core Playwright Script

Write this to `/tmp/meta_ad_scraper.py`:

```python
import asyncio
import json
import re
import sys
from playwright.async_api import async_playwright

async def scrape_ad_library(
    search_query: str = None,
    page_id: str = None,
    country: str = "ALL",
    ad_type: str = "all",           # all | political_and_issue_ads | housing_ads
    active_status: str = "active",  # active | inactive | all
    media_type: str = "all",        # all | image | meme | video | none
    max_ads: int = 50
) -> list[dict]:
    """
    Scrape Meta Ad Library for competitor ads.
    Either search_query or page_id must be provided.
    """
    results = []

    # Build URL
    base = "https://www.facebook.com/ads/library/?"
    params = {
        "active_status": active_status,
        "ad_type": ad_type,
        "country": country,
        "media_type": media_type,
    }
    if search_query:
        params["q"] = search_query
        params["search_type"] = "keyword_unordered"
    elif page_id:
        params["view_all_page_id"] = page_id
        params["search_type"] = "page"
    
    url = base + "&".join(f"{k}={v}" for k, v in params.items())

    async with async_playwright() as p:
        browser = await p.chromium.launch(
            headless=True,
            args=[
                "--no-sandbox",
                "--disable-blink-features=AutomationControlled",
                "--disable-dev-shm-usage",
            ]
        )
        context = await browser.new_context(
            viewport={"width": 1440, "height": 900},
            user_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
            locale="en-US",
        )
        # Stealth: mask webdriver
        await context.add_init_script("""
            Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
        """)

        page = await context.new_page()
        print(f"[Phase 1] Navigating to: {url}")
        await page.goto(url, wait_until="networkidle", timeout=30000)
        await page.wait_for_timeout(3000)

        # Scroll to load more ads
        ads_loaded = 0
        scroll_attempts = 0
        while ads_loaded < max_ads and scroll_attempts < 20:
            await page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
            await page.wait_for_timeout(2000)
            # Count ad cards
            ad_cards = await page.query_selector_all('[data-testid="ad-card"], ._7jvw, [class*="x8t9es0"]')
            ads_loaded = len(ad_cards)
            scroll_attempts += 1
            if scroll_attempts % 5 == 0:
                print(f"[Phase 1] Loaded {ads_loaded} ads so far...")

        # Extract ad data via JavaScript
        ads_data = await page.evaluate("""
            () => {
                const ads = [];
                // Meta Ad Library renders ads in divs; extract all visible text/image data
                // Look for ad archive links which contain library IDs
                const links = document.querySelectorAll('a[href*="ads/archive"]');
                const seen_ids = new Set();
                links.forEach(link => {
                    const href = link.href;
                    const id_match = href.match(/id=(\d+)/);
                    if (id_match && !seen_ids.has(id_match[1])) {
                        seen_ids.add(id_match[1]);
                        // Walk up to find the ad container
                        let container = link;
                        for (let i = 0; i < 8; i++) {
                            container = container.parentElement;
                            if (!container) break;
                        }
                        const getText = (el, fallback='') => el ? el.innerText.trim() : fallback;
                        const getAttr = (el, attr, fallback='') => el ? el.getAttribute(attr) || fallback : fallback;
                        
                        ads.push({
                            ad_archive_id: id_match[1],
                            ad_snapshot_url: href,
                            page_name: getText(container?.querySelector('[class*="page-name"], strong')),
                            ad_body: getText(container?.querySelector('[data-ad-preview="message"], [class*="body"]')),
                            ad_title: getText(container?.querySelector('[class*="title"]')),
                            cta_text: getText(container?.querySelector('[class*="cta"], button')),
                            image_url: getAttr(container?.querySelector('img[src*="fbcdn"]'), 'src'),
                            started_running: getText(container?.querySelector('[class*="started-running"]')),
                            platforms: Array.from(container?.querySelectorAll('[class*="platform"]') || []).map(el => el.innerText.trim()).filter(Boolean),
                            raw_text: container?.innerText?.substring(0, 500) || '',
                        });
                    }
                });
                return ads;
            }
        """)
        
        # Also capture network requests for richer data
        print(f"[Phase 1] Extracted {len(ads_data)} ads from DOM")
        results = ads_data[:max_ads]
        
        await browser.close()
    
    return results


async def main():
    query = sys.argv[1] if len(sys.argv) > 1 else "Nike shoes"
    ads = await scrape_ad_library(search_query=query, max_ads=20)
    print(json.dumps(ads, indent=2, ensure_ascii=False))


if __name__ == "__main__":
    asyncio.run(main())
```

### How to Run Phase 1

```bash
python /tmp/meta_ad_scraper.py "competitor brand name"
```

Or from within Python (for page ID lookups):
```python
ads = await scrape_ad_library(page_id="434174436675167", active_status="active")
```

### Filters Available in Phase 1

| Filter | Values | Notes |
|--------|--------|-------|
| `active_status` | `active`, `inactive`, `all` | `active` = currently running |
| `ad_type` | `all`, `political_and_issue_ads`, `housing_ads`, `employment_ads`, `credit_ads` | Default: all |
| `country` | `ALL`, `US`, `IN`, `GB`, `DE`, `FR`, `AU`, etc. | ISO codes |
| `media_type` | `all`, `image`, `meme`, `video`, `none` | Filter by creative format |
| `search_query` | Any keyword string | Brand name, product, keyword |
| `page_id` | Facebook Page ID | More precise than keyword search |

---

## PHASE 2: Meta Graph API

**When to use**: After Phase 1, or when user wants spend/impression/demographic data.  
**Requirements**: Meta developer account + access token (see setup below).  
**What it gets**: Spend ranges, impression ranges, demographic distribution (EU/political), delivery by region, ad creative details, estimated audience size.

### Setup Instructions (tell the user)

1. Go to [Meta for Developers](https://developers.facebook.com/) → Create App
2. Go to [facebook.com/ID](https://www.facebook.com/ID) → Confirm identity (required for spend data)
3. Generate a User Access Token with `ads_read` permission from [Graph API Explorer](https://developers.facebook.com/tools/explorer/)
4. Set as env var: `export META_ACCESS_TOKEN="your_token_here"`

### Core API Script

Write this to `/tmp/meta_ad_api.py`:

```python
import requests
import json
import os
import time
import sys
from typing import Optional

META_API_VERSION = "v23.0"
BASE_URL = f"https://graph.facebook.com/{META_API_VERSION}/ads_archive"

# All available fields from the API
ALL_FIELDS = [
    "id",
    "ad_archive_id", 
    "ad_creative_bodies",
    "ad_creative_link_captions",
    "ad_creative_link_descriptions", 
    "ad_creative_link_titles",
    "ad_delivery_start_time",
    "ad_delivery_stop_time",
    "ad_snapshot_url",
    "bylines",
    "delivery_by_region",
    "demographic_distribution",
    "estimated_audience_size",
    "impressions",
    "page_id",
    "page_name",
    "publisher_platforms",
    "spend",
    "languages",
    "currency",
    "ad_creative_link_caption",
    "ad_creative_link_url",
]

def query_ad_library(
    access_token: str,
    search_terms: str = None,
    search_page_ids: list[str] = None,
    ad_reached_countries: list[str] = ["US"],
    ad_active_status: str = "ACTIVE",     # ACTIVE | INACTIVE | ALL
    ad_type: str = "ALL",                  # ALL | POLITICAL_AND_ISSUE_ADS | etc.
    ad_delivery_date_min: str = None,      # "YYYY-MM-DD"
    ad_delivery_date_max: str = None,      # "YYYY-MM-DD"
    publisher_platforms: list[str] = None, # ["FACEBOOK", "INSTAGRAM"]
    languages: list[str] = None,
    limit: int = 50,
    max_pages: int = 5,
) -> list[dict]:
    """
    Query Meta Ad Library API with full pagination support.
    Returns list of ad objects with all available fields.
    """
    if not access_token:
        raise ValueError("META_ACCESS_TOKEN is required for Phase 2")

    params = {
        "access_token": access_token,
        "ad_active_status": ad_active_status,
        "ad_type": ad_type,
        "ad_reached_countries": json.dumps(ad_reached_countries),
        "fields": ",".join(ALL_FIELDS),
        "limit": min(limit, 500),  # API max per page
    }
    
    if search_terms:
        params["search_terms"] = search_terms
    if search_page_ids:
        params["search_page_ids"] = ",".join(search_page_ids)
    if ad_delivery_date_min:
        params["ad_delivery_date_min"] = ad_delivery_date_min
    if ad_delivery_date_max:
        params["ad_delivery_date_max"] = ad_delivery_date_max
    if publisher_platforms:
        params["publisher_platforms"] = json.dumps(publisher_platforms)
    if languages:
        params["languages"] = json.dumps(languages)

    all_ads = []
    page_count = 0
    next_url = None

    while page_count < max_pages:
        try:
            if next_url:
                response = requests.get(next_url, timeout=30)
            else:
                response = requests.get(BASE_URL, params=params, timeout=30)
            
            response.raise_for_status()
            data = response.json()
            
            if "error" in data:
                print(f"[Phase 2] API Error: {data['error']}", file=sys.stderr)
                break
            
            ads = data.get("data", [])
            all_ads.extend(ads)
            page_count += 1
            print(f"[Phase 2] Page {page_count}: fetched {len(ads)} ads (total: {len(all_ads)})")
            
            # Pagination
            paging = data.get("paging", {})
            next_url = paging.get("next")
            if not next_url or len(all_ads) >= limit:
                break
            
            time.sleep(1)  # Rate limit courtesy
            
        except requests.exceptions.RequestException as e:
            print(f"[Phase 2] Request error: {e}", file=sys.stderr)
            break

    return all_ads[:limit]


def analyze_ads(ads: list[dict]) -> dict:
    """
    Extract competitive intelligence insights from raw ad data.
    """
    if not ads:
        return {"error": "No ads found"}

    # Spend analysis
    spends = []
    for ad in ads:
        spend = ad.get("spend", {})
        if isinstance(spend, dict):
            lo = spend.get("lower_bound", 0)
            hi = spend.get("upper_bound", 0)
            if lo and hi:
                spends.append({"ad_id": ad.get("ad_archive_id"), "min": int(lo), "max": int(hi), "midpoint": (int(lo)+int(hi))//2})

    # Platform distribution
    platform_counts = {}
    for ad in ads:
        for p in ad.get("publisher_platforms", []):
            platform_counts[p] = platform_counts.get(p, 0) + 1

    # Ad longevity (proxy for performance — longer running = likely working)
    from datetime import datetime
    long_running = []
    for ad in ads:
        start = ad.get("ad_delivery_start_time")
        if start:
            try:
                days = (datetime.now() - datetime.fromisoformat(start.replace("Z",""))).days
                long_running.append({"ad_id": ad.get("ad_archive_id"), "days_running": days, "page": ad.get("page_name")})
            except:
                pass
    long_running.sort(key=lambda x: x["days_running"], reverse=True)

    # Creative format distribution
    creative_bodies = [ad.get("ad_creative_bodies", []) for ad in ads if ad.get("ad_creative_bodies")]
    
    return {
        "total_ads": len(ads),
        "spend_analysis": {
            "ads_with_spend_data": len(spends),
            "estimated_total_min_spend": sum(s["min"] for s in spends),
            "estimated_total_max_spend": sum(s["max"] for s in spends),
            "top_spenders": sorted(spends, key=lambda x: x["midpoint"], reverse=True)[:5],
        },
        "platform_distribution": platform_counts,
        "longest_running_ads": long_running[:10],
        "pages_advertising": list(set(ad.get("page_name") for ad in ads if ad.get("page_name"))),
        "sample_creatives": [
            {
                "page": ad.get("page_name"),
                "body": (ad.get("ad_creative_bodies") or [""])[0][:300],
                "title": (ad.get("ad_creative_link_titles") or [""])[0],
                "platforms": ad.get("publisher_platforms", []),
                "snapshot_url": ad.get("ad_snapshot_url"),
            }
            for ad in ads[:10]
        ]
    }


if __name__ == "__main__":
    token = os.environ.get("META_ACCESS_TOKEN", "")
    search = sys.argv[1] if len(sys.argv) > 1 else "Nike"
    ads = query_ad_library(token, search_terms=search, ad_reached_countries=["US"], limit=50)
    analysis = analyze_ads(ads)
    print(json.dumps(analysis, indent=2, ensure_ascii=False))
    # Also save raw data
    with open("/tmp/meta_ads_raw.json", "w") as f:
        json.dump(ads, f, indent=2, ensure_ascii=False)
    print(f"\n[Phase 2] Raw data saved to /tmp/meta_ads_raw.json")
```

### API Filter Reference

| Parameter | Values | Notes |
|-----------|--------|-------|
| `search_terms` | Any string | Keyword search in ad content |
| `search_page_ids` | List of FB page IDs | Most precise competitor lookup |
| `ad_reached_countries` | `["US"]`, `["IN"]`, `["GB","DE"]` | Required parameter |
| `ad_active_status` | `ACTIVE`, `INACTIVE`, `ALL` | ACTIVE = currently live |
| `ad_type` | `ALL`, `POLITICAL_AND_ISSUE_ADS`, `HOUSING_ADS`, `EMPLOYMENT_ADS`, `FINANCIAL_SERVICES` | Filter by category |
| `ad_delivery_date_min` | `"2024-01-01"` | Start of date range |
| `ad_delivery_date_max` | `"2024-12-31"` | End of date range |
| `publisher_platforms` | `["FACEBOOK"]`, `["INSTAGRAM"]`, `["FACEBOOK","INSTAGRAM"]` | Platform filter |
| `languages` | `["en"]`, `["hi"]`, `["es"]` | Language codes |

### Data Fields Available from API

**Always available (all ads):**
- `ad_archive_id` — Unique ad ID
- `page_id`, `page_name` — Advertiser page
- `ad_creative_bodies` — Ad copy text(s)
- `ad_creative_link_titles`, `ad_creative_link_descriptions` — Headlines
- `ad_delivery_start_time`, `ad_delivery_stop_time` — Run dates
- `publisher_platforms` — FB/Instagram/Messenger/Audience Network
- `ad_snapshot_url` — Link to view the actual ad

**EU/UK/Political ads only:**
- `spend` — `{lower_bound, upper_bound, currency}` — Spend RANGE, not exact
- `impressions` — `{lower_bound, upper_bound}` — Impression RANGE
- `estimated_audience_size` — `{lower_bound, upper_bound}`
- `demographic_distribution` — `[{age, gender, percentage}]` array
- `delivery_by_region` — Geographic breakdown
- `bylines` — "Paid for by" disclaimer

> ⚠️ **Important**: Spend and impressions are RANGES, not exact numbers. For non-EU/non-political ads in most countries including US and India, spend/impression data will NOT be returned. The official API is primarily a transparency tool. For richer commercial ad data, see the third-party alternatives in `references/alternatives.md`.

---

## ANALYSIS WORKFLOW

When a user wants competitor ad intelligence, follow this flow:

### Step 1 — Clarify the Target

Ask (or infer from context):
- **Who** — brand name OR Facebook Page ID (better)
- **Where** — country/region (`US`, `IN`, `ALL`, etc.)
- **What** — active only, or historical too?
- **Goal** — creative inspiration, spend monitoring, format analysis, copy patterns?

### Step 2 — Find the Page ID (if only brand name given)

```bash
# Tell user to visit: https://www.facebook.com/ads/library/?q=BRAND_NAME
# The page_id appears in the URL when clicking on a page
# OR use the search API:
curl "https://graph.facebook.com/v23.0/pages/search?q=BRAND_NAME&access_token=TOKEN"
```

### Step 3 — Run Phase 1 (Playwright)

Always run Phase 1 first. Write and execute `/tmp/meta_ad_scraper.py`.

### Step 4 — Run Phase 2 (API), if token available

Check for `META_ACCESS_TOKEN` env var. If present, run Phase 2.
If missing, tell user what Phase 2 would add, and give setup instructions.

### Step 5 — Synthesize & Report

Produce a structured competitive intelligence report covering:

```
## 🕵️ Competitor Ad Intelligence Report: [Brand Name]

### 1. Current Ad Activity
- How many ads active right now
- Platforms being used (FB vs Instagram split)
- Ad formats (video, image, carousel)

### 2. Creative Strategy Analysis  
- Common themes in ad copy
- CTA patterns (Shop Now, Learn More, Sign Up, etc.)
- Headline formulas being used
- Hook styles (question, statement, social proof, urgency)

### 3. Ad Longevity Signals
- Longest-running ads (strong = likely performing well)
- New ads launched recently (testing phase)

### 4. Spend & Scale Signals (Phase 2 only, EU/political)
- Estimated spend ranges
- Impression volume estimates
- Geographic distribution

### 5. Audience Signals (Phase 2 EU only)
- Age/gender demographic breakdown
- Platform delivery split

### 6. Strategic Recommendations
- Gaps in competitor's strategy you can exploit
- Formats/messages they're NOT using
- High-performing creative patterns to draw inspiration from
```

---

## COMMON WORKFLOWS

### "What ads is [Brand] running right now?"

```python
# Phase 1
ads = await scrape_ad_library(search_query="Brand Name", active_status="active")

# Phase 2 (if token available)
ads_api = query_ad_library(token, search_terms="Brand Name", ad_active_status="ACTIVE")
```

### "Show me competitor video ads in India"

```python
ads = await scrape_ad_library(
    search_query="competitor name",
    country="IN",
    media_type="video",
    active_status="active"
)
```

### "How much is [Brand] spending on ads?" (EU/political only)

```python
ads = query_ad_library(
    token,
    search_terms="Brand",
    ad_reached_countries=["GB"],  # or EU countries
    ad_type="ALL",
)
analysis = analyze_ads(ads)
# Look at analysis["spend_analysis"]
```

### "Show me ads that have been running the longest" (= likely winners)

```python
ads = query_ad_library(token, search_terms="Brand", ad_active_status="ALL")
analysis = analyze_ads(ads)
# analysis["longest_running_ads"] — sorted by days running
```

### "Find ads about [topic/product keyword]"

```python
ads = await scrape_ad_library(search_query="keyword phrase", active_status="all")
```

---

## LIMITATIONS & WORKAROUNDS

| Limitation | Workaround |
|------------|------------|
| Spend data only for EU/political | Target EU countries in API query |
| No CTR/conversion data | Use ad longevity as performance proxy |
| Phase 1 DOM selectors may break | Fall back to raw text extraction + Claude analysis |
| Rate limits on API | Add `time.sleep(1)` between pages, use cursor pagination |
| Max ~429 ads per API session | Run multiple targeted queries, filter by date ranges |
| No exact targeting info | Infer from demographic distribution (EU only) |

---

## NOTES ON LEGALITY & ETHICS

- The Meta Ad Library is **public data** — no login required for commercial ads
- Using it for competitive research is explicitly Meta's stated purpose for the tool
- The official API is a **transparency tool** — use it as intended
- Playwright scraping of public pages is generally legal (ref: hiQ v. LinkedIn, 2022)
- Do NOT attempt to scrape user data, private profiles, or non-public content
- Always respect rate limits and avoid aggressive scraping

---

## SEE ALSO

- `references/alternatives.md` — Third-party APIs with richer data (SearchAPI.io, AdLibrary.com)
- `references/page_id_lookup.md` — How to find competitor Facebook Page IDs
- `references/field_reference.md` — Complete API field reference

Related Skills

zhua-metacognition

3891
from openclaw/skills

爪爪元认知系统 —— 思考自己的思考、监控认知过程、优化决策质量。Use when 爪爪需要反思自己的思维过程、优化认知策略、或提升决策质量。

metacognitive-self-mod

3891
from openclaw/skills

Analyze and improve the improvement process. Use for detecting quality regressions and refining meta-optimization

Skill Builder — Meta-Skill for Creating Skills

3891
from openclaw/skills

## Metadata

meta-mcp-wizard

3891
from openclaw/skills

Use the MCPHero Meta-MCP server inside AI clients (Claude Desktop, Cursor, etc.) to create, deploy, and manage MCP servers through the wizard pipeline. Use this skill when the user wants to connect the Meta-MCP server, build MCP servers interactively via MCP tools, or asks about the meta-mcp endpoint at api.mcphero.app.

MetaMask Agent Wallet

3891
from openclaw/skills

Control a sandboxed MetaMask browser extension wallet for autonomous blockchain transactions. Features configurable permission guardrails including spend limits, chain allowlists, protocol restrictions, and approval thresholds. MetaMask-only (other wallets not supported).

meta-research

3891
from openclaw/skills

Autonomous research workflow agent for AI and scientific research. Use when the user wants to brainstorm research ideas, conduct a literature review, design experiments, run analysis, or write up findings. Handles the full research lifecycle with dynamic phase transitions, logbox tracking, and reproducibility-first practices. Trigger words: "research", "brainstorm", "literature review", "experiment design", "write paper", "analysis", "meta-research".

metagenomic-krona-chart

3891
from openclaw/skills

Analyze data with `metagenomic-krona-chart` using a reproducible workflow, explicit validation, and structured outputs for review-ready interpretation.

meta-analysis-forest-plotter

3891
from openclaw/skills

Use when creating forest plots for meta-analyses, visualizing effect sizes across studies, or generating publication-ready meta-analysis figures. Produces high-quality forest plots with confidence intervals, heterogeneity metrics, and subgroup analyses.

meta-ads-manager

3891
from openclaw/skills

Manage and analyze Meta (Facebook/Instagram) Ads campaigns. Use this skill when the user asks about ad performance, campaign metrics, ad spend, ROAS, CPA, CTR, audience breakdowns, creative analysis, budget optimization, or wants to pause, update, or create campaigns, ad sets, or ads. Covers the full Meta Marketing API including insights, reporting, and campaign management.

meta-business

3891
from openclaw/skills

Skill for the Meta Business CLI. Complete WhatsApp, Instagram, Facebook Pages, and Messenger automation via the Graph API. Supports messaging, media, templates, analytics, webhooks, and systemd service management.

Meta Ads Collector Skill

3891
from openclaw/skills

## Purpose

meta-tags-optimizer

3891
from openclaw/skills

SEO meta tags optimizer: craft title tags, meta descriptions, Open Graph, and Twitter cards for maximum CTR with A/B test variations. Part of a 20-skill SEO & GEO suite. 标题优化/元描述/CTR提升/TDK优化/搜索优化