Install
$ agentstack add skill-david-vrba-claude-power-skills-reel ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ● Filesystem access Used
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Instagram Reel Intelligence Skill
Analyze a single Instagram Reel from every available signal and produce a direct, opinionated action report.
Arguments: [reel-url] [-l | -h]
-hor no flag → High mode (deep analysis). Run the HIGH MODE section below.-l→ Low mode (fast analysis). Run the LOW MODE section below.
Only single-post URLs are supported. /reel/ and /p/ are both valid; profile pages (/@, /channel/, /c/) are not — if given one, stop and say: > "This skill is for single reels only. Please provide a direct reel URL (e.g. instagram.com/reel/XXXXX/)."
Prerequisites: yt-dlp, ffmpeg (frames), faster-whisper (audio transcription), plus Firecrawl, Tavily, and Playwright MCP tools if available. Each step degrades gracefully when a tool is missing.
Output location: reports are saved under research/reels/ in your working directory (change the path to suit your setup). Scratch files live in research/reels/.tmp/ and are deleted at the end.
HIGH MODE (-h, default)
Full multi-signal analysis. Downloads the reel via yt-dlp, transcribes spoken audio with Whisper, extracts frames with ffmpeg, fetches comments, and captures a Playwright visual.
Phase 1: Check & Update yt-dlp
command -v yt-dlp
If not found, install:
- Windows (winget):
winget install yt-dlp.yt-dlp - Windows (pip fallback):
pip install yt-dlp - macOS:
brew install yt-dlp - Linux:
pip3 install yt-dlp
yt-dlp -U
Phase 2: Detect Channel/Profile URLs
Check URL for /@, /channel/, /c/. If it is a profile page, stop with the single-reel message above. /reel/ and /p/ are allowed.
Phase 3: yt-dlp Download (Primary Source)
Step 0 — Pin the working directory. All reel_session.* / reel_frame_*.jpg scratch files are written here and globbed/cleaned up from here in later phases, so every phase must share one known CWD. Run this first and stay here:
mkdir -p "research/reels/.tmp"
cd "research/reels/.tmp"
Authentication: a cookies.txt file, not the live browser. Do NOT use --cookies-from-browser chrome — since Chrome 127 (mid-2024) Windows Chrome encrypts cookies with app-bound encryption that no external process can decrypt, and Chrome locks its cookie DB while running. The reliable path is an exported cookies.txt, which works with the browser open and needs no DB access. To create one: in a browser logged into Instagram, use the "Get cookies.txt LOCALLY" extension to export instagram.com cookies to ~/.claude/secrets/instagram_cookies.txt.
Run as a single bash block — it uses the cookie file if present, else goes cookieless, and reports which path it took:
COOKIES_FILE="$HOME/.claude/secrets/instagram_cookies.txt"
if [ -s "$COOKIES_FILE" ]; then
echo "[reel] using cookies.txt (authenticated)"
yt-dlp \
--cookies "$COOKIES_FILE" \
--write-comments \
--write-info-json \
--output "reel_session" \
"REEL_URL"
else
echo "[reel] no cookies file — running cookieless (comments limited, no private reels)"
yt-dlp \
--write-comments \
--write-info-json \
--output "reel_session" \
"REEL_URL"
fi
--cookies passes an Instagram session to yt-dlp, bypassing login walls and unlocking real comment data + like counts. --write-comments reads comments into the info JSON — it does not post anything. It is present in both branches, so cookieless still attempts whatever comment data Instagram returns unauthenticated.
After the run, react to what happened — tell the user at most once, don't block:
- No cookies file existed (cookieless branch ran): say once → "No Instagram cookies file found, so comments are limited and private reels are inaccessible. To fix permanently (works with the browser open): export instagram.com cookies once with the 'Get cookies.txt LOCALLY' extension to
~/.claude/secrets/instagram_cookies.txt." - Cookies file existed but yt-dlp errored with a login/auth wall (
login required,rate-limit,Restricted Video, or an empty/zero-comment result on a clearly popular reel): cookies are likely stale. Say once → "Your Instagram cookies look stale — re-export them; Instagram wants a fresh session." - Success: no message needed.
If yt-dlp fails entirely (private reel, geo-blocked, unavailable): note it and continue — Phase 8 (Playwright) becomes the primary source.
Check for success: Glob for reel_session*.info.json. If found, Phase 3 succeeded.
Phase 4: Parse Metadata + Comments
Only run if reel_session*.info.json exists. Run as a single bash block:
PYTHON=$(command -v python3 2>/dev/null || command -v python 2>/dev/null)
PYTHONUTF8=1 $PYTHON -c "
import json, glob, sys, html
files = glob.glob('reel_session*.info.json')
if not files:
print('ERROR: info JSON not found')
sys.exit(1)
with open(files[0], encoding='utf-8') as f:
info = json.load(f)
print('Title: ' + str(info.get('title', 'Unknown')))
print('Uploader: ' + str(info.get('uploader', 'Unknown')))
print('Duration: ' + str(info.get('duration_string', 'Unknown')))
print('Duration_secs: ' + str(info.get('duration', 30)))
print('Like_count: ' + str(info.get('like_count', 'Unknown')))
print('View_count: ' + str(info.get('view_count', 'Unknown')))
print()
print('=== DESCRIPTION ===')
desc = (info.get('description') or '').strip()
if desc:
print(html.unescape(desc[:1000]))
if len(desc) > 1000:
print('...[truncated]')
else:
print('[No description]')
comments = info.get('comments') or []
print()
# Engagement-bait detection: many creators run a 'comment WORD for the link' DM funnel,
# which floods comments with one repeated trigger word. That is a marketing mechanic,
# not organic sentiment — flag it so the report doesn't misread it as community reaction.
from collections import Counter
texts = [html.unescape((c.get('text') or '').strip()) for c in comments]
short = [t.lower() for t in texts if t and len(t.split()) = 8 and top_n / len(comments) >= 0.35:
pct = round(100 * top_n / len(comments))
print('=== ENGAGEMENT-BAIT FLAG ===')
print(f'~{pct}% of captured comments are the repeated trigger \"{top_text}\" — this is a '
f'\"comment X for the link\" DM-funnel mechanic, NOT organic sentiment. Treat with skepticism.')
print()
# Without auth, IG returns comment text but every like_count is 0, so a
# 'top comments' sort is meaningless. Detect that and label honestly.
have_likes = any((c.get('like_count') or 0) > 0 for c in comments)
print('=== COMMENTS ===' if have_likes else '=== COMMENTS (unranked — like counts unavailable without login) ===')
if comments:
sorted_c = sorted(comments, key=lambda c: c.get('like_count', 0) or 0, reverse=True)[:20]
for c in sorted_c:
author = c.get('author', 'Unknown')
text = html.unescape((c.get('text') or '').strip())
likes = c.get('like_count', 0) or 0
if not text:
continue
if have_likes:
print('[' + str(likes) + ' likes] ' + author + ': ' + text[:300])
else:
print(author + ': ' + text[:300])
else:
print('[Not available via yt-dlp — Playwright DOM snapshot will attempt to capture visible comments]')
" > reel_session_meta.txt
Read reel_session_meta.txt with the Read tool. If it contains ERROR: info JSON not found, Phase 3 failed — skip Phases 5 and 6.
Phase 5: Extract Frames
Only run if reel_session*.info.json exists. Run as a single bash block:
PYTHON=$(command -v python3 2>/dev/null || command -v python 2>/dev/null)
VIDEOFILE=$(ls reel_session.* 2>/dev/null | grep -Ev '\.(json|txt|jpg|webp|png)$' | head -1)
if [ -z "$VIDEOFILE" ]; then
echo "No video file found — skipping frame extraction" > reel_session_frames.txt
exit 0
fi
DURATION=$(PYTHONUTF8=1 $PYTHON -c "
import json, glob
files = glob.glob('reel_session*.info.json')
if files:
d = json.load(open(files[0], encoding='utf-8')).get('duration', 30)
print(int(d) if d else 30)
else:
print(30)
")
if command -v ffmpeg &>/dev/null; then
for PCT in 0 25 50 75; do
TS=$(( DURATION * PCT / 100 ))
ffmpeg -ss "$TS" -i "$VIDEOFILE" -frames:v 1 -q:v 2 "reel_frame_${PCT}.jpg" -y 2>/dev/null
done
echo "Frames extracted at 0% 25% 50% 75%" > reel_session_frames.txt
else
echo "ffmpeg not available — install ffmpeg to enable frame extraction" > reel_session_frames.txt
fi
Phase 6: Whisper Transcription
Only run if a video file exists (Phase 3 succeeded). Uses faster-whisper — runs on GPU (CUDA) when available and falls back to CPU automatically.
Step 1 — Install check (do this BEFORE running the transcription block). Run PYTHONUTF8=1 $PYTHON -c "import faster_whisper". If it succeeds, go to Step 2. If it fails, ask the user: > "faster-whisper is not installed — the first install pulls the package (and, for GPU use, the CUDA runtime). Proceed?"
If confirmed, install: $PYTHON -m pip install --user faster-whisper. For NVIDIA GPU acceleration, also install nvidia-cublas-cu12 nvidia-cudnn-cu12==9.*. If declined, skip Phase 6 entirely and note "Audio transcription declined" in the report.
Step 2 — Transcribe. Run as a single bash block:
PYTHON=$(command -v python3 2>/dev/null || command -v python 2>/dev/null)
VIDEOFILE=$(ls reel_session.* 2>/dev/null | grep -Ev '\.(json|txt|jpg|webp|png)$' | head -1)
if [ -z "$VIDEOFILE" ]; then
echo "No video file — Whisper skipped" > reel_session_transcript.txt
exit 0
fi
if ! PYTHONUTF8=1 $PYTHON -c "import faster_whisper" 2>/dev/null; then
echo "faster-whisper not installed — run: $PYTHON -m pip install --user faster-whisper" > reel_session_transcript.txt
exit 0
fi
PYTHONUTF8=1 $PYTHON -c "
import sys
from faster_whisper import WhisperModel
audio, out = sys.argv[1], sys.argv[2]
try:
model = WhisperModel('small', device='cuda', compute_type='float16')
except Exception:
model = WhisperModel('small', device='cpu', compute_type='int8')
segments, info = model.transcribe(audio, vad_filter=True)
with open(out, 'w', encoding='utf-8') as f:
for seg in segments:
t = seg.text.strip()
if t:
f.write(t + '\n')
" "$VIDEOFILE" reel_session_transcript.txt || echo "Transcription failed" > reel_session_transcript.txt
Read reel_session_transcript.txt with the Read tool.
If the transcript is empty or whitespace-only, that is correct behavior for a silent or music-only reel — Whisper detected no speech. Do not treat it as an error. Carry [Silent reel — background music only, no speech detected] into the report's Raw Captures → Spoken transcript section.
Phase 7: Firecrawl Scrape — SKIP for Instagram
Firecrawl categorically blocks Instagram (We do not support this site) — a policy block, not a transient error. Skip this phase entirely and do not spend a tool call on it. The caption/links/account it would have provided are recovered from the yt-dlp description (Phase 4) and the Playwright DOM snapshot (Phase 8).
(If this skill is ever pointed at a non-Instagram host, or Firecrawl later adds Instagram support, re-enable a Firecrawl scrape with formats: ["markdown","links"], onlyMainContent: false, extracting caption / hashtags / mentioned links / account name.)
Phase 8: Playwright Visual Capture — ALWAYS RUN
This phase is mandatory on every high-mode analysis. Do NOT skip it because yt-dlp already returned comments. The screenshot and DOM caption captured here are independent signals (they confirm what the frames show and catch on-screen text the frames missed). The only reason to skip is if Playwright is unavailable.
- Navigate to the reel URL with
browser_navigate. - Wait for page load with
browser_wait_for— selectorvideo, timeout 3000ms. Prevents a blank screenshot on Instagram's JS-heavy load. - Check for overlays with
browser_snapshot. If a cookie consent dialog or modal is present ("Allow all cookies", "Accept", "Decline optional cookies"), dismiss it withbrowser_click. This snapshot also yields the caption / engagement counts — keep it. - Screenshot with
browser_take_screenshot. The image is returned inline — analyze it in Phase 9. - Close browser with
browser_close— but only after Phase 8b, if you run it. Keep the page open if you will run 8b.
If a login wall is shown: extract whatever text is visible (preview frame, account name, captions) and note it.
Phase 8b: Comment Recovery via Playwright — OPTIONAL
Run only when the yt-dlp comments from Phase 4 are absent or weak — no comments, or only the engagement-bait flood, or comments with no like counts when you want real community reaction. If Phase 4 already gave solid ranked comments, skip 8b. Skipping 8b does not skip the screenshot — that already happened in Phase 8.
Keep the browser from Phase 8 open. Instagram lazy-loads comments, so the Step 3 snapshot has caption + counts but zero comment text. To capture them:
- In the Step 3 snapshot, find the comment button (labeled like "Comment" with the count).
browser_clickit. browser_wait_for~2s for the panel to render.- Take a scoped snapshot to avoid the ~1,200-line full-feed dump:
browser_snapshotwithtargetset to the comment dialog/list container ref (or passdepth: 8to cap tree size). Instagram auto-loads unrelated reels below the target; scoping keeps the snapshot to the active reel. - To pull more than the first few comments, scroll the panel (
browser_press_keyPageDown, orbrowser_evaluateto scroll the container), then snapshot again. One extra scroll is usually enough.
Extract comment text from these scoped snapshots. Caveat: if the panel renders to canvas rather than accessible DOM, note "comments visible in screenshot but not extractable from DOM" and move on. Then close the browser.
Phase 9: Analyze Frames
Glob for reel_frame_*.jpg. If frames exist, Read each in order: reel_frame_0.jpg (opening), 25, 50, 75 (near end). Also analyze the Playwright screenshot from Phase 8.
For each frame, extract: all visible text overlays (on-screen captions, subtitles, titles); any tool/product/website names shown; any URLs or handles; the visual subject; any UI, code, or before/after being shown.
If no frames exist (ffmpeg unavailable), the Playwright screenshot is the only visual.
Phase 10: Web Research
From Phases 4–9, compile every tool, product, website, or concept identified. For each significant one (skip generic words), use WebSearch (or Tavily/Firecrawl search if available):
- What it is / what it does
- Pricing or access model (free / paid / open-source)
- Relevance to the reader's workflow and developer productivity
Limit to 1 search per item.
Phase 11: Synthesize → Action Report
Combine all signals: transcript, comments, caption, visual frames, web research. Be direct and opinionated. Write for a technical reader who values speed and signal over completeness — judge relevance against their actual tools and workflow.
## What this is about
[1 sentence — the reel's topic and the specific angle or claim being made]
## The core idea
[2–4 sentences — what's being shown or recommended, and why it's framed as interesting. Draw from the transcript if available — spoken words are usually more specific than captions.]
## What was identified
- **Tool / Product / Concept**: [name] — [what it is, one line]
- [repeat for each significant item]
- **Source account**: [Instagram handle if known]
## What people are saying
[Only include if comments were found. Summarize sentiment — is the community validating the claim, calling it out, sharing results? Include 1–2 direct quotes if useful. If no comments, omit this section.]
## Why this is interesting
[Honest take: what makes t
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [david-vrba](https://github.com/david-vrba)
- **Source:** [david-vrba/claude-power-skills](https://github.com/david-vrba/claude-power-skills)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.