YouTube Search + Curation
youtube "system design interviews" # → saves ./lists/system-design-interviews.md
youtube "rl lectures" --captions --num 8 # only videos WITH transcripts
youtube "rust async" --preset fresh # ranking preset
youtube "query" --stdout # print only (legacy)
Finds fresh, long, deep content (Invidious API, no key), ranks it, and saves a list you curate — promote picks, tag, exclude channels, expand — then hand a URL to vtd to transcribe. Zero dependencies (stdlib only).
Install
Run the installer from this skill's own directory (next to SKILL.md):
./install.sh # → ~/.local/bin/youtube (no deps; symlink only)
Search → list
youtube "<query>" [filters] # saves ./lists/<slug>.md, prints path + preview
youtube "<query>" --save rl-set # name the list explicitly
Each entry carries a real youtube.com/watch?v=ID (vtd-ready) + channel · duration · views · age · ★score.
Filters
| Flag | Default | What |
|---|---|---|
--num N |
8 | picks surfaced (rest → ## Candidates) |
--pool N |
30 | candidates fetched+ranked |
--preset |
deep | deep · trending · fresh (ranking profiles) |
--fresh SPEC |
18m | max age: 3m 1y 2w 14d all |
--min-views N |
1000 | view floor |
--any-length |
off | include shorts |
--captions |
off | only videos with subtitles (transcript-friendly) |
--channel NAME|UCID |
— | search within a channel |
--exclude-channel X |
— | drop a channel (repeatable) |
--min-duration / --max-duration |
1200 / — | seconds |
--region CC |
— | locale |
--rank MODE |
— | raw sort: relevance/date/views/rating (disables deep mode) |
--no-favs |
off | disable favourite-channel boost |
Refine a list (curation)
The list is a living .md — curate by editing it directly (promote, tag, move to "maybe", drop). Use the CLI only for bulk/mechanical ops:
youtube exclude --channel "Name" --list <name> # remove every entry from a channel
youtube dedup --list <name> # drop duplicate video IDs
youtube expand --like <url> | --channel <name> | --more "<q>" [--list <name>] # add candidates
→ Read references/curation.md for section semantics, tag vocabulary, and the full refine loop. Read when curating a list.
Channel preferences (global, grows over time)
youtube channel --block "Tutorial Purge" # auto-excluded from every search
youtube channel --fav "Two Minute Papers" # ranked higher when they match
youtube channel --list
Stored at ~/.config/youtube-skill/channels.md. Blocked → filtered before ranking; favourites → score boost.
Update / health
youtube --update # git pull
youtube --selfcheck # version + last update
youtube --discover # refresh the Invidious instance cache
Gotchas
- Invidious
date/durationAPI filters are leaky — deep mode re-checks age + duration client-side. - Age-restricted videos are discovered (Invidious returns their metadata) but are marked
🔒 age-restrictedin the saved list — the public Invidious stream returns403 Forbiddenfor them (Google requires auth). To transcribe a marked video:vtd transcript --url … --cookies— but which browser profile passes the age gate is account-specific (see vtd SKILL.md → "Profile selection"). Translated captions often 429; prefer the video's original language. - Invidious
/videos/{id}(related videos) is widely blocked — soexpand --likeresolves the video's channel instead (more from creator). If that fails, use--channelor--more. - Watch links are
youtube.com(not the Invidious host), so they're ready forvtd transcript --url …. - Fewer picks than
--num? Filters are strict. Widen with--fresh all,--any-length, lower--min-views, or bigger--pool. - "all instances failed" →
youtube --discover(parallel probe, rebuilds the cache). Most public instances die regularly — the self-heal (evict/promote/merge) usually recovers on its own. - The long-term fix is a yt-dlp backend rewrite — see docs/youtube-rewrite-plan.md.
How it works
- Instance resolution, self-healing and self-maintaining: probe order is fresh cache → hosts with recent real-world success (health history in
.instance-stats.json, recorded on every search) → cold-start pool. Dead hosts are evicted, healthy ones promoted; rotted entries (45d without success) are pruned automatically. On cache-miss, a parallel probe (registry + pool, browser-UA retry) refreshes the cache for 4h. - Search
/api/v1/search(or/channels/{ucid}/searchfor--channel) with filters - Deep mode: apply fav/block + filters → score (preset weights) → rank → split picks/candidates
- Write
./lists/<slug>.md; print path + preview