Two Claude Code skills I use daily: pull YouTube thumbnails at full resolution, and scrape any website into clean markdown. Copy the files or download each skill, drop it in ~/.claude/skills/, add your own keys.
Used by youtube-thumbnails to search YouTube. Free tier available.
Get a SerpApi key →Used by firecrawl-scraper for all scraping. Free tier available.
Get a Firecrawl key →No keys are included in these files. Put your SerpApi key in env.sh (replace the placeholder), and your Firecrawl key in the FIRECRAWL_API_KEY env var or your macOS Keychain under the service name codex-firecrawl-api-key.
Search YouTube by topic, channel, or URL via SerpApi and download every thumbnail at max resolution. If the Paper design tool is open, it auto-places them in a reference frame.
Install to: ~/.claude/skills/youtube-thumbnails/
--- name: youtube-thumbnails description: Pull YouTube video thumbnails using SerpApi whenever Riley asks to "pull youtube thumbnails", grab thumbnails for a video/channel/search topic, collect thumbnail references, or research thumbnail styles. Downloads full-res thumbnail images locally, and if a Paper file is open, places them into a frame in Paper. --- # YouTube Thumbnails via SerpApi Pull YouTube thumbnails for a search query, channel, or specific videos using SerpApi, download them locally, and (when Paper is open) lay them out in a frame in Paper. ## Credentials The SerpApi key lives in `env.sh` next to this file. Source it in every Bash call that hits SerpApi; NEVER print or echo the key: ```bash source ~/.claude/skills/youtube-thumbnails/env.sh ``` ## Step 1 — Search YouTube via SerpApi Use the `youtube` engine with the search query Riley gives (video title, channel name, or topic): ```bash source ~/.claude/skills/youtube-thumbnails/env.sh curl -s "https://serpapi.com/search.json?engine=youtube&search_query=$(python3 -c 'import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))' 'QUERY HERE')&api_key=$SERPAPI_API_KEY" > /tmp/yt_results.json ``` Parse `video_results[]` from the JSON — each entry has `title`, `link`, `channel.name`, `views`, and `thumbnail.static`. ## Step 2 — Get full-resolution thumbnails SerpApi's `thumbnail.static` is often small. For full quality, extract the video ID from each `link` (the `v=` param) and fetch directly from YouTube's image CDN: - `https://img.youtube.com/vi/<VIDEO_ID>/maxresdefault.jpg` (1280x720 — try first) - Fall back to `hqdefault.jpg` (480x360) if maxres returns 404 or a tiny placeholder (<5KB file = placeholder). Download 6–12 thumbnails (or however many Riley asked for) with descriptive filenames into the session scratchpad directory (or a folder Riley names): ```bash curl -s -o "thumb_01_some-title.jpg" "https://img.youtube.com/vi/VIDEO_ID/maxresdefault.jpg" ``` ## Step 3 — If Paper is open, place them in a frame Check whether a Paper file is open with `mcp__paper__get_basic_info` (load Paper tool schemas via ToolSearch first if needed). - **If a file is open**: follow the Paper MCP guide (`get_guide`), then use `write_html` to create a new frame/artboard containing the thumbnails in a grid — each thumbnail as an image at 16:9 (e.g. 320x180 or 480x270 tiles), labeled underneath with the video title and channel/views in small text. Name the frame after the query (e.g. "Thumbnails — mrbeast"). Screenshot to verify, then call `finish_working_on_nodes`. - **If no file is open**: just report the downloaded local files with a short table of title/channel/views, and offer to open a Paper file and place them. ## Notes - Also handle direct video URLs: if Riley pastes a youtube.com/watch or youtu.be link, skip the search and go straight to Step 2 with that video ID. - If SerpApi returns an error about the key or quota, report the error message but never the key itself.
# SerpApi credentials for the youtube-thumbnails skill. Do not print this value. # Get your key at https://serpapi.com/manage-api-key export SERPAPI_API_KEY="YOUR_SERPAPI_KEY_HERE"
Scrape, crawl, map, search, and extract from any website — JS-heavy or bot-protected included — into clean markdown/JSON via the Firecrawl v2 API. One shell wrapper, five endpoints.
Install to: ~/.claude/skills/firecrawl-scraper/
---
name: firecrawl-scraper
description: Scrape any website into clean markdown/JSON using Riley's Firecrawl account (key already in macOS Keychain). Use this whenever Riley asks to scrape, crawl, extract, or pull content from a website or URL, read a page that blocks simple fetching, map all the pages/links on a site, do web search with full page content, or mentions Firecrawl at all. Prefer this over plain WebFetch when a site is JS-heavy, paywalled-ish, bot-protected, or when he wants many pages from one site.
---
# Firecrawl Scraper
Scrape, crawl, map, search, and extract from websites via the Firecrawl REST API (v2).
All calls go through the bundled wrapper, which resolves the API key at runtime — from `$FIRECRAWL_API_KEY` if set, otherwise from the macOS Keychain service `codex-firecrawl-api-key`. Never print, echo, or log the key itself.
```bash
bash ~/.claude/skills/firecrawl-scraper/scripts/firecrawl.sh <endpoint> '<json-body>'
```
## Endpoints
### scrape — one page → clean content (most common)
```bash
bash ~/.claude/skills/firecrawl-scraper/scripts/firecrawl.sh scrape \
'{"url":"https://example.com","formats":["markdown"]}'
```
- `formats` can include `"markdown"`, `"html"`, `"links"`, `"screenshot"`.
- Add `"onlyMainContent": true` (default) to strip nav/footers.
- For JS-heavy pages add `"waitFor": 2000` (ms).
### map — list every URL on a site (fast, cheap)
```bash
... firecrawl.sh map '{"url":"https://example.com"}'
```
Use this first when Riley wants "the whole site" — map it, pick the relevant URLs, then scrape just those. Much cheaper than a blind crawl.
### crawl — scrape many pages in one job (async)
```bash
... firecrawl.sh crawl '{"url":"https://example.com","limit":20}'
```
Returns a job `id`. Poll for results with a GET (no body):
```bash
... firecrawl.sh crawl/<job-id>
```
Poll every ~5s until `"status":"completed"`. Always set a sensible `limit` (default to 10–25 unless Riley asks for more) — crawls burn credits per page.
### search — web search with scraped content
```bash
... firecrawl.sh search '{"query":"best ai video editors 2026","limit":5}'
```
Add `"scrapeOptions":{"formats":["markdown"]}` to get full page content for each result.
### extract — structured data via a schema/prompt
```bash
... firecrawl.sh extract \
'{"urls":["https://example.com/pricing"],"prompt":"Extract all plan names and monthly prices"}'
```
Async like crawl — poll `extract/<job-id>` if the response returns an id.
## Working style
- Save big outputs to files (scratchpad or wherever Riley asks), not into chat — a scraped site can be huge. Summarize in chat, link the files.
- Responses nest content under `.data` — pipe through `jq -r '.data.markdown'` for a single scrape, or `.data[]` for crawl results.
- On a 401/402 error, the key is invalid or out of credits — tell Riley which it is; don't retry blindly.
- If Riley only needs one simple, static page, plain WebFetch is fine and costs no credits; use Firecrawl when it earns its keep.
#!/bin/bash
# firecrawl.sh — thin wrapper around the Firecrawl REST API.
# The API key is resolved at runtime and never printed:
# 1. $FIRECRAWL_API_KEY env var (wins if set)
# 2. macOS Keychain service "codex-firecrawl-api-key"
#
# Usage:
# firecrawl.sh <endpoint> ['<json-body>']
#
# Examples:
# firecrawl.sh scrape '{"url":"https://example.com","formats":["markdown"]}'
# firecrawl.sh map '{"url":"https://example.com"}'
# firecrawl.sh search '{"query":"ai video tools","limit":5}'
# firecrawl.sh crawl '{"url":"https://example.com","limit":20}'
# firecrawl.sh crawl/<job-id> # GET job status/results (no body)
set -euo pipefail
KEY="${FIRECRAWL_API_KEY:-$(security find-generic-password -s codex-firecrawl-api-key -w 2>/dev/null || true)}"
if [ -z "$KEY" ]; then
echo "error: no Firecrawl API key found (env FIRECRAWL_API_KEY or Keychain service codex-firecrawl-api-key)" >&2
exit 1
fi
ENDPOINT="${1:?usage: firecrawl.sh <endpoint> ['<json-body>']}"
BODY="${2:-}"
if [ -n "$BODY" ]; then
curl -sS -X POST "https://api.firecrawl.dev/v2/${ENDPOINT}" \
-H "Authorization: Bearer ${KEY}" \
-H "Content-Type: application/json" \
-d "$BODY"
else
curl -sS "https://api.firecrawl.dev/v2/${ENDPOINT}" \
-H "Authorization: Bearer ${KEY}"
fi