claude up some atproto stuff

initial release — AT Protocol development toolkit for Claude Code

skills for Microcosm services (Constellation, Spacedust, Slingshot, UFOs),
bundled MCP servers (pdsx for record CRUD, atproto-mcp for docs search),
and common app-building patterns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+709
+14
.claude-plugin/marketplace.json
··· 1 + { 2 + "name": "protopack", 3 + "owner": { 4 + "name": "nate", 5 + "url": "https://bsky.app/profile/zzstoatzz.io" 6 + }, 7 + "plugins": [ 8 + { 9 + "name": "protopack", 10 + "description": "AT Protocol development toolkit — Microcosm services, pdsx record operations, and atproto documentation.", 11 + "source": "./" 12 + } 13 + ] 14 + }
+15
.claude-plugin/plugin.json
··· 1 + { 2 + "name": "protopack", 3 + "version": "0.1.0", 4 + "description": "AT Protocol development toolkit — Microcosm services, pdsx record operations, and atproto documentation at your fingertips.", 5 + "author": { 6 + "name": "nate", 7 + "url": "https://bsky.app/profile/zzstoatzz.io" 8 + }, 9 + "repository": "https://tangled.org/zzstoatzz.io/protopack", 10 + "homepage": "https://microcosm.blue", 11 + "license": "MIT", 12 + "keywords": ["atproto", "bluesky", "microcosm", "pdsx", "decentralized"], 13 + "skills": "./skills/", 14 + "mcpServers": "./.mcp.json" 15 + }
+12
.mcp.json
··· 1 + { 2 + "mcpServers": { 3 + "pdsx": { 4 + "command": "uvx", 5 + "args": ["--from", "pdsx[mcp]", "pdsx-mcp"] 6 + }, 7 + "atproto": { 8 + "command": "uvx", 9 + "args": ["atproto-mcp"] 10 + } 11 + } 12 + }
+54
README.md
··· 1 + # protopack 2 + 3 + a claude code plugin for building AT Protocol applications. 4 + 5 + ## install 6 + 7 + ```bash 8 + /plugin marketplace add https://tangled.org/zzstoatzz.io/protopack.git 9 + /plugin install protopack 10 + ``` 11 + 12 + that's it. no cloning, no setup. the bundled MCP servers run via `uvx` automatically. 13 + 14 + ## what's included 15 + 16 + ### skills 17 + 18 + | skill | what it does | 19 + |-------|-------------| 20 + | `/protopack:constellation` | query the global backlink index (likes, reposts, follows, replies, quotes) | 21 + | `/protopack:spacedust` | real-time interaction streaming via WebSocket | 22 + | `/protopack:slingshot` | fast record fetching and identity resolution | 23 + | `/protopack:ufos` | lexicon timeseries statistics and ecosystem discovery | 24 + | `/protopack:app-patterns` | common patterns for building atproto apps | 25 + 26 + ### bundled MCP servers 27 + 28 + | server | what it does | install method | 29 + |--------|-------------|----------------| 30 + | [pdsx](https://github.com/zzstoatzz/pdsx) | AT Protocol record CRUD (list, get, create, update, delete) | `uvx` from PyPI | 31 + | [atproto-mcp](https://github.com/ashex/atproto-mcp) | searchable atproto docs, lexicon schemas, cookbook examples | `uvx` from PyPI | 32 + 33 + ### microcosm services 34 + 35 + all unauthenticated and free to use. 36 + 37 + | service | url | purpose | 38 + |---------|-----|---------| 39 + | constellation | constellation.microcosm.blue | global backlink index | 40 + | spacedust | spacedust.microcosm.blue | real-time interaction firehose | 41 + | slingshot | slingshot.microcosm.blue | record/identity cache | 42 + | ufos | ufos-api.microcosm.blue | lexicon timeseries stats | 43 + 44 + ## local development 45 + 46 + ```bash 47 + claude --plugin-dir ./protopack 48 + ``` 49 + 50 + ## credits 51 + 52 + - [microcosm.blue](https://microcosm.blue) — AT Protocol infrastructure by [@bad-example.com](https://bsky.app/profile/bad-example.com) 53 + - [pdsx](https://github.com/zzstoatzz/pdsx) — AT Protocol record operations 54 + - [atproto-mcp](https://github.com/ashex/atproto-mcp) — AT Protocol documentation search
+176
skills/app-patterns/SKILL.md
··· 1 + --- 2 + name: app-patterns 3 + description: Common patterns for building AT Protocol applications with Microcosm services. Use when scaffolding a new atproto app, adding engagement features, or combining Microcosm services together. 4 + user-invocable: true 5 + --- 6 + 7 + # app patterns — building with microcosm 8 + 9 + This skill covers common patterns for combining Microcosm services to build AT Protocol applications. 10 + 11 + ## the compose pattern 12 + 13 + Most atproto apps combine three Microcosm services: 14 + 15 + 1. **Constellation** — query historical data ("how many likes?", "who follows this user?") 16 + 2. **Slingshot** — hydrate references into full records and resolve identities 17 + 3. **Spacedust** — stream real-time updates to keep the UI live 18 + 19 + ``` 20 + [Constellation] → backlink counts/lists 21 + 22 + [Slingshot] → hydrate AT-URIs → full records + identities 23 + 24 + [Spacedust] → WebSocket stream → live updates 25 + ``` 26 + 27 + ## pattern: engagement counters 28 + 29 + Display like/repost/reply counts on a post, updated in real time. 30 + 31 + ```javascript 32 + // 1. get initial counts from Constellation 33 + const base = "https://constellation.microcosm.blue/xrpc"; 34 + const counts = await fetch( 35 + `${base}/blue.microcosm.links.getManyToManyCounts?` + 36 + `subjects=${encodeURIComponent(postUri)}` + 37 + `&sources=app.bsky.feed.like:subject.uri` + 38 + `&sources=app.bsky.feed.repost:subject.uri` 39 + ).then(r => r.json()); 40 + 41 + // 2. subscribe to real-time updates via Spacedust 42 + const ws = new WebSocket( 43 + `wss://spacedust.microcosm.blue/subscribe?` + 44 + `wantedSubjects=${encodeURIComponent(postUri)}` + 45 + `&wantedSources=app.bsky.feed.like:subject.uri` + 46 + `&wantedSources=app.bsky.feed.repost:subject.uri` 47 + ); 48 + ws.onmessage = (event) => { 49 + const { link } = JSON.parse(event.data); 50 + // increment/decrement counters based on link.operation and link.source 51 + }; 52 + ``` 53 + 54 + ## pattern: highlight reel 55 + 56 + Show a user's top posts ranked by engagement (like highlights.waow.tech). 57 + 58 + ```python 59 + import httpx 60 + 61 + slingshot = "https://slingshot.microcosm.blue/xrpc" 62 + constellation = "https://constellation.microcosm.blue/xrpc" 63 + 64 + # 1. resolve identity 65 + identity = httpx.get( 66 + f"{slingshot}/com.bad-example.identity.resolveMiniDoc", 67 + params={"identifier": handle}, 68 + ).json() 69 + 70 + # 2. get user's posts (via public Bluesky API or pdsx) 71 + # 3. for each post, get engagement counts from Constellation 72 + for post_uri in post_uris: 73 + counts = httpx.get( 74 + f"{constellation}/blue.microcosm.links.getManyToManyCounts", 75 + params={ 76 + "subjects": post_uri, 77 + "sources": [ 78 + "app.bsky.feed.like:subject.uri", 79 + "app.bsky.feed.repost:subject.uri", 80 + "app.bsky.feed.post:embed.record.uri", # quotes 81 + ], 82 + }, 83 + ).json() 84 + # 4. score and rank posts 85 + ``` 86 + 87 + ## pattern: notifications service 88 + 89 + React to interactions with a user's content in real time (like Microcosm's notifications.microcosm.blue). 90 + 91 + ```python 92 + import asyncio 93 + import websockets 94 + import json 95 + 96 + async def notification_service(user_dids: list[str]): 97 + url = "wss://spacedust.microcosm.blue/subscribe?" + "&".join( 98 + f"wantedSubjectDids={did}" for did in user_dids 99 + ) 100 + async with websockets.connect(url) as ws: 101 + async for message in ws: 102 + event = json.loads(message) 103 + if event["kind"] == "link": 104 + link = event["link"] 105 + # resolve who did it 106 + source_did = link["source_record"].split("/")[2] 107 + # send notification to the affected user 108 + # (the DID in the subject) 109 + ``` 110 + 111 + ## pattern: identity-first fetch 112 + 113 + Almost every atproto operation starts with resolving a handle to a DID. Use Slingshot for this. 114 + 115 + ```python 116 + import httpx 117 + 118 + def resolve_and_fetch(handle: str, collection: str, rkey: str): 119 + slingshot = "https://slingshot.microcosm.blue/xrpc" 120 + 121 + # resolve handle → DID + PDS 122 + identity = httpx.get( 123 + f"{slingshot}/com.bad-example.identity.resolveMiniDoc", 124 + params={"identifier": handle}, 125 + ).json() 126 + 127 + # fetch record from cache 128 + record = httpx.get( 129 + f"{slingshot}/com.atproto.repo.getRecord", 130 + params={ 131 + "repo": identity["did"], 132 + "collection": collection, 133 + "rkey": rkey, 134 + }, 135 + ).json() 136 + 137 + return identity, record 138 + ``` 139 + 140 + ## working with pdsx 141 + 142 + pdsx handles write operations and record CRUD. Use it alongside Microcosm: 143 + 144 + - **reads**: Slingshot (cached, fast) or pdsx with `-r` flag 145 + - **writes**: pdsx (`create`, `update`, `delete`) 146 + - **engagement data**: Constellation (backlinks, counts) 147 + - **real-time**: Spacedust (WebSocket stream) 148 + - **identity**: Slingshot's `resolveMiniDoc` or pdsx's `whoami` 149 + 150 + ## working with atproto-mcp 151 + 152 + atproto-mcp provides searchable documentation for the AT Protocol. Use it when you need to: 153 + - look up lexicon schemas (`get_lexicon`, `search_lexicons`) 154 + - find API documentation (`search_bsky_api`, `search_atproto_docs`) 155 + - discover cookbook examples (`list_cookbook_examples`, `get_cookbook_example`) 156 + 157 + ## tech stack suggestions 158 + 159 + For building atproto apps quickly: 160 + 161 + | layer | recommendation | 162 + |-------|---------------| 163 + | frontend | SvelteKit (Svelte 5), deployed to Cloudflare Pages | 164 + | record operations | pdsx (CLI or MCP) | 165 + | engagement data | Constellation API | 166 + | real-time updates | Spacedust WebSocket | 167 + | identity/hydration | Slingshot | 168 + | atproto docs lookup | atproto-mcp | 169 + 170 + ## tips 171 + 172 + - use `getManyToManyCounts` for batch engagement — one request for multiple posts × multiple interaction types 173 + - cache Constellation results client-side (localStorage, 15-minute TTL is reasonable) 174 + - Spacedust's 21-second delay filters accidental interactions — usually what you want for UX 175 + - all Microcosm services are unauthenticated for reads — no API keys needed 176 + - for lexicons beyond `app.bsky.*`, use UFOs to discover what exists, then Constellation to query backlinks
+125
skills/constellation/SKILL.md
··· 1 + --- 2 + name: constellation 3 + description: Query the Constellation global backlink index for AT Protocol. Use when you need to find who liked/reposted/quoted/replied to a record, count interactions, or query relationships between any atproto records. 4 + user-invocable: true 5 + --- 6 + 7 + # constellation — global backlink index 8 + 9 + Constellation indexes every link in the AT Protocol network (~13 billion links). It answers "who interacted with this record?" for any atproto app, not just Bluesky. 10 + 11 + **Base URL:** `https://constellation.microcosm.blue` 12 + 13 + ## the source format 14 + 15 + The key abstraction is the `source` parameter: `{collection}:{path}` where path omits the leading dot. 16 + 17 + Common sources for Bluesky: 18 + 19 + | interaction | source | 20 + |-------------|--------| 21 + | likes | `app.bsky.feed.like:subject.uri` | 22 + | reposts | `app.bsky.feed.repost:subject.uri` | 23 + | follows | `app.bsky.graph.follow:subject` | 24 + | blocks | `app.bsky.graph.block:subject` | 25 + | replies | `app.bsky.feed.post:reply.parent.uri` | 26 + | quotes | `app.bsky.feed.post:embed.record.uri` | 27 + | thread replies | `app.bsky.feed.post:reply.root.uri` | 28 + | list members | `app.bsky.graph.listitem:list` | 29 + 30 + This works with any lexicon — not just `app.bsky.*`. If an app stores a link in a record field, Constellation indexes it. 31 + 32 + ## XRPC endpoints 33 + 34 + ### get backlinks 35 + ``` 36 + GET /xrpc/blue.microcosm.links.getBacklinks 37 + ?subject=at://did:plc:.../app.bsky.feed.post/3lwcmto4tck2h 38 + &source=app.bsky.feed.like:subject.uri 39 + &limit=100 40 + &cursor=<hex> 41 + ``` 42 + 43 + Returns `{ items: [...], cursor: "..." }`. Items contain the source record AT-URI and metadata. 44 + 45 + ### count backlinks 46 + ``` 47 + GET /xrpc/blue.microcosm.links.getBacklinksCount 48 + ?subject=at://did:plc:.../app.bsky.feed.post/3lwcmto4tck2h 49 + &source=app.bsky.feed.like:subject.uri 50 + ``` 51 + 52 + Returns `{ total: 42 }`. 53 + 54 + ### get distinct DIDs 55 + ``` 56 + GET /xrpc/blue.microcosm.links.getBacklinkDids 57 + ?subject=at://did:plc:.../app.bsky.feed.post/3lwcmto4tck2h 58 + &source=app.bsky.feed.like:subject.uri 59 + ``` 60 + 61 + Returns unique identities that created backlinks (deduped by DID). 62 + 63 + ### many-to-many queries 64 + ``` 65 + GET /xrpc/blue.microcosm.links.getManyToManyCounts 66 + ?subjects=at://...&subjects=at://... 67 + &sources=app.bsky.feed.like:subject.uri&sources=app.bsky.feed.repost:subject.uri 68 + ``` 69 + 70 + Batch query: multiple subjects × multiple sources in one request. Use this for efficiency when displaying engagement counts on a list of posts. 71 + 72 + ## pagination 73 + 74 + - Cursors are opaque hex-encoded strings 75 + - Default limit: 100, max: 1000 76 + - Pass `cursor` from previous response to get next page 77 + 78 + ## examples 79 + 80 + Count likes on a post (curl): 81 + ```bash 82 + curl "https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinksCount?subject=at://did:plc:hdhoaan3xa3jiuq4fg4mefid/app.bsky.feed.post/3lwcmto4tck2h&source=app.bsky.feed.like:subject.uri" 83 + ``` 84 + 85 + Count likes on a post (python): 86 + ```python 87 + import httpx 88 + resp = httpx.get( 89 + "https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinksCount", 90 + params={ 91 + "subject": "at://did:plc:hdhoaan3xa3jiuq4fg4mefid/app.bsky.feed.post/3lwcmto4tck2h", 92 + "source": "app.bsky.feed.like:subject.uri", 93 + }, 94 + ) 95 + print(f"likes: {resp.json()['total']}") 96 + ``` 97 + 98 + Count likes on a post (javascript): 99 + ```javascript 100 + const url = new URL("https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinksCount"); 101 + url.searchParams.set("subject", "at://did:plc:hdhoaan3xa3jiuq4fg4mefid/app.bsky.feed.post/3lwcmto4tck2h"); 102 + url.searchParams.set("source", "app.bsky.feed.like:subject.uri"); 103 + const { total } = await fetch(url).then(r => r.json()); 104 + ``` 105 + 106 + Get all followers of a user: 107 + ```bash 108 + curl "https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinks?subject=did:plc:z72i7hdynmk6r22z27h6tvur&source=app.bsky.graph.follow:subject&limit=1000" 109 + ``` 110 + 111 + ## legacy endpoints 112 + 113 + The older REST endpoints still work but prefer the XRPC ones above: 114 + - `GET /links` — equivalent to getBacklinks 115 + - `GET /links/count` — equivalent to getBacklinksCount 116 + - `GET /links/count/distinct-dids` — equivalent to getBacklinkDids 117 + 118 + Legacy uses `target`, `collection`, `path` as separate params instead of `subject` + `source`. 119 + 120 + ## tips 121 + 122 + - Always use the full AT-URI for `subject` when querying about a specific record 123 + - For user-level queries (followers, blocks), use just the DID as subject 124 + - Use `getManyToManyCounts` when displaying engagement on a feed — one request instead of N 125 + - Constellation is read-only and unauthenticated — no API key needed
+141
skills/slingshot/SKILL.md
··· 1 + --- 2 + name: slingshot 3 + description: Use the Slingshot edge cache for fast AT Protocol record fetching and identity resolution. Use when you need to hydrate AT-URIs into full records, resolve handles to DIDs, or get identity details quickly. 4 + user-invocable: true 5 + --- 6 + 7 + # slingshot — edge record and identity cache 8 + 9 + Slingshot is a fast, eager cache of atproto records and identities. It pre-caches records from the firehose, so most fetches are instant. Use it instead of hitting PDS instances directly for read operations. 10 + 11 + **Base URL:** `https://slingshot.microcosm.blue` 12 + 13 + ## identity resolution 14 + 15 + ### resolve handle to DID 16 + ``` 17 + GET /xrpc/com.atproto.identity.resolveHandle?handle=zzstoatzz.io 18 + ``` 19 + Returns `{ "did": "did:plc:..." }`. Bi-directionally verified (both DNS/well-known and DID document checked). 20 + 21 + ### resolve identity mini-doc 22 + ``` 23 + GET /xrpc/com.bad-example.identity.resolveMiniDoc?identifier=zzstoatzz.io 24 + ``` 25 + Returns a compact identity bundle: 26 + ```json 27 + { 28 + "did": "did:plc:...", 29 + "handle": "zzstoatzz.io", 30 + "pds": "https://...", 31 + "signing_key": "zQ3sh..." 32 + } 33 + ``` 34 + 35 + Accepts either a handle or DID as the `identifier` parameter. This is the most efficient way to get all identity details in one call. 36 + 37 + ## record fetching 38 + 39 + ### get a cached record (JSON) 40 + ``` 41 + GET /xrpc/com.atproto.repo.getRecord 42 + ?repo=did:plc:... 43 + &collection=app.bsky.feed.post 44 + &rkey=3lwcmto4tck2h 45 + ``` 46 + Returns the record value as JSON. Same interface as the standard atproto endpoint but served from cache. 47 + 48 + ### get a record with proof (DAG-CBOR) 49 + ``` 50 + GET /xrpc/com.atproto.sync.getRecord 51 + ?did=did:plc:... 52 + &collection=app.bsky.feed.post 53 + &rkey=3lwcmto4tck2h 54 + ``` 55 + Returns the record with cryptographic proof. Use this when you need to verify record authenticity. 56 + 57 + ## examples 58 + 59 + Resolve a handle and fetch their profile (python): 60 + ```python 61 + import httpx 62 + 63 + # resolve identity 64 + identity = httpx.get( 65 + "https://slingshot.microcosm.blue/xrpc/com.bad-example.identity.resolveMiniDoc", 66 + params={"identifier": "zzstoatzz.io"}, 67 + ).json() 68 + 69 + # fetch profile record 70 + profile = httpx.get( 71 + "https://slingshot.microcosm.blue/xrpc/com.atproto.repo.getRecord", 72 + params={ 73 + "repo": identity["did"], 74 + "collection": "app.bsky.actor.profile", 75 + "rkey": "self", 76 + }, 77 + ).json() 78 + 79 + print(f"{identity['handle']}: {profile['value'].get('description', '')}") 80 + ``` 81 + 82 + Resolve and fetch (javascript): 83 + ```javascript 84 + const base = "https://slingshot.microcosm.blue/xrpc"; 85 + 86 + const identity = await fetch( 87 + `${base}/com.bad-example.identity.resolveMiniDoc?identifier=zzstoatzz.io` 88 + ).then(r => r.json()); 89 + 90 + const record = await fetch( 91 + `${base}/com.atproto.repo.getRecord?repo=${identity.did}&collection=app.bsky.feed.post&rkey=3lwcmto4tck2h` 92 + ).then(r => r.json()); 93 + ``` 94 + 95 + ## when to use slingshot vs PDS directly 96 + 97 + | scenario | use | 98 + |----------|-----| 99 + | reading public records | slingshot (faster, cached) | 100 + | reading records from Constellation/Spacedust results | slingshot (hydrate AT-URIs) | 101 + | identity resolution | slingshot (bi-directional verification, fast) | 102 + | writing records | PDS directly (or pdsx) | 103 + | authenticated operations | PDS directly (or pdsx) | 104 + | cryptographic verification | slingshot's sync.getRecord | 105 + 106 + ## the hydration pattern 107 + 108 + Constellation and Spacedust return references (AT-URIs, DIDs), not full records. The standard pattern: 109 + 110 + 1. Query Constellation for backlinks → get list of AT-URIs 111 + 2. Fetch each record from Slingshot → get full content 112 + 3. Resolve DIDs via resolveMiniDoc → get display names/handles 113 + 114 + ```python 115 + # get who liked a post 116 + backlinks = httpx.get( 117 + "https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinks", 118 + params={ 119 + "subject": post_uri, 120 + "source": "app.bsky.feed.like:subject.uri", 121 + "limit": 10, 122 + }, 123 + ).json() 124 + 125 + # hydrate each liker's identity 126 + for item in backlinks["items"]: 127 + # extract DID from the source_record AT-URI 128 + did = item["source_record"].split("/")[2] 129 + identity = httpx.get( 130 + "https://slingshot.microcosm.blue/xrpc/com.bad-example.identity.resolveMiniDoc", 131 + params={"identifier": did}, 132 + ).json() 133 + print(f"liked by @{identity['handle']}") 134 + ``` 135 + 136 + ## tips 137 + 138 + - Slingshot eagerly caches from the firehose, so most recent records are available instantly 139 + - Use `resolveMiniDoc` instead of `resolveHandle` when you need more than just the DID 140 + - Batch your hydration calls when possible — Slingshot is fast but network round-trips add up 141 + - No authentication needed for any Slingshot endpoints
+139
skills/spacedust/SKILL.md
··· 1 + --- 2 + name: spacedust 3 + description: Connect to the Spacedust real-time interactions firehose for AT Protocol. Use when building live notifications, real-time counters, activity feeds, or any feature that needs to react to interactions as they happen. 4 + user-invocable: true 5 + --- 6 + 7 + # spacedust — real-time interactions firehose 8 + 9 + Spacedust extracts links from every record in the AT Protocol firehose and re-emits them over WebSocket with client-side filtering. Think of it as a filtered, link-focused view of the entire network in real time. 10 + 11 + **Base URL:** `wss://spacedust.microcosm.blue` 12 + 13 + ## connecting 14 + 15 + ``` 16 + GET /subscribe?wantedSources=...&wantedSubjectDids=... 17 + ``` 18 + 19 + Upgrades to WebSocket. All query parameters are repeatable for multiple values. 20 + 21 + ### filter parameters 22 + 23 + | parameter | type | description | 24 + |-----------|------|-------------| 25 + | `wantedSubjects` | `string[]` | specific AT-URIs to receive links about | 26 + | `wantedSubjectPrefixes` | `string[]` | URI/DID prefixes to match | 27 + | `wantedSubjectDids` | `string[]` | DIDs to receive links about | 28 + | `wantedSources` | `string[]` | link sources to filter by (same format as Constellation) | 29 + | `instant` | `bool` | bypass the 21-second delay buffer (default: false) | 30 + 31 + Sources use the same `collection:path` format as Constellation (e.g. `app.bsky.feed.like:subject.uri`). 32 + 33 + ## message format 34 + 35 + Server sends JSON messages: 36 + 37 + ```json 38 + { 39 + "kind": "link", 40 + "origin": "live", 41 + "link": { 42 + "operation": "create", 43 + "source": "app.bsky.feed.like:subject.uri", 44 + "source_record": "at://did:plc:.../app.bsky.feed.like/3lv4ouczo2b2a", 45 + "source_rev": "...", 46 + "subject": "at://did:plc:.../app.bsky.feed.post/3lgwdn7vd722r" 47 + } 48 + } 49 + ``` 50 + 51 + - `operation` is `create` or `delete` 52 + - `source` tells you the type of interaction 53 + - `source_record` is the AT-URI of the record that created the link 54 + - `subject` is what was interacted with 55 + 56 + ## dynamic filter updates 57 + 58 + Send a JSON message on an open connection to update filters without reconnecting: 59 + 60 + ```json 61 + { 62 + "type": "options_update", 63 + "payload": { 64 + "wantedSubjectDids": ["did:plc:z72i7hdynmk6r22z27h6tvur"], 65 + "wantedSources": ["app.bsky.graph.follow:subject"] 66 + } 67 + } 68 + ``` 69 + 70 + This replaces the current filters entirely. 71 + 72 + ## the 21-second delay 73 + 74 + By default, Spacedust holds links for 21 seconds before emitting them. This filters out quickly-undone interactions (about 1% of all interactions — accidental likes, immediate unfollows, etc.). 75 + 76 + Set `instant=true` to bypass this if you need zero-latency events and can handle the noise. 77 + 78 + ## examples 79 + 80 + Watch for new followers of a user (bash): 81 + ```bash 82 + websocat "wss://spacedust.microcosm.blue/subscribe?wantedSources=app.bsky.graph.follow:subject&wantedSubjectDids=did:plc:z72i7hdynmk6r22z27h6tvur" 83 + ``` 84 + 85 + Watch for likes on a specific post (python): 86 + ```python 87 + import asyncio 88 + import websockets 89 + import json 90 + 91 + async def watch_likes(post_uri: str): 92 + url = f"wss://spacedust.microcosm.blue/subscribe?wantedSources=app.bsky.feed.like:subject.uri&wantedSubjects={post_uri}" 93 + async with websockets.connect(url) as ws: 94 + async for message in ws: 95 + event = json.loads(message) 96 + if event["kind"] == "link": 97 + link = event["link"] 98 + print(f"{link['operation']}: {link['source_record']}") 99 + ``` 100 + 101 + Watch for likes on a specific post (javascript): 102 + ```javascript 103 + const ws = new WebSocket( 104 + `wss://spacedust.microcosm.blue/subscribe?wantedSources=app.bsky.feed.like:subject.uri&wantedSubjects=${encodeURIComponent(postUri)}` 105 + ); 106 + ws.onmessage = (event) => { 107 + const { kind, link } = JSON.parse(event.data); 108 + if (kind === "link") { 109 + console.log(`${link.operation}: ${link.source_record}`); 110 + } 111 + }; 112 + ``` 113 + 114 + Dynamically update filters (javascript): 115 + ```javascript 116 + // start watching follows 117 + ws.send(JSON.stringify({ 118 + type: "options_update", 119 + payload: { 120 + wantedSubjectDids: [userDid], 121 + wantedSources: ["app.bsky.graph.follow:subject"] 122 + } 123 + })); 124 + ``` 125 + 126 + ## use cases 127 + 128 + - **notifications**: subscribe to `wantedSubjectDids` for your users, filter by source type 129 + - **live counters**: increment/decrement counts on create/delete events 130 + - **activity feeds**: show real-time interaction stream for a user or post 131 + - **monitoring**: watch for interactions on specific records or from specific users 132 + 133 + ## tips 134 + 135 + - Use `wantedSubjectDids` for user-level monitoring (all interactions with a user's content) 136 + - Use `wantedSubjects` for record-level monitoring (interactions with specific posts) 137 + - Use `wantedSources` to filter interaction type (likes only, follows only, etc.) 138 + - Combine filters: `wantedSubjectDids` + `wantedSources` = "new followers of this user" 139 + - The delay buffer means Spacedust is not suitable for sub-second latency requirements unless you set `instant=true`
+33
skills/ufos/SKILL.md
··· 1 + --- 2 + name: ufos 3 + description: Query the UFOs API for AT Protocol lexicon timeseries statistics. Use when you need to discover what lexicons exist in the network, track collection activity over time, or understand the shape of the atproto ecosystem. 4 + user-invocable: true 5 + --- 6 + 7 + # ufos — lexicon timeseries stats 8 + 9 + UFOs tracks every collection (lexicon) ever observed in the AT Protocol firehose, providing timeseries statistics and sample records. Uses HyperLogLog sketches for cardinality estimation. 10 + 11 + **API URL:** `https://ufos-api.microcosm.blue` 12 + **Web explorer:** `https://ufos.microcosm.blue` 13 + 14 + ## what it's for 15 + 16 + - **discover unknown lexicons**: see what atproto apps exist beyond Bluesky 17 + - **track activity**: how many records are being created in a collection over time? 18 + - **estimate cardinality**: how many unique users are using a particular app? 19 + - **sample records**: see what records in a collection look like without crawling 20 + 21 + ## use cases 22 + 23 + - building an "everything app" that shows all atproto activity 24 + - researching which atproto apps are gaining traction 25 + - understanding record schemas before building integrations 26 + - monitoring the health/activity of a specific lexicon namespace 27 + 28 + ## tips 29 + 30 + - the web explorer at ufos.microcosm.blue is useful for quick interactive exploration 31 + - HyperLogLog estimates have ~1-2% error margin at scale — precise enough for analytics 32 + - combine with Constellation to understand not just volume but interaction patterns 33 + - use UFOs to discover lexicons, then Constellation to query their backlinks