Browse Bluesky content via API and firehose - search posts, fetch user activity, sample trending topics, read feeds and lists, analyze and categorize accounts. Supports authenticated access for personalized feeds. Use for Bluesky research, user monitoring, trend analysis, feed reading, firehose sampling, account categorization.
npx skills add https://github.com/oaustegard/claude-skills --skill browsing-bluesky
Access Bluesky content through public APIs and real-time firehose. Supports optional authentication for personalized feeds. Includes account analysis for categorization.
Add skill directory to path and import:
import sys
sys.path.insert(0, '/path/to/skills/browsing-bluesky') # or use .claude/skills symlink path
from browsing_bluesky import (
# Core browsing
search_posts, get_user_posts, get_profile, get_feed_posts, sample_firehose,
get_thread, get_quotes, get_likes, get_reposts,
get_followers, get_following, search_users,
# Trending
get_trending, get_trending_topics,
# Account analysis
get_all_following, get_all_followers, extract_post_text,
extract_keywords, analyze_account, analyze_accounts,
# Authentication utilities
is_authenticated, get_authenticated_user, clear_session
)
Authentication enables personalized feeds (like Paper Skygest) that require knowing who's asking.
export BSKY_HANDLE="yourhandle.bsky.social"
export BSKY_APP_PASSWORD="xxxx-xxxx-xxxx-xxxx"
if is_authenticated():
print(f"Logged in as: {get_authenticated_user()}")
else:
print("Using public access")
# Clear session if needed (e.g., switching accounts)
clear_session()
Use search_posts() with query syntax matching bsky.app advanced search:
event sourcing"event sourcing"from:acairns.co.uk or use author= paramsince:2025-01-01 or use since= param#python mentions:user domain:github.comCombine query syntax with function params for complex searches.
get_profile(handle) for context (bio, follower count, post count)get_user_posts(handle, limit=N)search_posts(query, author=handle)Recommended workflow — trending API first, firehose for deep dives:
topics = get_trending_topics(limit=10)
# Returns: {topics: [{topic, display_name, description, link}, ...],
# suggested: [...]}
trends = get_trending(limit=10)
for t in trends:
print(f"{t['display_name']} — {t['post_count']} posts ({t['status']})")
# Each trend includes: topic, display_name, link, started_at,
# post_count, status, category, actors
posts = search_posts(trend["topic"], limit=25)
Prerequisites: Install Node.js dependencies once per session:
cd /home/claude && npm install ws https-proxy-agent 2>/dev/null
data = sample_firehose(duration=30) # Full firehose sample
data = sample_firehose(duration=20, filter="python") # Filtered sample
Returns dict with keys:
{startTime, endTime, durationSeconds} — sampling time range{totalReceived, totalPosts, postsPerSecond, filter, languages} — volume metrics and language breakdown[[word, count], ...] — top 50 words (count >= 3)[[bigram, count], ...] — top 30 bigrams (count >= 2)[[trigram, count], ...] — top 20 trigrams (count >= 2)[[entity, count], ...] — top 25 handles/hashtags (count >= 2)[{text, altTexts, hasImages}, ...] — first 50 matching postsget_feed_posts() accepts:
https://bsky.app/profile/austegard.com/lists/3lankcdrlip2fhttps://bsky.app/profile/did:plc:xxx/feed/feednameat://did:plc:xxx/app.bsky.graph.list/xyzThe function extracts the AT-URI from URLs automatically.
Fetch full thread context for a post with parents and replies:
thread = get_thread("https://bsky.app/profile/user/post/xyz", depth=10)
# Returns: {post: {...}, parent: {...}, replies: [...]}
Discover posts that quote a specific post:
quotes = get_quotes("https://bsky.app/profile/user/post/xyz")
for q in quotes:
print(f"@{q['author_handle']}: {q['text'][:80]}")
Get users who engaged with a post:
likes = get_likes(post_url)
reposts = get_reposts(post_url)
# Accepts both URLs and AT-URIs
likes = get_likes("at://did:plc:.../app.bsky.feed.post/...")
Every parsed post carries an images field — a list of
{alt, url, transcription} dicts, one per embed image. The legacy
image_alts: list[str] field is preserved (non-empty alts only).
When alt text is *missing* and the image content matters, opt in to model
transcription via the transcribe parameter on any post-fetch function
(get_user_posts, search_posts, get_feed_posts, get_thread,
get_quotes):
# Routine/bulk work (zeitgeist, inbox review, news scans) —
# gemini-2.5-flash-lite is the recommended default. Cheapest production
# model anywhere ($0.10/$0.40 per 1M tokens), ~95% accuracy on dense
# screenshots in May 2026 benchmarks:
posts = get_user_posts("ayourtch.bsky.social", limit=40, transcribe="gemini-lite")
# Token-perfect transcription, still cheap:
posts = get_user_posts(..., transcribe="gemini-flash")
# Frontier model with thinking_level=minimal — for cases where the image
# content needs reasoning, not just transcription:
posts = get_user_posts(..., transcribe="gemini-3.5-flash")
# Anthropic single-vendor option (note: empirically weaker prompt-following
# than Gemini on dense transcription — Haiku tends to summarize rather
# than transcribe):
posts = get_user_posts(..., transcribe="haiku")
# Interactive sessions where image is part of the active task and you want
# conversation context to inform interpretation (only available on Anthropic):
thread = get_thread(post_url, transcribe="opus")
# Default (no transcription) — current behavior preserved:
posts = get_user_posts("ayourtch.bsky.social", limit=40)
Policy is invariant across all callers: images with non-empty alt text are
never transcribed (the author already described the image; trust it).
Only images with missing or empty alt are sent to the model. Network or
API failures leave transcription as None; callers degrade silently.
Cost/quality empirics (May 2026, n=3 dense terminal screenshots, single
run each — sample size is small, treat as directional):
| Alias | Latency | $/image | Chord-token recall |
|---|---|---|---|
| gemini-lite | ~8s | ~$0.001 | 95% |
| gemini-flash | ~10s | ~$0.003 | 100% |
| gemini-3.5-flash | ~10s | ~$0.014 | 100% |
| haiku | ~7s | ~$0.008 | 18% (summarizes) |
| opus | ~20s | ~$0.12 | 91% |
Requires either ANTHROPIC_API_KEY (or API_KEY in /mnt/project/claude.env)
for the haiku / opus aliases, or CF AI Gateway credentials in
/mnt/project/proxy.env for the gemini-* aliases. Transcription only
fires when the parameter is set, so callers without the relevant
credentials can simply pick a different alias or leave the feature off.
Navigate follower/following relationships:
followers = get_followers("handle.bsky.social")
following = get_following("handle.bsky.social")
# Returns list of actor dicts with handle, display_name, did, description, etc.
Search for users by name, handle, or bio:
users = search_users("machine learning researcher")
for u in users:
print(f"{u['display_name']} (@{u['handle']}): {u['description'][:100]}")
https://api.bsky.app/xrpc/ for unauthenticated readshttps://bsky.social/xrpc/ for authenticated requestsapp.bsky.unspecced.getTrends (rich) and app.bsky.unspecced.getTrendingTopics (lightweight)wss://jetstream1.us-east.bsky.network/subscribeAll API functions return structured dicts with:
uri: AT protocol identifiertext: Post contentcreated_at: ISO timestampauthor_handle: User handleauthor_name: Display namelikes, reposts, replies: Engagement countslinks: Full URLs extracted from post facets (post text truncates URLs with "...")image_alts: Alt text from embedded imagesurl: Direct link to post on bsky.appProfile function returns: handle, display_name, description, followers, following, posts, did
Analyze accounts for categorization by topic. Fetches profile and posts, extracts keywords, and returns structured data for Claude to categorize.
# Analyze accounts you follow
results = analyze_accounts(following="yourhandle.bsky.social", limit=50)
# Analyze your followers
results = analyze_accounts(followers="yourhandle.bsky.social", limit=50)
# Analyze specific handles
results = analyze_accounts(handles=["user1.bsky.social", "user2.bsky.social"])
analysis = analyze_account("user.bsky.social")
# Returns: {handle, display_name, description, keywords, post_count, followers, following}
Stopwords parameter filters domain-specific noise:
"en": English (general purpose, default)"ai": AI/ML domain (filters tech boilerplate)"ls": Life Sciences (filters research methodology)results = analyze_accounts(following="handle", stopwords="ai")
Requires: extracting-keywords skill with YAKE venv for keyword extraction.
results = analyze_accounts(
following="handle",
exclude_patterns=["bot", "spam", "promo"] # Skip accounts matching these
)
For large account lists beyond the 100 limit of get_following/get_followers:
all_following = get_all_following("handle", limit=500) # Handles pagination
all_followers = get_all_followers("handle", limit=500)
Each analyzed account returns:
{
"handle": "user.bsky.social",
"display_name": "User Name",
"description": "Bio text here",
"keywords": ["keyword1", "keyword2", "keyword3"],
"post_count": 20,
"followers": 1234,
"following": 567
}
Claude uses bio + keywords to categorize accounts by topic without hardcoded rules
Assists in writing high-quality content by conducting research, adding citations, improving hooks, iterating on outlines, and providing real-time feedback on each section. Transforms your writing process from solo effort to collaborative partnership.
Identifies high-quality leads for your product or service by analyzing your business, searching for target companies, and providing actionable contact strategies. Perfect for sales, business development, and marketing professionals.
Use this skill to query your Google NotebookLM notebooks directly from Claude Code for source-grounded, citation-backed answers from Gemini. Browser automation, library management, persistent auth. Drastically reduced hallucinations through document-only responses.
Efficient database search tool for bioRxiv preprint server. Use this skill when searching for life sciences preprints by keywords, authors, date ranges, or categories, retrieving paper metadata, downloading PDFs, or conducting literature reviews.
Query and analyze scholarly literature using the OpenAlex database. This skill should be used when searching for academic papers, analyzing research trends, finding works by authors or institutions, tracking citations, discovering open access publications, or conducting bibliometric analysis across 240M+ scholarly works. Use for literature searches, research output analysis, citation analysis, and academic database queries.
Access USPTO APIs for patent/trademark searches, examination history (PEDS), assignments, citations, office actions, TSDR, for IP analysis and prior art searches.
Multiagent AI system for scientific research assistance that automates research workflows from data analysis to publication. This skill should be used when generating research ideas from datasets, developing research methodologies, executing computational experiments, performing literature searches, or generating publication-ready papers in LaTeX format. Supports end-to-end research pipelines with customizable agent orchestration.
Automated LLM-driven hypothesis generation and testing on tabular datasets. Use when you want to systematically explore hypotheses about patterns in empirical data (e.g., deception detection, content analysis). Combines literature insights with data-driven hypothesis testing. For manual hypothesis formulation use hypothesis-generation; for creative ideation use scientific-brainstorming.
Take oaustegard/browsing-bluesky from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npm.
Without those the skill loads but fails at the first command.