Practical TypeScript examples for building with the AT Protocol and Bluesky.
npm install && npm run setupThe setup wizard will create your Bluesky account and configure everything automatically.
This project demonstrates powerful use cases for AT Protocol, from syncing files to building distributed systems.
π Directory Sync
Sync entire directories to the AT Protocol blob store with incremental uploads, version tracking, and web viewer.
# Upload a directory (only uploads changed files)
npm run directory-sync upload ./my-documents
# List all synced directories
npm run directory-sync list
# Download anywhere
npm run directory-sync download
# Generate web viewer
npm run web-linksFeatures:
- Incremental uploads (skip unchanged files)
- Version history via multiple records
- Bauhaus-styled web gallery viewer
- Download blobs directly from CDN
Use cases: Backups, config sharing, static site deployment, file versioning
π Markdown Sync
Write Bluesky posts and threads as markdown files, publish them, and sync replies back.
# Write a post in markdown
cat > post.md <<EOF
---
title: My Post
---
Main post content here.
---
Reply 1
---
Reply 2
EOF
# Publish to Bluesky
npm run md-sync post post.md
# Sync external replies
npm run md-sync sync post.mdFeatures:
- Draft posts offline in markdown
- Thread posts automatically
- Version control posts with git
- Fetch replies from others
- Clean files (only main post URI stored)
Use cases: Blog post threads, content planning, conversation archiving
βοΈ Job Queue
Distributed job queue using AT Protocol custom records (lexicon-based) - jobs stored in your repo without cluttering your feed!
# Terminal 1: Start a worker
npm run job-listener 10
# Terminal 2: Submit a job
echo '{"type":"echo","data":{"message":"Hello"}}' > job.json
npm run job-poster post job.json
# Check status
npm run job-poster list
# Download results and cleanup
npm run job-poster finish at://... ./resultsFeatures:
- No central server (fully distributed)
- Jobs hidden in repo (not in feed)
- Instant queries (no search indexing)
- Blob support for file results
- Extensible job handlers
- Multi-worker parallelization
- Watch multiple accounts
Use cases: Image processing, data analysis, webhooks, background tasks, distributed computing, job services
π PDS Sync
Download and unpack your entire Personal Data Server repository as JSON files.
# Download everything
npm run pds-sync
# List contents
npm run pds-sync list output/pds-exports/repo-*.car
# Unpack specific export
npm run pds-sync unpack output/pds-exports/repo-*.carFeatures:
- Complete backup of your PDS
- Organized JSON files by collection
- Download all blobs (images, etc.)
- Perfect for account migration
Use cases: Account backups, data analysis, migration, archival
π₯ Firehose Blobs
Real-time media downloading from the Bluesky firehose.
# Download all media (default: ./output/firehose)
npm run firehose-blobs
# Custom output directory
npm run firehose-blobs ./my-media
# Images only
npm run firehose-blobs -- --images-only
# First 100 files
npm run firehose-blobs -- --limit=100
# Combine options
npm run firehose-blobs ./my-media --images-only --limit=50Features:
- Real-time streaming from
wss://bsky.network - Auto-downloads images and videos
- Saves metadata for each file
- Duplicate detection
Use cases: Media archival, network monitoring, training data collection
π Firehose Filter
Subscribe to the full AT Protocol firehose and filter for specific collections (e.g., private groups records).
# Filter for ai.thefocus.groups.* records
npm run firehose-filter
# Limit to first 100 records
npm run firehose-filter -- --limit=100
# Only group definitions
npm run firehose-filter -- --groupsFeatures:
- Full firehose via
@atproto/sync - Client-side collection filtering
- JSON database with deduplication
- Auto-save every 10 records
Use cases: Custom record indexing, application-specific data collection, protocol research
β‘ Jetstream Filter
Lightweight alternative to firehose using Jetstream's JSON WebSocket API.
# Filter for ai.thefocus.groups.* records
npm run jetstream-filter
# Resume from last position
npm run jetstream-filter
# Only messages
npm run jetstream-filter -- --messagesFeatures:
- Simpler protocol - Plain JSON (no CBOR/CAR decoding)
- Server-side filtering - Collections filtered at source via
wantedCollections - Cursor support - Resume from last position using microsecond timestamps
- Auto-reconnect - Handles connection drops gracefully
- No auth required - Freely accessible public endpoints
Advantages over firehose:
- Less bandwidth and processing overhead
- Easier to consume with standard JSON tooling
- Built-in replay/resume capabilities
Use cases: Real-time indexing, lightweight monitoring, custom app data collection, protocol experimentation
π₯οΈ PDS Server
Run your own AT Protocol Personal Data Server locally without Docker.
# Setup (generates config and secrets)
cd pds-server && ./setup.sh example.test [email protected]
# Start the server
npm run pds-server
# Test it
curl http://localhost:3000/xrpc/_healthFeatures:
- Runs directly with Node.js/TypeScript
- No Docker required
- Invite codes disabled (open registration)
- Local data storage
- Full AT Protocol PDS implementation
Use cases: Local development, testing, custom PDS deployment, learning AT Protocol internals
π Website Generator
Personal blog/website generator with content stored in your ATProto PDS. Write in markdown, sync to Bluesky, generate beautiful static sites.
# Setup and configure
cd website && npm install && npm run build
npm run website view # Pull content from PDS
# Create content locally
vim content/articles/my-post.md
vim content/microposts/2025-11-11.md
# Publish to PDS
npm run website post
# Generate static site from PDS (works anywhere!)
npm run website generateFeatures:
- PDS-First Architecture - Generate site from any machine with just credentials
- Blob CDN Integration - Images stored as blobs, served from Bluesky CDN
- Keyboard Navigation - Arrow keys navigate between microposts
- Themeable Design - Mix-and-match color palettes and font systems
- Obsidian Compatible - Works with daily notes and relative paths
- Live Comments - Dynamically load Bluesky comments on posts
- Article Announcements - Auto-post to Bluesky feed when publishing
Use cases: Personal blogs, digital gardens, portfolios, documentation sites, content publishing
π€ Account Creation
Programmatically create Bluesky accounts.
npm run create-account handle.bsky.social [email protected] password123Features:
- Automated account creation
- Returns DID and tokens
- Helpful error messages
Use cases: Testing, automation, bot accounts, onboarding flows
npm install
npm run setupnpm install
cp .env.example .env
# Edit .env with your credentials:
# BLUESKY_HANDLE=your-handle.bsky.social
# BLUESKY_PASSWORD=your-passwordimport { BskyAgent } from '@atproto/api';
const agent = new BskyAgent({ service: 'https://bsky.social' });
await agent.login({ identifier: 'handle', password: 'pass' });
await agent.post({
text: 'Hello from AT Protocol!',
createdAt: new Date().toISOString(),
});import { readFileSync } from 'fs';
const imageData = readFileSync('image.png');
const upload = await agent.uploadBlob(imageData, {
encoding: 'image/png',
});
await agent.post({
text: 'Check out this image!',
embed: {
$type: 'app.bsky.embed.images',
images: [{ alt: 'My image', image: upload.data.blob }],
},
});const parent = await agent.post({ text: 'Parent post' });
await agent.post({
text: 'Reply',
reply: {
root: { uri: parent.uri, cid: parent.cid },
parent: { uri: parent.uri, cid: parent.cid },
},
});const profile = await agent.getProfile({ actor: 'user.bsky.social' });
const convo = await agent.api.chat.bsky.convo.getConvoForMembers(
{ members: [profile.data.did] },
{ headers: { 'atproto-proxy': 'did:web:api.bsky.chat#bsky_chat' } }
);
await agent.api.chat.bsky.convo.sendMessage(
{
convoId: convo.data.convo.id,
message: { text: 'Hello!' },
},
{
headers: { 'atproto-proxy': 'did:web:api.bsky.chat#bsky_chat' },
encoding: 'application/json'
}
);Records are identified by:
at://did:plc:xxx/app.bsky.feed.post/yyy
did:plc:xxx- User's Decentralized Identifierapp.bsky.feed.post- Collection typeyyy- Record key (rkey)
Binary data (images, files) stored as blobs, referenced by CID (Content Identifier).
Each user has a signed data repository containing all their records. Can be synced and exported.
This project includes comprehensive test coverage (56 passing tests):
npm test # Run all tests
npm run test:watch # Watch mode
npm run test:ui # UI modeTest files cover:
- Authentication & sessions
- Posting & threading
- Attachments & blobs
- Profiles & follows
- Direct messages
- Repository sync
- Advanced features
All tools write their output to the centralized output/ directory:
output/
βββ firehose/ # Firehose blob downloads
β βββ images/ # Downloaded images
β βββ videos/ # Downloaded videos
β βββ metadata/ # Blob metadata JSON files
βββ pds-exports/ # PDS repository exports
β βββ repo-*.car # CAR files and unpacked directories
βββ directory-sync/ # Directory sync manifests
β βββ *-manifest.json
βββ private-groups/ # Private group data (sensitive)
β βββ .group-keys.json
βββ private-groups-db.json # Firehose filter database
βββ private-groups-jetstream.json # Jetstream filter database
This directory is gitignored (except for the README). Clean it periodically to free disk space.
MIT