---
name: spy
description: Download videos from Facebook Ad Library OR save text/EDITORIAL competitor ads to swipe file. Use when user wants to analyze competitor creatives. Examples - "/spy 926310263101412", "/spy https://www.facebook.com/ads/library/?id=926310263101412", or paste text ad directly after "/spy".
user_invocable: true
---

# /spy — Facebook Ad Library Spy Pipeline

Download competitor ad videos, preview them, transcribe selected ones, and translate to Russian.
Also supports pasting text/EDITORIAL ads directly — translates and saves to swipe file.

## Arguments

The first argument is a Facebook Ad Library URL, ad ID, or pasted text ad content.

## Input Detection

Determine input type:
- **URL** (contains "facebook.com" or is a numeric ad ID) → **Video Flow** (Steps 1-9 below)
- **Text** (multi-line text, no URL pattern) → **Text Flow** (see Text Flow section below)

## Video Flow

### Step 1: Download videos

Run the following using the Bash tool:

```bash
curl -s -X POST http://localhost:10000/api/spy/download \
  -H "Content-Type: application/json" \
  -d "{\"url\": \"$ARGS\"}"
```

Parse the JSON response. Extract `session_id` and `videos` array.

If the API returns an error, tell the user the API might not be running and suggest `pnpm dev:api`.

### Step 2: Show video list

Display a numbered table:

| # | Filename | Size | Duration |
|---|----------|------|----------|
| 1 | 1_926310263101412_1.mp4 | 1.2 MB | 12s |
| 2 | 2_926310263101412_2.mp4 | 5.3 MB | 1:32 |
...

Format duration: if > 60 seconds show as M:SS, otherwise as Ns.

### Step 3: Point to UI for preview

Tell the user:

> Видео скачаны. Открой в браузере для просмотра:
> **http://localhost:5173/spy?session={session_id}**
>
> Посмотри видео и скажи какие транскрибировать (номера через запятую, например: "2, 5").
> Или напиши "все" чтобы транскрибировать все.

Then **STOP and wait for the user's response**. Do NOT proceed until the user tells you which videos to transcribe.

### Step 4: Parse user selection

The user will respond with video numbers and optionally geo + vertical. Parse them:

**Video numbers:**
- "2, 5" → videos at index 2 and 5
- "все" or "all" → all videos
- "все кроме 3, 7" or "all except 3, 7" → all except those indices

**Geo + Vertical (optional, for auto-save):**
The user might include geo and vertical in the same message:
- "3, простатит, романия" → video #3, vertical: chronic_condition, geo: LOCALE_RO_- "3, 8, category_b, US" → videos #3 and #8, vertical: category_b, geo: US
- "all, category_x, Country_A" → all videos, vertical: category_x, geo: XX

Convert country names to ISO codes: e.g., US/USA → US, Germany → DE, France → FR, Spain → ES, etc.
Convert vertical names to English slugs: простатит → chronic_condition, похудение → category_b, сумки → bags, etc.

If geo and vertical are detected, remember them for Step 8 (auto-save).
Map the numbers to filenames from the video list.

### Step 5: Transcribe selected

Run the following using the Bash tool:

```bash
curl -s -X POST http://localhost:10000/api/spy/{session_id}/transcribe \
  -H "Content-Type: application/json" \
  -d "{\"filenames\": [\"filename1.mp4\", \"filename2.mp4\"]}"
```

Parse JSON response — `transcripts` is a dict of filename → text.

### Step 6: Clean up junk

Delete unselected videos:

```bash
curl -s -X POST http://localhost:10000/api/spy/{session_id}/delete-unselected \
  -H "Content-Type: application/json" \
  -d "{\"keep_filenames\": [\"selected1.mp4\", \"selected2.mp4\"]}"
```

### Step 7: Show transcript and translate

For each transcribed video, show the original transcript text.

Then translate EACH transcript to Russian. Use your own language capabilities to translate — do NOT call any external API. Show the translation clearly labeled.

Format:

```
## Video: {filename}

### Original ({detected_language})
{transcript text}

### Перевод (русский)
{translated text}
```

### Step 8: Save to swipe file

**If geo and vertical were already provided in Step 4:**
Skip asking — go directly to saving. Build the transcripts_ru dict from translations in Step 7 and run:

```bash
curl -s -X POST http://localhost:10000/api/spy/{session_id}/save \
  -H "Content-Type: application/json" \
  -d '{"filenames": ["file1.mp4"], "geo": "RO", "vertical": "chronic_condition", "transcripts_ru": {"file1.mp4": "перевод..."}}'
```

Tell the user: "Сохранено N крео в swipe file (geo: XX, vertical: YYY). Смотри: http://localhost:5173/swipe-file"

**If geo and vertical were NOT provided in Step 4:**
Ask the user:

> Хочешь сохранить в swipe file? Скажи номера видео и укажи geo и vertical.
> Например: "сохрани 3, 8, geo: US, vertical: category_b"
> Или "не сохранять" чтобы пропустить.

Then **STOP and wait**. Parse response and save, or skip if user says "не сохранять".

### Note on decomposition

Decomposition runs **automatically** after save. Intelligence (beliefs, learnings, winning formula) is extracted to the database without manual intervention. No need to run `/rewrite` for basic intelligence extraction — it happens at ingest.

### Step 9: Cleanup session

After showing all transcripts, clean up:

```bash
curl -s -X DELETE http://localhost:10000/api/spy/{session_id}
```

---

## Text Flow (for EDITORIAL / native ad text)

When user pastes text instead of URL:

### Text Step 1: Detect language and show original

Show the pasted text with detected language label.

### Text Step 2: Translate to Russian

Translate the text using your own capabilities (no API).
Show original + translation side by side.

Format:

```
### Original ({detected_language})
{original text}

### Перевод (русский)
{translated text}
```

### Text Step 3: Ask geo + vertical

If not provided in the command, ask:

> Укажи geo и vertical для сохранения.
> Например: "RO, chronic_condition"

Then **STOP and wait for the user's response**.

If geo and vertical were already included with the text (e.g. "/spy LOCALE_RO_chronic_condition [text]"), skip asking.

### Text Step 4: Save via API

Use the Bash tool to save via the API endpoint (triggers auto-decomposition):

```bash
curl -s -X POST http://localhost:10000/api/spy/save-text \
  -H "Content-Type: application/json" \
  -d '{
    "body_text": "{original_text}",
    "headline": "{first_line_as_headline}",
    "transcript_ru": "{translated_text}",
    "geo": "{geo}",
    "vertical": "{vertical}"
  }'
```

If API is not running, fall back to `mcp__supabase__execute_sql`:

```sql
INSERT INTO spy_creatives (body_text, headline, transcript_ru, source_type, geo, vertical, org_id)
VALUES (
  '{original_text}',
  '{first_line_as_headline}',
  '{translated_text}',
  'text',
  '{geo}',
  '{vertical}',
  (SELECT id FROM organizations LIMIT 1)
) RETURNING id;
```

**Important:** If using SQL fallback, auto-decomposition will NOT run. Tell the user: "API не запущен — decomposition не запустится автоматически. Запусти `/spy-backfill` позже для анализа."

Use the first line of the original text as the `headline`. Escape single quotes in text values.

### Text Step 5: Show result

> Текстовый креатив сохранён в swipe file. Decomposition запустится автоматически (если сохранено через API).
> Смотри: http://localhost:5173/swipe-file
>
> Следующие шаги:
> - `/rewrite` — анализ + генерация на основе этого текста
> - `/classify` — привязать к архетипу + BMI
