SkillsSkill for Claude

Competitor webinar battlecard

This skill turns one competitor's YouTube library (webinar replays, product updates, demos and how-to videos) into a battlecard page in Notion and one Linear issue per feature gap. Claude lists the channel, pulls free captions, prices the library against your Vivu plan, indexes the subset you approve, runs three standing searches, checks every hit against frames or captions, and compares the competitor's settings screens with your own feature list. The value is in what the competitor shows but never says: the settings form behind a feature that got one sentence in the voiceover, and what a presenter says is coming soon. Prices are kept only as candidates until Claude has seen them in the frames, because Vivu can describe a marketing homepage as a pricing table.

Maintained by Vivu. Updated 2026-09-25.

Download

competitor-webinar-battlecard.zip

10 KB. Unzips to competitor-webinar-battlecard/SKILL.md. Upload the zip as it is in the Claude app, or unzip it into your skills folder for Claude Code.

SHA-256 58278ae59002f29eb4e90097864e4ee2d0d814b181e285756ee8e82cc1d8ea6c

At a glance

What the Competitor webinar battlecard skill does, where it runs, what it needs, and when it asks
Looks forSettings and configuration screens that show what a competitor has shipped, spoken roadmap claims, and pricing pages that happen to be on screen. Captions and titles miss the settings screens, and a price on screen is often never read aloud. Every hit is checked against the original video before it is reported: frames for screens, a two frames per second contact sheet for prices, captions for roadmap quotes.
Runs onClaude Code on your computer (the terminal or the Code tab of Claude Desktop), with a shell, local files and a residential IP for YouTube downloads. It uses the Vivu, Notion and Linear connectors. The optional quarterly refresh is a local routine in Claude Code Desktop.
Needs
  • The Vivu connector with write access, to create a private project, open its upload page and search.
  • A shell on your computer with a residential IP and a working folder, because YouTube blocks cloud downloads and frames are cut from local files.
  • yt-dlp, ffmpeg with ffprobe, and node, to list and download videos, measure minutes and extract frames.
  • An upload path: the Vivu upload page opened in your own browser, or a browser tool that can attach local files.
  • Your own feature list, because a gap is only a gap against something.
  • The Notion connector with access to the parent page for the battlecard.
  • The Linear connector with access to the team that gets the gap issues.
Your Vivu planIndexing uses your monthly index minutes and each precise search uses search credits. Before anything is downloaded, Claude shows the selected minutes and the search credits next to what vivu_get_usage reports and waits for your approval. One run of the standing queries is about 15 search credits (estimate, before any rewording). In our test run 8 videos (29.59 minutes) were indexed.
Asks you firstDownloading from YouTube (terms of service), the indexed subset and its cost, the battlecard outline with a sample gap row and field mapping, writing the Notion page and the Linear issues, and creating the quarterly routine with its prompt and cadence.

The skill does its video work through the Vivu connector. If the connector is not in your Claude yet, add it first; the skill checks that it is connected before it does anything else.

Before you run it

  • Downloading YouTube videos may conflict with YouTube's Terms of Service; keep them for internal analysis and do not redistribute them or use competitor footage publicly.
  • Claude writes a Notion page and Linear issues as you, only after you approve a rendered sample.
  • Claude in Chrome uploads at most 10 MB per call; larger files go through your own browser or the Vivu web app, never split or recompressed.
  • The videos stay in your Vivu project until you delete them; the project is created private because the default is visible to your whole organization.
  • Pricing pages are weak: Vivu described a marketing homepage as a pricing table in our test run, so prices enter the battlecard only after a frame check.
  • Feature gaps are inferred from your feature list and stay UNCERTAIN until someone who owns the product confirms them.
  • Recall of settings screens is unknown and capped by maximum_results; an empty result does not prove the footage has none.

Start it

Once the skill is installed, ask for the task in your own words. Naming the skill is the most reliable way to have Claude use it. For example:

Build a battlecard for our competitor from their YouTube channel: roadmap claims, the settings they show in demos, and any pricing pages. Here is our feature list; file the gaps in Linear.

In Claude Code you can also type /competitor-webinar-battlecard. Claude asks for anything the request leaves out, most important first.

