---
name: youtube-competitor-watch
description: "Set up a recurring YouTube competitor watch with Vivu: research the company, recommend and price the channel list, download, upload, index, search, and post findings to Slack on a schedule."
---YouTube competitor watch with Vivu
This skill turns a company name into a standing competitive watch over YouTube. Claude reads the company's website, finds the channels that matter (competitors' official channels plus third party reviewers), recommends how many to watch and prices that against the user's Vivu plan, downloads recent videos, uploads and indexes them in Vivu, runs a fixed set of searches, and posts confirmed findings to a Slack channel on a schedule.
The value is in what competitors show on screen but never put in a title or description: a pricing page inside a webinar, a new feature in a product update video, a reviewer's verdict at minute 12. Text monitoring tools miss these. Every step below is built to surface that kind of moment and to keep false positives out of Slack.
When to use
Use when someone asks to monitor competitors on YouTube, set up a video based competitive intel feed, track what competitors show in their videos, or build a recurring competitor digest from video. For a one off question about a single video, use Vivu search directly instead.
Working principles
- Report measured numbers, not estimates. When a number is an estimate, say so.
- Nothing is "verified" until it has been checked against the source. Candidates stay candidates until indexed and triaged.
- Stop and tell the user when a required capability is missing. Do not guess around it.
- Ask the user before anything that is expensive to redo (the channel list, before downloading) or that acts on their behalf (Slack posts, scheduled tasks).
What you need before starting
Check each item at the start of the run and tell the user plainly what is missing.
| Requirement | Why | How to check |
|---|---|---|
| Vivu MCP connected with write access | create projects, upload, index, search | vivu_get_account shows can_create_projects: true (tool names may carry a server prefix). A write call failing with "has not granted vivu.write" means the connection was authorized read only; ask the user to reconnect Vivu |
| A shell on a machine with a residential IP, plus a folder it can write | YouTube blocks downloads from cloud and datacenter IPs with "Sign in to confirm you're not a bot" | a one video test download in Step 3 |
| yt-dlp, ffmpeg, and a JavaScript runtime (node or deno) on that machine | current yt-dlp needs a JS runtime to solve YouTube signatures | yt-dlp --version, node --version |
| An upload path | moving the files into Vivu | a Vivu direct upload tool if the MCP lists one; otherwise vivu_open_upload_page plus a browser that can attach local files |
| Web fetch and web search | Step 1 and channel verification | try one fetch |
| Slack connector with access to the target channel | Step 6 | read the channel once |
| A scheduled task tool that outlives the session | Step 5 | in Claude Cowork this is the scheduled task tool (create_trigger). Never use in session cron tools; they die with the session |
Inputs to collect
Ask for anything missing, most important first.
- The company whose competitors we watch: name or website.
- The Slack channel for the digest.
- Cadence. Default: daily at 7:00 in the user's timezone.
- Lookback window for the first backfill. Default: 30 days.
- Watch size. Claude recommends it in Step 2 from the product profile and the channels actually found, then prices it against the user's Vivu plan. The user adjusts from there.
Files and state
Keep everything in one working folder on the download machine:
competitor-watch/
config.json company, product profile, recommended and chosen channels, queries, slack channel, cadence, vivu project id
channels.csv verified channel table (Step 2)
state.json per channel last_checked, uploaded and searched video IDs, reported moment keys
archive.txt yt-dlp download archive
downloads.tsv one row per downloaded video
upload/ flat folder, only .mp4 files
extras/ everything that is not an upload candidate
upload/ must contain only videos and no subfolders, so a bulk upload never picks up a stray file.
Step 1: Understand the company
Goal: one paragraph on what the company sells, to whom, and the category name buyers use, plus the product profile Step 2 needs.
- Fetch the homepage, the product or features pages, the pricing page, and any "vs" or "alternatives" pages the company publishes. The "vs" pages are the fastest route to the competitor set the company itself acknowledges.
- Write down the product category in buyer language, the primary buyer role, the features the company leads with, and the pricing model.
- Record the product profile: buying motion (self serve, sales led, or mixed), visual surface (UI heavy, or API, infrastructure, hardware), market structure (a handful of real competitors, or dozens), and release pace.
- Draft the competitor candidate list. Tier 1 is direct competitors (same category, same buyer). Tier 2 is adjacent products with overlapping use cases.
- Draft the standing queries for Step 4 from the features and the pricing model.
Done when the user confirms the category description, the product profile, and the tier 1 list.
Step 2: Find the channels and size the watch
Official channels and third party channels are found in different ways. Size the watch only after both are measured.
Official competitor channels
- Search YouTube channels by brand name. yt-dlp does this without an API key; the sp value filters results to channels:
yt-dlp --flat-playlist --playlist-end 4 -J "https://www.youtube.com/results?search_query=BRAND&sp=EgIQAg%3D%3D"
Take the candidate whose title is the brand and whose follower count is largest, then verify it.
Verify ownership, strongest evidence first:
- The company's own website links to the channel. Fetch the homepage and look for youtube.com links. Old style links (/user/Name, /c/Name) still resolve: run yt-dlp on them to get the channel_id and compare.
- The channel description names the company's domain or product.
- Neither holds: mark the channel UNCERTAIN and tell the user.
Same name channels are common. In one real run the top "Basecamp" channel result was a gaming group, not the software company. Brand channels also get renamed, so compare channel_id, not title.
Measure activity in the lookback window. List both tabs; webinars often live under /streams, and listing only these two tabs leaves Shorts out:
yt-dlp --flat-playlist --playlist-end 60 --extractor-args youtubetab:approximate_date -J "https://www.youtube.com/@HANDLE/videos"
yt-dlp --flat-playlist --playlist-end 20 --extractor-args youtubetab:approximate_date -J "https://www.youtube.com/@HANDLE/streams"
approximate_date is coarse ("3 weeks ago"). For exact dates read the channel's RSS feed, which has exact publish times for the latest 15 uploads:
https://www.youtube.com/feeds/videos.xml?channel_id=UC...
Third party reviewer channels
Reviewer content about a category is spread across hundreds of small channels, so find reviewers from this month's comparison content rather than from a list of famous names.
- Search videos uploaded this month (the sp value filters to this month's videos) for queries like "BRAND vs", "BRAND review", "BRAND alternatives", and "best CATEGORY software":
yt-dlp --flat-playlist --playlist-end 120 -J "https://www.youtube.com/results?search_query=QUERY&sp=EgQIBBAB"
- Keep a video only if its title names at least one monitored brand. Generic brand words produce noise ("Monday" matches movie reviews, "Asana" matches yoga), so read the titles before trusting a match.
- Group by channel and rank by subscriber count. Flag content farms: any channel with more than about 100 uploads in 30 days. In one run, three reviewer channels had each uploaded over 400 videos in two weeks, nearly all unrelated to the category. Do not call a channel AI generated unless you checked; upload volume is the fact you can state.
- From third party channels, download only videos whose title names a monitored brand. Downloading everything a farm uploaded multiplies volume by ten or more and adds almost nothing relevant.
Size the watch: recommend first, then price it
Checking a channel for new uploads costs almost nothing. Indexing its uploads uses Vivu index minutes, and every run of the standing queries uses search credits. Work out the coverage the product needs first, without looking at the plan. Then show what that coverage costs against the user's plan and let the user adjust. A recommendation trimmed in advance to fit a plan hides what the watch should cover.
1. Recommend coverage from signal
Build the list in this order:
- Tier 1: every direct competitor with a verified official channel. Usually 3 to 5, up to 8 in a fragmented market. Always included, even when quiet, because checking a quiet channel is free.
- Third party: reviewer channels with at least one brand naming comparison or review in the last 90 days, excluding farms, ranked by subscribers. The count follows the buying motion. Self serve buyers watch "X vs Y" videos before a trial, so aim for 8 to 12. Mixed motion, 4 to 8. Sales led enterprise buyers lean on vendor webinars and events, so 0 to 4.
- Tier 2: adjacent official channels with at least one upload in the window, most relevant first. Stop at about 15, or earlier once the next channel is only loosely related to the buyer's decision.
Adjust for the product profile. A UI heavy product gets full value from official channels, because competitors' videos show screens Vivu can find. For an API, infrastructure, or hardware product, recommend tier 1 plus conference or event channels where competitors present, and warn that YouTube may carry thin signal.
If discovery found fewer channels than a rule calls for, recommend the real count. Never pad the list with inactive or unverified channels.
2. Price the recommendation
- Index minutes per month: for each recommended channel, measured window minutes × 30 ÷ window days, summed. For third party channels count only title matched videos.
- Search credits per month: standing queries per run × runs per month, plus precise confirmation searches. Estimate one confirmation per reported hit and label that part as an estimate.
- The first backfill: total minutes in upload/, indexed once.
- Read the plan with vivu_get_usage: monthly index minutes, monthly search credits, and what remains this month.
Show one table:
| Recommended watch | Plan allowance | Ratio | |
|---|---|---|---|
| Index minutes per month | measured | from vivu_get_usage | computed |
| Search credits per month | computed | from vivu_get_usage | computed |
| One time backfill minutes | measured | remaining this month | computed |
3. Let the user adjust to the plan
If the recommendation fits, say so and move on. If it does not, list these levers in order, each with its measured saving, and let the user choose:
- Drop tier 2 channels, least relevant first.
- Keep fewer reviewer channels, smallest subscriber count first.
- Lower the cadence, for example daily to weekly. This cuts search credits, not index minutes; the same videos still get indexed.
- Drop a tier 1 channel only as a last resort, and name the competitor being dropped.
- Move to a larger Vivu plan.
Do not suggest skipping long videos to save minutes. Webinars are where pricing pages and unannounced features tend to appear.
Record both the recommendation and the user's final choice in config.json, so the periodic re sizing in Step 5 can show the gap again.
Worked example from one real run: a work management SaaS (mixed buying motion, UI heavy, fragmented market). 34 official candidates were checked and 22 were active in 30 days. The watch that was run covered 5 tier 1, 15 tier 2, and 10 reviewer channels: 1,185 index minutes a month (tier 1 303, tier 2 439, reviewers 443 counting title matched videos only). Against a plan with 180 index minutes a month that is 6.6 times the allowance. Dropping tier 2 brings it to 4.1 times, and tier 1 alone is still 1.7 times. Seven daily standing queries use about 210 search credits a month, well inside a 500 credit plan, so index minutes were the only constraint. Official channels in a category often upload much less than people expect; the large volume sits with third party channels.
Confirm before downloading
Show the user the recommendation, its price against the plan, the adjusted choice, one sample row of the channel table, and the field mapping. Wait for a yes. Mismatched fields are cheap to fix now and expensive after downloading.
Sample row (placeholder values):
channel_handle,channel_url,owner_type,tier,last_30d_uploads,last_30d_minutes,median_duration_min,verification,followers,verified_on
@examplebrand,https://www.youtube.com/@examplebrand,official,tier1,12,180,8,site_link_match,98000,2026-01-15
| Field | Source | If unavailable |
|---|---|---|
| channel_handle, channel_url | channel search result | drop the channel |
| owner_type | website link or channel description | UNCERTAIN |
| tier | judgment from Step 1 (inferred) | none |
| last_30d_uploads, last_30d_minutes, median_duration_min | /videos and /streams listing plus RSS dates | NOT VERIFIED |
| verification | which ownership check passed | UNCERTAIN |
| followers | channel metadata | blank |
Say which fields are inferred (tier) and what changes if the inference is wrong.
Done when the user approves the chosen watch and the channel table. Save it as channels.csv.
Step 3: Download
Run on the residential IP machine, never in a cloud sandbox. Test one video before the batch.
Per video:
yt-dlp --js-runtimes node \
-f "bv*[height<=720]+ba/b[height<=720]" --merge-output-format mp4 \
--match-filter "upload_date>=YYYYMMDD" \
--download-archive competitor-watch/archive.txt \
-P "temp:SCRATCH_DIR_OUTSIDE_UPLOAD" -P "home:competitor-watch/upload" \
-o "BRAND_%(upload_date)s_%(id)s_%(title).80B.%(ext)s" \
--sleep-interval 1 --max-sleep-interval 3 --retries 5 \
"https://www.youtube.com/watch?v=VIDEO_ID"
Notes:
- 720p keeps on screen text (prices, UI labels) readable at a fraction of the 1080p size. Re-download a single video at higher resolution only if you need a frame for publishing.
- The BRAND prefix is the only place a flat folder records which company a video belongs to, and it shows up in Vivu results. Keep it.
- --match-filter enforces the exact window at download time, which corrects the coarse listing dates.
- Keep temp files outside the upload folder. Some shells can write but not delete in user folders (Claude Cowork connected folders are one), and merge leftovers would otherwise sit in upload/.
- Some shells kill background processes when a command returns and cap each call's runtime (Claude Cowork's device shell caps a call at 180 seconds). There, run downloads in batches that fit one call, three in parallel, and keep a list of attempted IDs so the next call resumes.
- HTTP 403 errors are usually transient. Retry failures once, one at a time, with a longer sleep. If yt-dlp reports "Requested format is not available", record the video as failed and move on.
After downloading, verify three things: the file count equals archive entries minus failures, every upload_date falls inside the window, and two or three files spot checked with ffprobe have the expected resolution and duration. Write downloads.tsv with brand, kind (official or third party), video ID, upload date, duration, title, and file name.
Done when upload/ holds only the verified mp4 files and downloads.tsv lists each one.
Step 4: Upload, index, and set up the standing queries
Project
- Call vivu_list_projects. Reuse a project for this watch if one exists.
- Otherwise call vivu_create_project with a name like "Competitive Watch CATEGORY". Record the project ID in config.json.
Upload
Claude uploads the files. No manual step is expected.
- If the Vivu MCP lists a direct upload tool (one that takes a file path, or returns a signed upload URL the local shell can send the file to), use it for every file in upload/ from the machine that holds them.
- Otherwise call vivu_open_upload_page with the project ID. It returns upload_url, a one time sign in link to the project's upload page. The link expires in 180 seconds, so request it immediately before opening it, and request a new one for each browser session.
- Open upload_url in a browser that can attach local files. The page has a video file input that accepts multiple files. Attach the files from upload/ in batches and wait until the page shows each one uploading.
- Respect the browser tool's size limit (Claude in Chrome accepts at most 10 MB per upload call). Send files under the limit in batches. Never split or recompress videos to fit. If a file cannot go through any available path, tell the user which ones and ask them to add those in the Vivu web app.
- Vivu replaces spaces and punctuation in file names with underscores. Match results back to downloads.tsv by the 11 character YouTube ID inside the name, not by the full name.
Poll vivu_list_videos until every video shows status ready, and compare the count with downloads.tsv. Record uploaded video IDs in state.json.
Standing queries
Each query maps to one field of a competitive battlecard, so results sort themselves. Start from this library and adapt the wording to the category found in Step 1:
| Field | Query | Mode |
|---|---|---|
| Pricing | a pricing or plans page shown on screen with plan names and prices | fast, then precise |
| New feature | presenter announces something new, says just launched, now available, in beta, or shows a feature labeled new | fast |
| AI | an AI assistant or AI feature demonstrated inside the product interface | fast |
| Integrations | integration with the tools this category's buyers care about, shown inside the product (name them) | fast |
| Third party verdict (third party videos only) | reviewer names two or more tools and recommends one over the other | fast |
| Confirm pricing frame | the exact frame where plan names and prices are fully visible | precise |
| Confirm feature frame | the first moment the new feature's interface appears on screen | precise |
Run each query with vivu_search_videos (project_id, query, mode, maximum_results). It returns a job ID. Call vivu_get_search_results until complete is true; each status call can wait up to 45 seconds, so a pending search is not a stalled one. Keep the Vivu result page link from every completed search.
Triage every hit
A hit is reportable only if all three checks pass:
- It is real. Read the timestamped summary around the hit (vivu_get_video_summary with include_segments, start_ms and end_ms bracketing the moment) and confirm it matches the query.
- It is not already in the title or description. A pricing page in a video titled "Our new pricing" is not news. Mark each reported hit with this check; it is where video indexing earns its place.
- It has not been reported before (check the reported moment keys in state.json).
Hits failing check 1 are false positives. Count them. Run the full query set on the backfill before scheduling anything, show the user the triaged results with the false positive count, and tighten query wording until the first page of results is clean.
Done when every video is indexed, each standing query has run once, and the user has seen the triaged results.
Step 5: Schedule the recurring run
Use the host's scheduled task tool so the job outlives this conversation. In Claude Cowork, create a scheduled task that requires the user's computer: downloads need the residential IP and the local folder. Convert the user's local time to UTC for the cron expression.
Each run starts fresh, so the task prompt must be standalone. Include the working folder path, the config.json location, the Vivu project ID, the Slack channel, and these run steps:
- For each channel in channels.csv, read the RSS feed and collect videos published since that channel's last_checked. For third party channels keep only titles naming a monitored brand, and rerun the Step 2 third party searches for the week's uploads.
- Call vivu_get_usage. If the remaining allowance will not cover the new minutes, keep tier 1 first, then third party, then tier 2, and note in the digest what was skipped.
- Download new videos per Step 3 into upload/. A watch of about twenty official channels usually adds a handful of videos a day, which fits in one or two shell calls.
- Upload per Step 4 and wait until the new videos are ready. Any file that could not be uploaded goes into the digest under "Waiting for upload" with the Vivu project page link. Never post upload_url; it expires in three minutes.
- Run the standing queries. Vivu search covers the whole project, so keep only hits on videos not searched in an earlier run (searched video IDs live in state.json).
- Triage per Step 4 and post per Step 6.
- Update state.json. Advance last_checked only for channels that succeeded, so a failed channel is backfilled on the next run. Record uploaded and searched video IDs and reported moment keys.
Every few weeks, rerun the Step 2 recommendation and pricing with fresh numbers. Tell the user if the recommended coverage has changed (a competitor started a webinar series, a reviewer went quiet) and how far the chosen watch sits from it.
Done when the scheduled task exists, the user has approved its prompt and cadence, and one run has completed end to end.
Step 6: Post the digest to Slack
Posting to Slack acts as the user. Get explicit approval of the channel and of one sample digest before the first post. After that the scheduled task posts on its own.
One message per run:
Competitor watch, DATE: N new videos from M channels, K findings
Tier 1
• BRAND: showed WHAT (not in title). MM:SS VIVU_RESULT_LINK | YOUTUBE_LINK&t=SECONDS
Tier 2
• ...
Third party
• CHANNEL (SUBSCRIBERS): "SHORT EXACT QUOTE" MM:SS LINK
Waiting for upload, skipped, or failed: LIST, if any
Rules:
- Post only triaged hits. Zero false positives matters more than completeness; a digest that cries wolf twice gets muted.
- One line per finding, brand first.
- Quote reviewers exactly and briefly. Never paraphrase a verdict into something stronger than what was said.
- With no findings, post one line saying so, or stay silent if the user prefers.
Compliance
- Downloading YouTube videos may conflict with YouTube's Terms of Service. Keep downloads for internal analysis, do not redistribute them, and have the user confirm this is acceptable for their organization before Step 3.
- Using competitors' logos or footage in anything public (posts, ads, demo videos) needs its own review.
Known failure modes
| Symptom | Cause | Fix |
|---|---|---|
| "Sign in to confirm you're not a bot" | download attempted from a cloud IP | run Step 3 on the user's machine |
| warning about a missing JS runtime, then failed downloads | no node or deno available to yt-dlp | install node and pass --js-runtimes node |
| the "official" channel is the wrong one | same name channel | verify by website link or description; compare channel_id |
| counts look off near the window edge | approximate_date is coarse | exact dates from RSS; --match-filter at download |
| a channel listing stops exactly at the limit | high volume channel hit --playlist-end | raise the limit, or report counts as lower bounds |
| stray .webm or .f136.mp4 files in upload/ | merge leftovers in a folder that forbids deletes | set -P temp: to a scratch folder outside upload/ |
| downloads stop between shell calls | the shell kills background jobs | batch inside one call and resume from the archive |
| "has not granted vivu.write" | Vivu connected read only | user reconnects Vivu with write access |
| upload page asks to sign in or shows an error | the one time upload link expired (180 seconds) | request a new link right before opening it |
| a file is rejected by the browser upload tool | file above the tool's size limit | use a direct upload tool if listed; otherwise the user adds that file in the Vivu web app |
| a result's file name does not match downloads.tsv | Vivu normalized the file name | match on the YouTube ID inside the name |
| the allowance runs out mid month | chosen watch larger than the plan | apply the Step 2 levers in order, or move to a larger plan |
| Slack digest full of noise | hits posted without triage | apply the three triage checks; tighten query wording |