skills/youtube-scan/SKILL.md
Scan YouTube for videos by topic (search), channel (uploads feed), or URL (metadata). Returns titles, view counts, channel, upload date. Optionally auto-inlines a trimmed transcript snippet for high-signal hits via `--rich auto`. Pair with `/ro:video-summarize` for a full deep dive.
npx skillsauth add RonanCodes/ronan-skills youtube-scanInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Three modes, auto-detected from the argument:
"AI agents" or "MCP servers" → yt-dlp "ytsearchN:<q>"https://www.youtube.com/@<handle> or channel URL → uploads RSShttps://www.youtube.com/watch?v=... or https://youtu.be/... → single-video metadataBy default, no transcripts — this is discovery, not digestion. With --rich auto, the skill pulls a trimmed transcript snippet for the subset of hits that match a "this might be load-bearing" heuristic (see below). For full transcript + slides + LLM summary, pipe a picked URL into /ro:video-summarize.
/ro:youtube-scan "AI agents" # topic search, last 7d, top 15 by views
/ro:youtube-scan "MCP servers" --since 24h # fresh drops
/ro:youtube-scan "vibe coding" --limit 30 --min-views 10000
/ro:youtube-scan https://www.youtube.com/@simonwillison # channel's latest
/ro:youtube-scan https://www.youtube.com/watch?v=XXXX # single video metadata
/ro:youtube-scan "MCP" --rich auto # inline transcripts for high-signal hits
/ro:youtube-scan https://youtu.be/XXXX --rich all # force transcript on a single video
7d15--min-views to filter out low-signal uploads)off (transcripts not fetched)30000 views-per-day velocity (when --rich auto), computed as view_count / max(age_days, 1). Catches recent viral hits, rejects evergreen high-total-view content (old music videos, study streams) that would pass a raw-view threshold.mcp,agent,claude,anthropic,openai,protocol,spec,sdk (when --rich auto)tool, lou zeffer, jimtv, cosmic hippo, chill music lab, freecodecamp.org, tech with tim. Substring (case-insensitive): lofi, chillstep, chill music, sleep music, ambient music. Tunes out the noise classes that dominated early validation scans (the band TOOL, coding-music channels, multi-hour study streams).yt-dlp "ytsearch${LIMIT}:${QUERY}" \
--dump-json --skip-download --no-write-subs \
--match-filter "upload_date>=$(date -v-${SINCE} +%Y%m%d)" \
2>/dev/null
Each JSON line has title, webpage_url, channel, view_count, upload_date, duration, description.
Do NOT use --flat-playlist. It strips upload_date, which breaks the velocity heuristic used by rich mode AND silently disables --match-filter (no date to filter on). Full-metadata mode takes ~1.5s per video vs. ~0.2s flat, which is an acceptable cost for a discovery-time scan.
Ranking heuristic: views / (days_since_upload + 2) — similar to HN's age-decay, but view-weighted.
Prefer the free RSS endpoint over yt-dlp for channel listings — no auth, no rate limiting:
# If given an @handle, first resolve to channel_id via yt-dlp
CHANNEL_ID=$(yt-dlp --print channel_id "$URL" --playlist-items 0 2>/dev/null | head -1)
curl -s "https://www.youtube.com/feeds/videos.xml?channel_id=${CHANNEL_ID}"
Atom XML. Parse with Python (namespace http://www.w3.org/2005/Atom and http://search.yahoo.com/mrss/ for media metadata). Each <entry> has <title>, <link href="...">, <published>, <media:statistics views="...">.
yt-dlp --dump-json --no-download "$URL"
Returns the full metadata blob. Pull: title, uploader, view_count, like_count, upload_date, duration, description, tags.
Lightweight transcript-grab for the subset of hits likely to contain novel content. Does NOT run any LLM — it just pulls captions and trims. This closes the "I only saw the title, missed the substance" gap for high-signal talks (conference keynotes, announcements, explainers from authoritative authors).
--rich auto)A video gets a transcript snippet only if it passes ALL the hard filters AND at least one signal trigger.
Hard filters (all must pass):
upload_date parseable (we need it for the velocity calc)Signal triggers (any one suffices):
views_per_day >= ${RICH_THRESHOLD:-30000} — recency-weighted. Computed as view_count / max(age_days, 1). A Fireship short at 800k views over 2 days (400k/day) triggers; a 20-year-old music video at 70M total (≈10k/day) does not.${RICH_KEYWORDS:-mcp,agent,claude,anthropic,openai,protocol,spec,sdk}Override per-call:
--rich-threshold N (views-per-day, not raw views)--rich-keywords "a,b,c"--rich-min-duration S / --rich-max-duration S (seconds)--rich-channel-deny "a,b,c" (adds to the default denylist; does not replace it)For force-on-all (bypasses all hard filters and triggers), use --rich all. For force-off, use --rich off (the default).
TMP=$(mktemp -d)
yt-dlp \
--write-auto-sub --skip-download \
--sub-lang en --sub-format vtt \
-o "$TMP/cap" \
"$URL" 2>/dev/null
If no English auto-subs exist, skip gracefully (leave the entry title-only, flag [no captions]).
python3 - "$TMP/cap.en.vtt" <<'PY'
import sys, re, pathlib
vtt = pathlib.Path(sys.argv[1]).read_text(errors="ignore")
# drop WEBVTT header + cue timing lines, keep only caption text
lines = [l for l in vtt.splitlines()
if l.strip() and "-->" not in l and not l.startswith(("WEBVTT", "Kind:", "Language:", "NOTE"))]
# strip HTML-like tags that yt-dlp injects (<c>, <00:00:00.000>)
text = re.sub(r"<[^>]+>", "", "\n".join(lines))
# deduplicate consecutive duplicate lines (auto-caption rolling overlap)
out, prev = [], None
for l in text.splitlines():
s = l.strip()
if s and s != prev:
out.append(s); prev = s
joined = " ".join(out)
# trim to ~1000 words (first-1000 covers intro/thesis for most talks)
words = joined.split()
print(" ".join(words[:1000]))
PY
Output: ~1000 words of readable text. For a 30-min talk that's the intro + first third, which typically contains the thesis and any novel terminology.
Under a triggered video, add a > block with the snippet plus a marker so downstream consumers can tell it was auto-triggered:
1. <Title> (<N> views · <M> days ago · <channel>)
https://www.youtube.com/watch?v=<id>
<brief gist from description, 1 line>
📜 [transcript snippet, auto-rich, ~1000 words]
> <snippet text...>
For videos that DON'T trigger, output is unchanged. Keep the scan lean.
Duration outside 3-60min and denylisted channels are now rejected upfront by the hard filters (see Trigger heuristic), not skipped after the fact. After trigger, the only remaining skip reasons are:
In each case, append [transcript skipped: <reason>] to the entry and move on.
# YouTube: "<query>" (window: <since>, mode: <search|channel|url>)
1. <Title> (<N> views · <M> days ago · <channel>)
https://www.youtube.com/watch?v=<id>
<brief gist from description, 1 line>
2. ...
For the single-URL mode, emit:
# Video: <title>
Channel: <channel>
Uploaded: <date> (<N days ago>)
Views: <count>
Likes: <count>
Duration: <mm:ss>
<description, first 5 lines>
When called by /ro:trend-scan --discover, there's no topic. In that case run a handful of specific seeds in sequence — generic words like AI, coding, new tool pull in music videos, lofi streams, and the band TOOL. Use narrowly agent/AI-tooling-framed phrases instead:
ytsearch15:AI agents
ytsearch15:MCP
ytsearch15:claude code
ytsearch15:anthropic
ytsearch15:new AI tool
ytsearch15:AI keynote
Each seed is fetched without --flat-playlist so that upload_date is present (required for the velocity calc). The --match-filter "upload_date>=$SINCE" clause must be applied at fetch time; it's a no-op under flat-playlist mode.
Filter to the --since window, dedupe by webpage_url, sort by view count. Return the top 20.
If the caller passed --rich auto through (trend-scan does this by default in discover mode), apply the rich heuristic to each of the top 20 and inline transcript snippets for the matches. With the v2 heuristic (velocity-based + keyword + hard filters), typical yield is 3-8 videos triggered per discover scan when there's a real news cluster (e.g. Anthropic announcement weeks), 0-2 on quiet weeks.
yt-dlp (from the video-summarize skill install, or brew install yt-dlp)curl (for channel RSS)--extractor-args "youtube:player_client=web" or try --no-check-formats.youtube.com/feed/trending) is broken as of 2026-04 (redirects to homepage). Don't rely on it for discover mode; use seed searches instead.[transcript skipped: <reason>] appended. Never let one bad video block the rest.video-summarize — downstream: pick a video, get its transcript + slides + LLM summary.hn-scan, reddit-scan, x-scan — sibling source scanners.trend-scan — upstream: orchestrates all source scanners in parallel.llm-wiki/.claude/skills/ingest-youtube — when you want to keep a video in a vault.testing
--- name: linear-pipeline description: The Fable orchestrator for a single dispatched Linear ticket. Holds almost no context itself; it receives `--issue <ID> --detached`, decides the stage sequence, and fans out a sub-agent per stage, passing forward only each stage's artifact (never re-derived, never inlined into its own context). Step zero, before any planning or stage routing, is a boundary triage against `canon/security-boundary.md` (#199): a match tags Ronan Connolly and stops the run, no
development
--- name: in-your-face description: Capture a chat-only answer into a durable artifact (markdown + HTML, PDF when cheap) and launch it automatically so the user cannot miss it. Use when user says "in your face", "don't let me lose this", "save that answer", "make that durable", or right after answering a substantive side question (a recipe, comparison, how-to, or generated prompt) that would otherwise die with the context. category: workflow argument-hint: [--no-open] [--vault <short>] [hint of
tools
One-shot headless OpenAI Codex CLI calls for background/admin AI tasks — summaries, classification, extraction, admin glue. The default engine for anything that runs AI constantly in the background (daemon-driven, per-event), because it bills the flat ChatGPT subscription instead of Claude usage or per-token API spend, and it keeps working while Claude is rate-limited. NEVER for coding — coding stays Claude. Use when a skill or daemon needs a cheap always-on AI call, when the user says "use codex", "ask codex", "codex as backup", or when building a background summarizer/classifier into a listener or loop. Reads auth from ~/.codex/auth.json (ChatGPT account, no API key).
research
Turn a warranty rejection, repair quote, or RMA email into a cited decision brief — legal read (NL/EU consumer law), is the part user-serviceable, live part and new-unit prices, repair-vs-DIY-vs-new economics, before-you-send-it checklist, deadlines. Use when the user pastes or screenshots a repair quote, warranty rejection, "not covered" email, onderzoekskosten fee, or asks "should I repair or replace this".