What is inside

  1. When to use
  2. Working principles
  3. What you need before starting
  4. Inputs to collect
  5. Files and state
  6. Step 1: Check the Vivu connector and the setup
  7. Step 2: List the library and pull captions
  8. Step 3: Price the library and pick the subset
  9. Step 4: Download, upload and index
  10. Step 5: Run the standing queries
  11. Step 6: Triage every hit
  12. Step 7: Build the battlecard and the gap list
  13. Step 8: Write the battlecard to Notion and the gaps to Linear
  14. Step 9: Refresh each quarter
  15. Compliance
  16. Known failure modes

The full skill

This is competitor-webinar-battlecard/SKILL.md from the download, as Claude reads it: the frontmatter first, then the instructions.

---
name: competitor-webinar-battlecard
description: "Turn one competitor's YouTube demos and webinars into a Notion battlecard and Linear feature gap issues with Vivu. Use when building or refreshing a battlecard from competitor videos."
---

Competitor webinar battlecard with Vivu

This skill turns one competitor's YouTube library (webinar replays, product update videos, demos and how-to videos) into a battlecard page in Notion and one Linear issue per feature gap. Claude lists the channel, pulls the free auto captions, prices the library against the user's Vivu plan and picks the subset worth indexing, downloads and indexes that subset, runs three standing searches, checks every hit against frames or captions, compares the competitor's settings screens with the user's own feature list, and writes the battlecard and the gap issues after the user approves a sample.

The value is in what the competitor shows but never says: the settings form behind a feature that got one sentence in the voiceover (every switch, dropdown and limit the feature has), and the moment a presenter says something is coming soon while the screen shows something else. Captions and titles miss the settings entirely. Pricing pages are collected too, but only as candidates: Vivu sometimes describes a marketing homepage with a Pricing link as a table of plans and prices, so a price enters the battlecard only after Claude has looked at the frames and seen the numbers. Every saved link points to the YouTube video at the right second (&t=SECONDS), so a sales rep can check any battlecard line in one click months later.

When to use

Use when someone asks to build or refresh a battlecard from a competitor's webinars or YouTube channel, list the features and settings a competitor shows in its demos, collect what a competitor says is on its roadmap, or turn competitor videos into feature gap tickets. For a recurring feed of new competitor videos posted to Slack, use the youtube-competitor-watch skill (https://vivu.ai/skills/youtube-competitor-watch) instead. For a one off question about a single video, search Vivu directly. For a pricing comparison alone, read the competitor's pricing page on the web; this skill finds prices in video only when they happen to be on screen.

Working principles

  1. Report measured numbers, not estimates. When a number is an estimate, say so.
  2. Nothing is "verified" until it has been checked against the source video. Candidates stay candidates until triaged, and the battlecard marks anything unchecked as UNCERTAIN.
  3. Stop and tell the user when a required capability or tool is missing. Do not guess around it.
  4. Ask the user before anything that is expensive to redo (the indexed subset, before downloading) or that acts on their behalf (the Notion page, Linear issues, a scheduled refresh).
  5. Everything saved links to the user's own source: the YouTube URL plus &t=SECONDS. A Vivu result page link expires after four hours, so it appears only in the live reply.
  6. A roadmap claim is what the competitor said, not a fact. Label it as a claim and quote the captions, never the search reason.

What you need before starting

Check each item at the start of the run and tell the user plainly what is missing before doing anything else.

Requirement Why How to check
Vivu connector with write access create a project, open its upload page, search, read summaries 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 user reconnects Vivu and allows write access
A shell on the user's computer with a residential IP, plus a working folder YouTube blocks downloads from cloud and datacenter IPs; frames are cut from local files the caption download in Step 2
yt-dlp, ffmpeg with ffprobe, and node list and download videos, measure minutes, extract frames; yt-dlp needs a JavaScript runtime for YouTube yt-dlp --version, ffmpeg -version, ffprobe -version, node --version
An upload path move the selected files into Vivu vivu_open_upload_page plus the user's own browser, or a browser tool that can attach local files
The user's own feature list a gap is only a gap against something ask for a doc, a features page URL or a pasted list; without it, stop after the battlecard and skip gaps
Notion connector with access to the parent page Step 8 writes the battlecard page search for the parent page once
Linear connector with access to the team Step 8 creates one issue per gap list the team's labels once

