Composable Bluesky data filtering and monitoring CLI built with Effect
npm install @mepuka/skygentComposable Bluesky data filtering and monitoring CLI built with Effect.
Sync posts from timelines, feeds, lists, authors, and the real-time Jetstream firehose into local SQLite stores. Query, filter, derive, and export data with a powerful filter DSL and multiple output formats.
``bash`
bun add -g @mepuka/skygent
`bash`
git clone https://github.com/mepuka/skygent-bsky.git
cd skygent-bsky
bun install
bun run index.ts --help
Download a prebuilt binary from GitHub Releases, or build locally:
`bash`
bun run build:binary
./skygent --help
Cross-platform targets: linux-x64, linux-arm64, darwin-x64, darwin-arm64.
Skygent needs a Bluesky handle and app password. Credentials are resolved in this order:
1. CLI flags: --identifier and --passwordSKYGENT_IDENTIFIER
2. Environment variables: and SKYGENT_PASSWORD~/.skygent/credentials.json
3. Encrypted credential file (, requires a credentials key)
Credentials key resolution order:
1. Environment: SKYGENT_CREDENTIALS_KEY~/.skygent/credentials.key
2. Keyfile:
Manage the encrypted credential file with skygent config credentials.skygent config credentials key set|status|clear
Manage the credentials key with .
The simplest setup:
`bash`
cp .env.example .envEdit .env with your handle and app password
Bun loads .env automatically.
`bashCreate a store
skygent store create my-store
Commands
$3
| Subcommand | Description |
|---|---|
|
store create | Create a new store |
| store list | List all stores |
| store show | Show store config and metadata |
| store rename | Rename a store |
| store delete | Delete a store |
| store stats | Show store statistics |
| store summary | Summarize all stores |
| store tree | Visualize store lineage |
| store materialize | Materialize filter outputs to disk |$3
| Subcommand | Description |
|---|---|
|
sync timeline | Sync your timeline |
| sync feed | Sync a feed generator |
| sync list | Sync a list feed |
| sync author | Sync posts from an author |
| sync thread | Sync a thread (parents + replies) |
| sync notifications | Sync notifications |
| sync jetstream | Sync from Jetstream firehose |All sync commands accept
--store, --filter, --quiet, and --refresh.$3
Same subcommands as
sync, with continuous polling. Supports --interval (default: 30s).`bash
skygent watch timeline --store my-store --interval "5 minutes"
`$3
`bash
skygent query my-store --limit 25 --format table
skygent query my-store --filter 'hashtag:#ai' --sort desc --format json
skygent query my-store --range 2024-01-01T00:00:00Z..2024-01-31T00:00:00Z
skygent query my-store --fields @minimal --newest-first
skygent query my-store --fields @images --resolve-images
skygent query my-store --extract-images --format json
skygent query store-a,store-b --format ndjson
`Formats:
json, ndjson, table, markdown, compact, card, threadField presets:
@minimal, @social, @full, @images, @embeds, @media, or comma-separated field names with dot notation (use to traverse arrays, e.g. images..alt).Image options:
--extract-images, --resolve-images, --cache-images (requires images in output), --no-cache-images-thumbnails.Multi-store queries accept comma-separated store lists or repeated store arguments and include store names in output by default.
$3
Apply a filter to a source store to produce a new filtered store:
`bash
skygent derive source-store target-store --filter 'hashtag:#ai'
`Modes:
-
event-time (default) -- Pure filters only, replayable
- derive-time -- Allows effectful filters (Trending, HasValidLinks)$3
Tip: run
skygent filter help for a compact list of predicates and aliases.| Subcommand | Description |
|---|---|
|
filter create | Save a named filter |
| filter list | List saved filters |
| filter show | Show a saved filter |
| filter delete | Delete a saved filter |
| filter help | Show filter DSL and JSON help |
| filter validate | Validate a filter expression |
| filter test | Test a filter against a post |
| filter explain | Explain why a post matches |
| filter benchmark | Benchmark filter performance |
| filter describe | Describe a filter in plain text |$3
| Subcommand | Description |
|---|---|
|
search posts | Search posts locally or --network |
| search handles | Search Bluesky profiles |
| search feeds | Search feed generators |$3
| Subcommand | Description |
|---|---|
|
graph followers | List followers |
| graph follows | List follows |
| graph known-followers | Mutual followers |
| graph relationships | Relationship status |
| graph lists | Lists created by actor |
| graph list | View a list's members |
| graph blocks | Your blocked accounts |
| graph mutes | Your muted accounts |$3
| Subcommand | Description |
|---|---|
|
feed show | Show feed details |
| feed batch | Fetch multiple feeds |
| feed by | List feeds by an actor |$3
| Subcommand | Description |
|---|---|
|
post likes | Who liked a post |
| post reposted-by | Who reposted |
| post quotes | Quote posts |$3
| Subcommand | Description |
|---|---|
|
view thread | Display a thread |
| view status | Check if a derived view is stale |$3
`bash
skygent config check # Run health checks
`Filter DSL
Filters are passed via
--filter (DSL string) or --filter-json (JSON AST).$3
| Filter | Example |
|---|---|
|
hashtag:#tag | Match posts with hashtag |
| author:handle.bsky.social | Match posts by author |
| contains:"text" | Text search (case-insensitive by default) |
| regex:/pattern/i | Regex match |
| language:en,es | Match languages |
| date: | Date range (ISO 8601) |
| engagement:minLikes=100 | Engagement thresholds |
| is:reply | Post type (reply, quote, repost, original) |
| has:images | Media presence (images, video, links, media, embed) |
| @saved-name | Reference a saved filter |$3
from: = author:, tag: = hashtag:, text: = contains:, lang: = language:$3
authorin:alice,bob,charlie and hashtagin:#ai,#ml,#dl$3
`
hashtag:#ai AND author:user.bsky.social
hashtag:#ai OR hashtag:#ml
NOT hashtag:#spam
(hashtag:#ai OR hashtag:#ml) AND engagement:minLikes=10
`Operators:
AND / &&, OR / ||, NOT / !, parentheses for grouping.Configuration
$3
| Variable | Default | Description |
|---|---|---|
|
SKYGENT_IDENTIFIER | -- | Bluesky handle or DID |
| SKYGENT_PASSWORD | -- | App password |
| SKYGENT_CREDENTIALS_KEY | -- | Master key for encrypted credential storage (overrides ~/.skygent/credentials.key) |
| SKYGENT_SERVICE | https://bsky.social | Bluesky service URL |
| SKYGENT_STORE_ROOT | ~/.skygent | Root storage directory |
| SKYGENT_OUTPUT_FORMAT | ndjson | Default output format |
| SKYGENT_BSKY_RATE_LIMIT | 250 millis | Min delay between API calls |
| SKYGENT_BSKY_RETRY_MAX | 5 | Max retry attempts |
| SKYGENT_SYNC_CONCURRENCY | 5 | Concurrent sync workers |$3
-
--full -- Use verbose JSON output (compact is the default)
- --quiet -- Suppress progress output
- --log-format json|human -- Control log formatArchitecture
Skygent is built entirely on Effect with a layered service architecture:
- Domain (
src/domain/) -- Data models for posts, stores, filters, events, and derivations using Effect Schema
- Services (src/services/) -- Business logic: Bluesky API client, SQLite store, sync engine, filter runtime, derivation engine
- CLI (src/cli/) -- Command definitions, output formatting, error handlingStores are local SQLite databases with an append-only event log. Derivations track lineage between stores and support incremental processing with checkpoints.
Security
- Passwords are handled as
Redacted values and never logged
- Encrypted credential storage uses AES-GCM with PBKDF2 (100,000 iterations)
- Avoid putting passwords in config files; use environment variables or the credential storeDocumentation
Detailed docs are in
docs/`:- Getting Started
- CLI Reference
- Filters
- Configuration
- Credentials
- Stores
- Output Formats
MIT