This skill needs Claude Code on the user's computer (the terminal or the Code tab of Claude Desktop). Claude on the web and cloud sessions cannot reach the local files or the residential IP.

Inputs to collect

Ask for anything missing, most important first.

  1. The competitor (COMPETITOR): name and YouTube channel URL. Required.
  2. Our feature list (OUR_FEATURES). Required for the gap list; without it the run ends at the battlecard.
  3. Lookback window. Default: the last 12 months of uploads.
  4. Index minute budget for this run. Default: what vivu_get_usage says remains this month, minus a margin the user picks.
  5. Notion parent page for the battlecard. Required before Step 8.
  6. Linear team and label. Default label: competitive-gap.
  7. Optional: third party review or comparison videos that name the competitor. Official channels rarely show their own pricing page; reviewers sometimes do.

Files and state

Keep everything in one working folder:

battlecard-COMPETITOR/
  config.json      competitor, channel URL, OUR_FEATURES path, budget, Vivu project id, Notion page, Linear team
  videos.tsv       one row per listed video: id, upload date, duration, title, caption scores, selected
  captions/        auto captions, one .vtt per video
  upload/          only the selected .mp4 files
  frames/          frames and contact sheets extracted during triage
  results/         raw search results, one JSON per query
  hits.csv         every returned hit: field, youtube_id, start, end, judgment, brand_check, note, frame_file
  battlecard.md    the rendered battlecard
  gaps.csv         one row per feature gap
  state.json       completed steps, uploaded and searched video IDs, Linear issue ID per gap key

A rerun reads state.json first and skips finished steps. A video already in the uploaded list is never uploaded again, and a gap key (competitor plus feature) that already has a Linear issue ID is never filed twice.

Step 1: Check the Vivu connector and the setup

Goal: confirm every requirement before spending anything.

  1. Call vivu_get_account. If the tool does not exist, tell the user to add the Vivu connector in Claude (https://mcp.vivu.ai/mcp) and stop. If the account does not show can_create_projects: true, or a later write call fails with "has not granted vivu.write", ask the user to reconnect Vivu and allow write access, then stop until they have.
  2. Run yt-dlp --version, ffmpeg -version, ffprobe -version and node --version.
  3. Confirm the Notion parent page and the Linear team are reachable.
  4. Ask for OUR_FEATURES.

Done when vivu_get_account shows can_create_projects: true, the four tool versions print, and the user has either given OUR_FEATURES or agreed to a battlecard without gaps.

Step 2: List the library and pull captions

Goal: know every candidate video and its captions without spending any Vivu allowance.

Ask the user to confirm the Compliance items on downloading before this step.

yt-dlp --flat-playlist --playlist-end 200 --extractor-args youtubetab:approximate_date --print "%(id)s|%(duration)s|%(upload_date)s|%(title)s" "https://www.youtube.com/@HANDLE/videos"
yt-dlp --flat-playlist --playlist-end 200 --extractor-args youtubetab:approximate_date --print "%(id)s|%(duration)s|%(upload_date)s|%(title)s" "https://www.youtube.com/@HANDLE/streams"
yt-dlp --js-runtimes node --skip-download --write-auto-subs --write-subs --sub-langs "en,en-orig" --sub-format vtt --sleep-subtitles 3 -P "home:battlecard-COMPETITOR/captions" -o "%(id)s.%(ext)s" "https://www.youtube.com/watch?v=VIDEO_ID"

HANDLE is the channel handle and VIDEO_ID the 11 character YouTube ID. Webinar replays often live under /streams. approximate_date is coarse: many uploads older than a few months share the same date. Ask only for the caption languages you need; asking for every translated track triggers rate limits.

Done when videos.tsv has one row per listed video and captions/ has a .vtt for each video that has captions.

Step 3: Price the library and pick the subset

Goal: an indexed set the user's plan can pay for, chosen by the user.

A competitor's full library rarely fits a plan. As an estimate, 40 hours of webinars is about 2,400 index minutes; the Premium plan has 180 index minutes a month. Rank the videos with free signals first:

grep -c -i -E "pric|per month|a month|plans? " battlecard-COMPETITOR/captions/VIDEO_ID.en.vtt
grep -c -i -E "coming|upcoming|roadmap|soon|next month|later this year" battlecard-COMPETITOR/captions/VIDEO_ID.en.vtt
grep -c -i -E "settings|set up|configure|enable|turn on|toggle" battlecard-COMPETITOR/captions/VIDEO_ID.en.vtt

Caption counts only rank videos; they never rule one out. In our test run a product update whose captions never mentioned settings still showed a full settings form on screen, and the only competitor pricing page sat in a video whose narration named no price. Rank by video type too (product updates, demos, how-to videos and webinar replays first), then add durations until the budget is reached.

Call vivu_get_usage and show one table:

This run Plan allowance Remaining this month
Index minutes sum of selected durations from vivu_get_usage from vivu_get_usage
Search credits standing queries x 5, plus rewordings (estimate) from vivu_get_usage from vivu_get_usage

Plan facts: Free is $0 a month with 20 index minutes a month and 50 search credits a month; Premium is $30 a month with 180 index minutes and 500 search credits. A fast search uses 1 credit; a precise search uses 5 credits in total. One battlecard run with the three standing queries uses about 15 search credits (estimate, before any rewording). Whether a larger maximum_results costs more credits is untested.

If the selection does not fit, offer levers in this order: a shorter lookback window, only product update, demo and how-to videos, drop the lowest scoring videos, and last a larger plan. Do not suggest skipping long webinars to save minutes; pricing pages and roadmap talk tend to sit near the end of them.

Done when the user approves the subset and the minute total, and config.json records both.

Step 4: Download, upload and index

Goal: every approved video indexed in a private Vivu project.

yt-dlp --js-runtimes node --restrict-filenames \
  -f "bv*[height<=720]+ba/b[height<=720]" --merge-output-format mp4 \
  --match-filter "duration<=MAXSECONDS" \
  --download-archive battlecard-COMPETITOR/archive.txt \
  -P "temp:SCRATCH_DIR_OUTSIDE_UPLOAD" -P "home:battlecard-COMPETITOR/upload" \
  -o "%(id)s_%(title).60B.%(ext)s" --retries 5 \
  "https://www.youtube.com/watch?v=VIDEO_ID"

MAXSECONDS is the longest video the user approved, in seconds; SCRATCH_DIR_OUTSIDE_UPLOAD is any folder outside upload/. 720p keeps settings labels and prices readable at a fraction of the 1080p size. On HTTP Error 403, retry that video once with --extractor-args "youtube:player_client=web_embedded". If yt-dlp warns that formats were skipped and falls back to 360p, keep the file and tell the user that small labels and billing units may be unreadable. Check each file with ffprobe -v error -show_entries format=duration -of csv=p=0 FILE. The youtube-competitor-watch skill (https://vivu.ai/skills/youtube-competitor-watch) covers channel verification and download troubleshooting in more depth.

  1. Call vivu_list_projects and reuse a project for this competitor if one exists. Otherwise call vivu_create_project with name "Battlecard COMPETITOR" and visibility "private". The default visibility is organization, which shows the project to everyone in the user's Vivu organization.
  2. Call vivu_open_upload_page with the project ID immediately before uploading. The link expires in 180 seconds and works once, so never post or store it. Give it to the user to open in their own browser and select the files in upload/, or open it in a browser tool that can attach local files. Claude in Chrome accepts at most 10 MB per upload call; a larger file goes through the user's own browser or the Vivu web app. Never split or recompress a video to fit.
  3. Vivu replaces spaces and punctuation in file names with underscores, plus signs included. Match results back by the 11 character YouTube ID at the start of each file name.
  4. Poll vivu_list_videos until every video shows ready, and record each Vivu video_id next to its YouTube ID in state.json.

In our test run the 8 videos (29.59 minutes) were ready 448 seconds after the upload started, about 7.5 minutes. The files were uploaded through the same upload page by an automated browser; opening the link in the user's own browser, as item 2 describes, was not exercised in our test run.

Done when every approved video shows ready and state.json maps each YouTube ID to its Vivu video_id.

Step 5: Run the standing queries

Goal: candidate moments for each battlecard block.

Field Query Mode maximum_results Role
roadmap_said the speaker says out loud that a feature is not released yet: it is coming soon, upcoming, or planned for the coming months (spoken in the voiceover, not only written on screen) precise 20 core: Roadmap claims
settings_panel a settings or configuration panel for a product feature with toggles, dropdowns or form fields visible on screen precise 40 core: Feature inventory and gaps
pricing_candidate a pricing table on screen where each plan column shows a price amount, a number with a currency symbol per month, next to the plan name precise 20 candidates only, see Step 6

The chain: roadmap_said finds what the competitor says it does not have yet; settings_panel flips from said to shown and lays out what it has already shipped, screen by screen, which is what the gap list compares against; pricing_candidate adds which plan those screens sit behind, when a plans page happens to be on screen. All three are precise because only precise returns a time window and a reason; fast returns whole files with an empty reason, and vivu_search_videos has no file filter that would let fast narrow the precise search.

maximum_results is also the recall ceiling. In our test run the settings query at maximum_results 20 returned 20, exactly the cap, so it was rerun at 40. The roadmap and pricing queries returned far fewer than 20.

If a settings run returns exactly its cap, raise maximum_results and rerun before building the inventory; maximum_results is 1 to 100.

How far to trust each field: finding spoken statements by meaning is reliable in Vivu, though the reason text is a paraphrase, not a transcript. Matching what a screen looks like (a settings form) is rated weak in general because it can miss moments, and this skill's settings query has been measured on one test corpus only. Reading prices from a screen is the weakest of the three here, so pricing hits are candidates. No result goes into the battlecard on the search alone: Step 6 checks every hit against frames or captions.

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. Save each completed result to results/FIELD.json. Show the Vivu result page link in the live reply; it expires after four hours, so it never goes into a file, the battlecard or an issue.

Worked example from our test run

The test corpus was public: 8 videos (29.59 minutes) from webinar software vendors' official channels (a how-to, three monthly product updates, a talk about paid webinars, a partner integration tutorial, a short demo) plus one third party comparison video.

  1. roadmap_said: the wording in the table came after 1 rewording on the same corpus. It returned 3 moments, 3 real, 0 false and 0 missed against the 3 roadmap statements in the captions. The first wording also returned an on screen "(Feature in beta)" label and a "coming soon" the reason made up, 2 false out of 5.
  2. settings_panel: at maximum_results 40 it returned 24 moments, 22 real and 2 false, a false positive rate of 8%. The false ones were an AI generated mock interface in the comparison video and an intro animation. Rerunning at the higher cap found 3 more real settings screens than the run capped at 20. No full ground truth exists for this field, so recall is unknown. Several real moments were settings screens of a partner or another vendor, which the brand check keeps out.
  3. pricing_candidate: the wording in the table came after 2 rewordings on the same corpus, and all three wordings behaved the same: 3 moments, 2 real pricing pages and 1 false, 0 missed. The false one was two vendor homepages with a Pricing link and no price; the reason described plans with monthly prices. That is why every pricing candidate goes through the contact sheet in Step 6.

Done when every standing query has a completed result saved in results/ and no settings run returned exactly its maximum_results.

Step 6: Triage every hit

Goal: every hit judged real or false against the video, with the frame or caption that proves it.

For settings panels, extract three frames inside the window (start plus half a second, middle, end minus half a second) and a strip of the three side by side:

ffmpeg -v error -y -ss START -i FILE -ss MID -i FILE -ss END -i FILE -filter_complex "[0:v]split=2[s0][t0];[1:v]split=2[s1][t1];[2:v]split=2[s2][t2];[t0]scale=640:-2[u0];[t1]scale=640:-2[u1];[t2]scale=640:-2[u2];[u0][u1][u2]hstack=3[strip]" -map "[s0]" -frames:v 1 frames/FIELD_hN_start.png -map "[s1]" -frames:v 1 frames/FIELD_hN_mid.png -map "[s2]" -frames:v 1 frames/FIELD_hN_end.png -map "[strip]" -frames:v 1 frames/FIELD_hN_strip.png

START, MID and END are seconds, FILE is the local video, FIELD and N name the hit. Look at the strip. A hit is real when at least one frame shows a settings or configuration form (switches, dropdowns, input fields) for a product feature. A marketing page, a mock illustration or the presenter talking is false. If none of the three frames shows the target, tile the window at one frame per second before calling it false, because a form can sit between the sampled frames:

ffmpeg -v error -y -ss START -to END -i FILE -vf "fps=1,scale=480:-2,tile=4x4" -frames:v 1 frames/FIELD_hN_every_second.png

Then run the brand check: whose product is on screen? Partner tools and other vendors' screens are real Vivu hits but stay out of this competitor's inventory.

For pricing candidates, a price enters the battlecard only after a contact sheet of the whole window at two frames per second shows a plans table with prices:

ffmpeg -v error -y -ss START -to END -i FILE -vf "fps=2,scale=320:-2,tile=8x6" -frames:v 1 frames/pricing_hN_every_half_second.png

Raise the tile rows for windows longer than 24 seconds. Read plan names and prices from the frames, never from the reason. Record whose pricing page it is. A candidate with no price in any tile is rejected and counted.

In our test run the reason quoted a price that differs from the one in the frame, and it described plans and prices on a homepage that had none.

For roadmap_said, quote from the local captions (captions/VIDEO_ID.en.vtt) around the window, never from the reason, which paraphrases and can invent a phrase. Use vivu_get_video_summary with include_segments, start_ms and end_ms around the hit as a second signal. "You can now" and "recently released" are shipped features, not roadmap; a word written only on screen is not a spoken claim.

Write every hit to hits.csv with its judgment and frame file, and tell the user the false positive count per field. Anything not checked stays labeled as a candidate.

Done when every row of hits.csv has a judgment of real, false or rejected and each real visual hit names a frame file that exists.

Step 7: Build the battlecard and the gap list

Goal: a battlecard and gap list the user has approved in sample form.

Render battlecard.md with these blocks, each line ending in its YouTube link:

# COMPETITOR battlecard (DATE, from N videos, M minutes indexed)

## Roadmap claims (what they say is coming; claims, not facts)
- "QUOTE FROM CAPTIONS" (VIDEO TITLE, MM:SS, https://www.youtube.com/watch?v=VIDEO_ID&t=SECONDS)

## Feature inventory from settings panels
- FEATURE: SETTING, SETTING, SETTING (VIDEO TITLE, MM:SS, LINK)

## Pricing (only candidates confirmed on a contact sheet)
- PLAN: PRICE per UNIT (billing period NOT VERIFIED if unreadable) (VIDEO TITLE, MM:SS, LINK)
- Rejected candidates: N, and why

## Gaps vs OUR_PRODUCT
- FEATURE: missing | partial (UNCERTAIN until a product owner confirms)

## Sources
- VIDEO TITLE, https://www.youtube.com/watch?v=VIDEO_ID

When no pricing candidate survives the contact sheet, write "No pricing page confirmed in the indexed videos" rather than leaving the block out, and say that this is not proof none exists.

Compare each inventory line with OUR_FEATURES and write gaps.csv:

competitor,feature,evidence_type,video_id,title,timestamp,youtube_link,frame_file,our_status,priority_guess
COMPETITOR,Caption hints for brand names,settings_panel,VIDEO_ID,Product update,00:44,https://www.youtube.com/watch?v=VIDEO_ID&t=44,settings_panel_h6_mid.png,missing,UNCERTAIN
Column Source If unavailable
feature the settings screen in the frame drop the row
video_id, title, timestamp, youtube_link hits.csv and videos.tsv drop the row
frame_file the triage frame that shows the setting drop the row
our_status Claude's reading of OUR_FEATURES (inferred) UNCERTAIN
priority_guess left for the user UNCERTAIN

our_status is an inference from the list the user gave. If it is wrong, the Linear issue describes a gap that does not exist, so every row stays UNCERTAIN until the user or a product owner confirms it. Show the user the battlecard outline, one gaps.csv row and the mapping table, and wait for approval before bulk work. If fewer gaps are found than the user expects, report the real number.

Done when battlecard.md and gaps.csv exist and the user has approved the sample row and the mapping.

Step 8: Write the battlecard to Notion and the gaps to Linear

Goal: the approved battlecard and issues in the user's tools. This step was not exercised in our test run; the page and issues were only rendered to files.

Writing to Notion and Linear acts as the user. Name the Notion parent page and the Linear team, show one fully rendered issue and the page outline, and wait for an explicit yes.

  1. Create one Notion page under the parent page with the content of battlecard.md.
  2. For each gaps.csv row without an issue ID in state.json, create one Linear issue: title "[Gap] COMPETITOR: FEATURE", description with one sentence, the YouTube link with &t=SECONDS, the frame file name, our_status and a link to the Notion page, label competitive-gap, no assignee. Record the issue ID in state.json.

Sample issue:

[Gap] COMPETITOR: Caption hints for brand names
COMPETITOR shows caption hints in Product update at 00:44 (https://www.youtube.com/watch?v=VIDEO_ID&t=44). Evidence: settings panel, frame settings_panel_h6_mid.png. Our status: missing (UNCERTAIN). Battlecard: NOTION_PAGE_LINK.

Done when the Notion page exists and every gaps.csv row has a Linear issue ID in state.json.

Step 9: Refresh each quarter

Goal: an updated battlecard without redoing old videos. This step was not exercised in our test run.

A refresh needs the shell, the local folder and the residential IP, so it runs as a local routine in Claude Code Desktop (Code tab, Routines, New routine, Local). It runs only while Claude Desktop is open and the computer is awake. The routine prompt must stand alone: the working folder path, config.json, the Vivu project ID, the Notion page, the Linear team, and these steps: list new uploads since the last run, repeat Steps 3 to 7 for new videos only (videos already in state.json are skipped; the search covers the whole project, so keep only hits on new video IDs), then pause for approval before Step 8. Show the user the prompt and the cadence, wait for approval, create it, and start it once with Run now so the user can allow its tools.

Done when the routine exists, the user approved its prompt and cadence, and one Run now has finished.

Compliance

  1. Downloading YouTube videos may conflict with YouTube's Terms of Service. Keep downloads for internal competitive analysis, do not redistribute them, and do not use the competitor's footage or logos in anything public. Have the user confirm this is acceptable for their organization before Step 2.
  2. The people on screen are the competitor's staff in public marketing videos. Do not identify them; the battlecard names companies and videos, not presenters. Do not download videos centered on minors.
  3. The videos stay in the user's Vivu project until the user deletes them. Create the project as private; the default is visible to the whole Vivu organization. Delete videos or the project only when the user asks, and confirm first.
  4. Steps 8 and 9 act on the user's behalf in Notion, Linear and a local routine; each needs the approval described there.
  5. This skill does no face, logo or identity recognition.

Known failure modes

Symptom Cause Fix
(observed) "ERROR: unable to download video data: HTTP Error 403: Forbidden" YouTube refused the default yt-dlp client retry that video once with the web_embedded player client (Step 4)
(observed) "Some web_embedded client https formats have been skipped as they are missing a URL", then a 360p file YouTube's SABR streaming experiment keep the file, warn that small labels and billing units may be unreadable
(observed) "HTTP Error 429: Too Many Requests" on caption downloads too many caption tracks requested at once ask only for en and en-orig and keep the sleep between caption requests (Step 2)
(observed) a marketing homepage comes back as a pricing page with made up plans and prices the search matched a Pricing link or narration about pricing reject any pricing candidate whose contact sheet shows no price
(observed) the settings query returns exactly maximum_results moments the cap, not the footage, set the count raise maximum_results and rerun before building the inventory
(observed) none of the sampled frames shows the settings form, yet the form is in the window the window is longer than the time the form is on screen tile the window at a frame per second before calling it false
(observed) a word written on screen, such as a beta label, comes back as a spoken roadmap claim speech and screen text mixed in the match keep the roadmap wording that asks for spoken statements only; check captions
(observed) a settings hit shows a partner's or another vendor's screen tutorials and comparison videos show several products brand check each hit before it enters the inventory
upload page asks to sign in or shows an error the one time upload link expires after 180 seconds request a new link right before opening it
a file is rejected by the browser upload tool the tool accepts at most 10 MB per call the user adds that file in their own browser or the Vivu web app
"has not granted vivu.write" Vivu connected read only the user reconnects Vivu with write access
a result's file name does not match videos.tsv Vivu normalized the file name match on the YouTube ID at the start of the name
"Sign in to confirm you're not a bot" download attempted from a cloud IP run the downloads on the user's computer
an empty settings or roadmap result the indexed videos may not contain it, or the query missed it an empty result does not prove the footage has none; try a broader wording or more videos

Connect Vivu, then add the skill.

The skill runs through the Vivu connector. Add it to Claude from the connector directory, then install the skill.