claude up some atproto stuff
9
fork

Configure Feed

Select the types of activity you want to include in your feed.

rewrite skills: action-oriented, not API mirrors

- point to live docs at each service's URL instead of mirroring API ref
- include only tested, working examples (verified with curl)
- note getBacklinkDids/getManyToMany are in source but not yet deployed
- dramatically shorter — 592 lines removed

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

zzstoatzz 60b0c303 e28b28aa

+125 -592
+29 -143
skills/app-patterns/SKILL.md
··· 6 6 7 7 # app patterns — building with microcosm 8 8 9 - This skill covers common patterns for combining Microcosm services to build AT Protocol applications. 10 - 11 9 ## the compose pattern 12 10 13 - Most atproto apps combine three Microcosm services: 11 + Most atproto apps combine three services: 14 12 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 - ``` 13 + 1. **Constellation** — historical queries ("how many likes?", "who follows?") 14 + 2. **Slingshot** — hydrate references into full records and identities 15 + 3. **Spacedust** — real-time updates via WebSocket 26 16 27 17 ## pattern: engagement counters 28 18 29 - Display like/repost/reply counts on a post, updated in real time. 19 + Get initial counts from Constellation, then keep them live with Spacedust: 30 20 31 21 ```javascript 32 - // 1. get initial counts from Constellation (parallel requests — no batch endpoint) 33 - const base = "https://constellation.microcosm.blue/xrpc"; 22 + // initial counts (parallel) 34 23 const [likes, reposts] = await Promise.all([ 35 - fetch(`${base}/blue.microcosm.links.getBacklinksCount?subject=${encodeURIComponent(postUri)}&source=app.bsky.feed.like:subject.uri`).then(r => r.json()), 36 - fetch(`${base}/blue.microcosm.links.getBacklinksCount?subject=${encodeURIComponent(postUri)}&source=app.bsky.feed.repost:subject.uri`).then(r => r.json()), 24 + fetch(`https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinksCount?subject=${encodeURIComponent(postUri)}&source=app.bsky.feed.like:subject.uri`).then(r => r.json()), 25 + fetch(`https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinksCount?subject=${encodeURIComponent(postUri)}&source=app.bsky.feed.repost:subject.uri`).then(r => r.json()), 37 26 ]); 38 27 39 - // 2. subscribe to real-time updates via Spacedust 28 + // live updates 40 29 const ws = new WebSocket( 41 - `wss://spacedust.microcosm.blue/subscribe?` + 42 - `wantedSubjects=${encodeURIComponent(postUri)}` + 43 - `&wantedSources=app.bsky.feed.like:subject.uri` + 44 - `&wantedSources=app.bsky.feed.repost:subject.uri` 30 + `wss://spacedust.microcosm.blue/subscribe?wantedSubjects=${encodeURIComponent(postUri)}&wantedSources=app.bsky.feed.like:subject.uri&wantedSources=app.bsky.feed.repost:subject.uri` 45 31 ); 46 - ws.onmessage = (event) => { 47 - const { link } = JSON.parse(event.data); 48 - // increment/decrement counters based on link.operation and link.source 32 + ws.onmessage = (e) => { 33 + const { link } = JSON.parse(e.data); 34 + // increment/decrement based on link.operation and link.source 49 35 }; 50 36 ``` 51 37 52 - ## pattern: highlight reel 53 - 54 - Show a user's top posts ranked by engagement (like highlights.waow.tech). 55 - 56 - ```python 57 - import httpx 58 - 59 - slingshot = "https://slingshot.microcosm.blue/xrpc" 60 - constellation = "https://constellation.microcosm.blue/xrpc" 61 - 62 - # 1. resolve identity 63 - identity = httpx.get( 64 - f"{slingshot}/blue.microcosm.identity.resolveMiniDoc", 65 - params={"identifier": handle}, 66 - ).json() 67 - 68 - # 2. get user's posts (via public Bluesky API or pdsx) 69 - # 3. for each post, get engagement counts from Constellation (parallel) 70 - import asyncio 71 - 72 - async def get_counts(client: httpx.AsyncClient, post_uri: str): 73 - likes, reposts, quotes = await asyncio.gather( 74 - client.get(f"{constellation}/blue.microcosm.links.getBacklinksCount", 75 - params={"subject": post_uri, "source": "app.bsky.feed.like:subject.uri"}), 76 - client.get(f"{constellation}/blue.microcosm.links.getBacklinksCount", 77 - params={"subject": post_uri, "source": "app.bsky.feed.repost:subject.uri"}), 78 - client.get(f"{constellation}/blue.microcosm.links.getBacklinksCount", 79 - params={"subject": post_uri, "source": "app.bsky.feed.post:embed.record.uri"}), 80 - ) 81 - return { 82 - "likes": likes.json()["total"], 83 - "reposts": reposts.json()["total"], 84 - "quotes": quotes.json()["total"], 85 - } 86 - 87 - # 4. score and rank posts 88 - ``` 89 - 90 - ## pattern: notifications service 91 - 92 - React to interactions with a user's content in real time (like Microcosm's notifications.microcosm.blue). 93 - 94 - ```python 95 - import asyncio 96 - import websockets 97 - import json 98 - 99 - async def notification_service(user_dids: list[str]): 100 - url = "wss://spacedust.microcosm.blue/subscribe?" + "&".join( 101 - f"wantedSubjectDids={did}" for did in user_dids 102 - ) 103 - async with websockets.connect(url) as ws: 104 - async for message in ws: 105 - event = json.loads(message) 106 - if event["kind"] == "link": 107 - link = event["link"] 108 - # resolve who did it 109 - source_did = link["source_record"].split("/")[2] 110 - # send notification to the affected user 111 - # (the DID in the subject) 112 - ``` 113 - 114 38 ## pattern: identity-first fetch 115 39 116 - Almost every atproto operation starts with resolving a handle to a DID. Use Slingshot for this. 40 + Almost everything starts with resolving a handle: 117 41 118 - ```python 119 - import httpx 42 + ```javascript 43 + const base = "https://slingshot.microcosm.blue/xrpc"; 120 44 121 - def resolve_and_fetch(handle: str, collection: str, rkey: str): 122 - slingshot = "https://slingshot.microcosm.blue/xrpc" 45 + // resolve identity (DID, handle, PDS URL, signing key) 46 + const identity = await fetch(`${base}/blue.microcosm.identity.resolveMiniDoc?identifier=${handle}`).then(r => r.json()); 123 47 124 - # resolve handle → DID + PDS 125 - identity = httpx.get( 126 - f"{slingshot}/blue.microcosm.identity.resolveMiniDoc", 127 - params={"identifier": handle}, 128 - ).json() 129 - 130 - # fetch record from cache 131 - record = httpx.get( 132 - f"{slingshot}/com.atproto.repo.getRecord", 133 - params={ 134 - "repo": identity["did"], 135 - "collection": collection, 136 - "rkey": rkey, 137 - }, 138 - ).json() 48 + // fetch a record by AT-URI 49 + const record = await fetch(`${base}/blue.microcosm.repo.getRecordByUri?at_uri=${encodeURIComponent(atUri)}`).then(r => r.json()); 139 50 140 - return identity, record 51 + // for listRecords, query the PDS directly (slingshot doesn't support it) 52 + const posts = await fetch(`${identity.pds}/xrpc/com.atproto.repo.listRecords?repo=${identity.did}&collection=app.bsky.feed.post&limit=100`).then(r => r.json()); 141 53 ``` 142 54 143 - ## working with pdsx 55 + ## working with pdsx and atproto-mcp 144 56 145 - pdsx handles write operations and record CRUD. Use it alongside Microcosm: 146 - 147 - - **reads**: Slingshot (cached, fast) or pdsx with `-r` flag 148 - - **writes**: pdsx (`create`, `update`, `delete`) 149 - - **engagement data**: Constellation (backlinks, counts) 150 - - **real-time**: Spacedust (WebSocket stream) 151 - - **identity**: Slingshot's `resolveMiniDoc` or pdsx's `whoami` 57 + - **pdsx** — record CRUD (create, update, delete), auth, batch operations. Use alongside Microcosm for writes. 58 + - **atproto-mcp** — search atproto docs, lexicon schemas, cookbook examples. Use when you need to look up how a lexicon works. 59 + - **pub-search** — search published writing across atmosphere platforms. Use when researching prior art. 152 60 153 - ## working with atproto-mcp 61 + ## notes 154 62 155 - atproto-mcp provides searchable documentation for the AT Protocol. Use it when you need to: 156 - - look up lexicon schemas (`get_lexicon`, `search_lexicons`) 157 - - find API documentation (`search_bsky_api`, `search_atproto_docs`) 158 - - discover cookbook examples (`list_cookbook_examples`, `get_cookbook_example`) 159 - 160 - ## tech stack suggestions 161 - 162 - For building atproto apps quickly: 163 - 164 - | layer | recommendation | 165 - |-------|---------------| 166 - | frontend | SvelteKit (Svelte 5), deployed to Cloudflare Pages | 167 - | record operations | pdsx (CLI or MCP) | 168 - | engagement data | Constellation API | 169 - | real-time updates | Spacedust WebSocket | 170 - | identity/hydration | Slingshot | 171 - | atproto docs lookup | atproto-mcp | 172 - 173 - ## tips 174 - 175 - - fire parallel `getBacklinksCount` requests for engagement counts — Constellation is fast enough that concurrency makes up for the lack of batching 176 - - cache Constellation results client-side (localStorage, 15-minute TTL is reasonable) 177 - - Spacedust's 21-second delay filters accidental interactions — usually what you want for UX 178 - - all Microcosm services are unauthenticated for reads — no API keys needed 63 + - all Microcosm services are unauthenticated — no API keys needed 64 + - check the live docs at each service's URL for the latest API details 179 65 - for lexicons beyond `app.bsky.*`, use UFOs to discover what exists, then Constellation to query backlinks
+31 -172
skills/constellation/SKILL.md
··· 6 6 7 7 # constellation — global backlink index 8 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. 9 + Constellation indexes every link in the AT Protocol network. Use it to answer "who interacted with this?" for any atproto record. 10 10 11 - **Base URL:** `https://constellation.microcosm.blue` 11 + **Live API docs (interactive, try requests in browser):** https://constellation.microcosm.blue 12 + **Source:** https://github.com/at-microcosm/microcosm-rs/tree/main/constellation 12 13 13 - ## the source format 14 + ## key concept: the source format 14 15 15 - The key abstraction is the `source` parameter: `{collection}:{path}` where path omits the leading dot. 16 - 17 - Common sources for Bluesky: 16 + All queries use `source` in the format `{collection}:{path}` (path omits leading dot): 18 17 19 18 | interaction | source | 20 19 |-------------|--------| 21 20 | likes | `app.bsky.feed.like:subject.uri` | 22 21 | reposts | `app.bsky.feed.repost:subject.uri` | 23 22 | follows | `app.bsky.graph.follow:subject` | 24 - | blocks | `app.bsky.graph.block:subject` | 25 23 | replies | `app.bsky.feed.post:reply.parent.uri` | 26 24 | 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 25 30 - This works with any lexicon — not just `app.bsky.*`. If an app stores a link in a record field, Constellation indexes it. 26 + Works with any lexicon, not just Bluesky. 31 27 32 - Note: the XRPC source format omits the leading dot (e.g. `subject.uri`). The server prepends it internally. The legacy endpoints use the dot-prefixed format (e.g. `.subject.uri`). 28 + ## what you can do 33 29 34 - ## XRPC endpoints 35 - 36 - ### getBacklinks — list records linking to a target 37 - ``` 38 - GET /xrpc/blue.microcosm.links.getBacklinks 39 - ?subject=at://did:plc:.../app.bsky.feed.post/3lwcmto4tck2h 40 - &source=app.bsky.feed.like:subject.uri 41 - &limit=100 42 - &cursor=<hex> 30 + **Count interactions** — `getBacklinksCount` with `subject` + `source`: 31 + ```bash 32 + 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" 33 + # {"total":16} 43 34 ``` 44 35 45 - Parameters: 46 - - `subject` (required) — the target AT-URI or DID being linked to. must be URL-encoded. 47 - - `source` (required) — collection:path format. 48 - - `did` (optional, repeatable) — filter results to specific DIDs. repeat for multiple: `&did=did:plc:...&did=did:plc:...` 49 - - `limit` (optional) — default 100, max 1000. 50 - - `reverse` (optional) — boolean, return oldest-first instead of newest-first. 51 - - `cursor` (optional) — opaque hex-encoded cursor from a previous response. 52 - 53 - Returns `{ total, records: [...], cursor }`. 54 - 55 - ### getBacklinksCount — count links to a target 56 - ``` 57 - GET /xrpc/blue.microcosm.links.getBacklinksCount 58 - ?subject=at://did:plc:.../app.bsky.feed.post/3lwcmto4tck2h 59 - &source=app.bsky.feed.like:subject.uri 36 + **List who interacted** — `getBacklinks` with `subject` + `source` + `limit`: 37 + ```bash 38 + curl "https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinks?subject=at://did:plc:hdhoaan3xa3jiuq4fg4mefid/app.bsky.feed.post/3lwcmto4tck2h&source=app.bsky.feed.like:subject.uri&limit=3" | jq . 39 + # {"total":16,"records":[{"did":"...","collection":"app.bsky.feed.like","rkey":"..."},...],"cursor":"12020f"} 60 40 ``` 61 41 62 - Parameters: 63 - - `subject` (required) — the target. must be URL-encoded. 64 - - `source` (required) — collection:path format. 65 - 66 - Returns `{ total: 42 }`. 67 - 68 - ### getBacklinkDids — list distinct DIDs linking to a target 69 - ``` 70 - GET /xrpc/blue.microcosm.links.getBacklinkDids 71 - ?subject=at://did:plc:.../app.bsky.feed.post/3lwcmto4tck2h 72 - &source=app.bsky.feed.like:subject.uri 73 - &limit=100 42 + **Explore all link types to a target** — `/links/all` with `target`: 43 + ```bash 44 + curl "https://constellation.microcosm.blue/links/all?target=did:plc:hdhoaan3xa3jiuq4fg4mefid" | jq . 45 + # shows every collection+path linking to this DID with counts 74 46 ``` 75 47 76 - Parameters: 77 - - `subject` (required) — the target. 78 - - `source` (required) — collection:path format. 79 - - `cursor` (optional) — opaque hex cursor. 80 - - `limit` (optional) — default 100, max 1000. 81 - 82 - Returns `{ total, linking_dids: [...], cursor }`. Deduped by DID — useful for "who liked this?" without duplicates. 83 - 84 - ### getManyToManyCounts — count secondary links through a join record 85 - 86 - For records that link to TWO things — e.g. a list item linking to both a list and a user. This is NOT a batch endpoint for multiple subjects. 87 - 88 - ``` 89 - GET /xrpc/blue.microcosm.links.getManyToManyCounts 90 - ?subject=at://did:plc:.../app.bsky.graph.list/abc 91 - &source=app.bsky.graph.listitem:list 92 - &pathToOther=subject 48 + **Get distinct DIDs** — legacy `/links/distinct-dids` (the XRPC equivalent `getBacklinkDids` is in source but not yet deployed): 49 + ```bash 50 + curl "https://constellation.microcosm.blue/links/distinct-dids?target=at://...&collection=app.bsky.feed.like&path=.subject.uri" | jq . 93 51 ``` 94 52 95 - Parameters: 96 - - `subject` (required) — the primary target. 97 - - `source` (required) — primary link specification. 98 - - `pathToOther` (required) — path to the secondary link field in the record (without leading dot). 99 - - `did` (optional, repeatable) — filter by linking record DIDs. 100 - - `otherSubject` (optional, repeatable) — filter to specific secondary link targets. 101 - - `cursor` (optional) — opaque hex cursor. 102 - - `limit` (optional) — default 100, max 1000. 53 + ## getting counts for multiple posts 103 54 104 - Returns `{ counts_by_other_subject: [{ subject, total, distinct }], cursor }`. 105 - 106 - ### getManyToMany — list items through a join record 107 - 108 - Same as getManyToManyCounts but returns actual items instead of just counts. 109 - 110 - ``` 111 - GET /xrpc/blue.microcosm.links.getManyToMany 112 - ?subject=at://did:plc:.../app.bsky.graph.list/abc 113 - &source=app.bsky.graph.listitem:list 114 - &pathToOther=subject 115 - ``` 116 - 117 - Parameters: 118 - - `subject` (required) — the primary target. 119 - - `source` (required) — primary link specification. 120 - - `pathToOther` (required) — path to the secondary link field (without leading dot). 121 - - `linkDid` (optional, repeatable) — filter by linking record DIDs. 122 - - `otherSubject` (optional, repeatable) — filter to specific secondary link targets. 123 - - `cursor` (optional) — opaque hex cursor. 124 - - `limit` (optional) — default 100, max 1000. 125 - 126 - Returns `{ items: [...], cursor }`. 127 - 128 - ## getting engagement counts for multiple posts 129 - 130 - There is no batch endpoint for "get like counts for N posts in one request." To get engagement counts for a list of posts, fire parallel requests to `getBacklinksCount`: 131 - 55 + No batch endpoint — fire parallel requests: 132 56 ```javascript 133 - const posts = ["at://did:plc:.../app.bsky.feed.post/abc", "at://did:plc:.../app.bsky.feed.post/def"]; 134 57 const counts = await Promise.all( 135 - posts.flatMap(uri => [ 58 + postUris.flatMap(uri => [ 136 59 fetch(`https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinksCount?subject=${encodeURIComponent(uri)}&source=app.bsky.feed.like:subject.uri`).then(r => r.json()), 137 60 fetch(`https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinksCount?subject=${encodeURIComponent(uri)}&source=app.bsky.feed.repost:subject.uri`).then(r => r.json()), 138 61 ]) 139 62 ); 140 63 ``` 141 64 142 - This is fast — Constellation responses are typically sub-10ms. 65 + ## notes 143 66 144 - ## pagination 145 - 146 - - Cursors are opaque hex-encoded strings (bincode serialized) 147 - - Default limit: 100, max: 1000 148 - - Pass `cursor` from previous response to get next page 149 - - Supported on: getBacklinks, getBacklinkDids, getManyToManyCounts, getManyToMany 150 - 151 - ## legacy endpoints 152 - 153 - These older REST endpoints still work but prefer the XRPC ones above: 154 - 155 - - `GET /links` — list backlinks. params: `target`, `collection`, `path` (dot-prefixed), `did`, `limit`, `cursor` 156 - - `GET /links/count` — count backlinks. params: `target`, `collection`, `path` (dot-prefixed), `cursor` 157 - - `GET /links/distinct-dids` — unique DIDs. params: `target`, `collection`, `path` (dot-prefixed) 158 - - `GET /links/count/distinct-dids` — count distinct DIDs. params: `target`, `collection`, `path` (dot-prefixed), `cursor` 159 - - `GET /links/all` — all sources with links to target, including counts. params: `target` 160 - - `GET /links/all/count` (deprecated, use /links/all) — all link counts. params: `target` 161 - 162 - Legacy endpoints use dot-prefixed paths (`.subject.uri`) and `target` instead of `subject`. 163 - 164 - ## examples 165 - 166 - Count likes on a post (curl): 167 - ```bash 168 - 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" 169 - ``` 170 - 171 - Count likes on a post (python): 172 - ```python 173 - import httpx 174 - resp = httpx.get( 175 - "https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinksCount", 176 - params={ 177 - "subject": "at://did:plc:hdhoaan3xa3jiuq4fg4mefid/app.bsky.feed.post/3lwcmto4tck2h", 178 - "source": "app.bsky.feed.like:subject.uri", 179 - }, 180 - ) 181 - print(f"likes: {resp.json()['total']}") 182 - ``` 183 - 184 - Count likes on a post (javascript): 185 - ```javascript 186 - const url = new URL("https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinksCount"); 187 - url.searchParams.set("subject", "at://did:plc:hdhoaan3xa3jiuq4fg4mefid/app.bsky.feed.post/3lwcmto4tck2h"); 188 - url.searchParams.set("source", "app.bsky.feed.like:subject.uri"); 189 - const { total } = await fetch(url).then(r => r.json()); 190 - ``` 191 - 192 - Get followers of a user (paginated): 193 - ```bash 194 - curl "https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinks?subject=did:plc:z72i7hdynmk6r22z27h6tvur&source=app.bsky.graph.follow:subject&limit=100" 195 - # use the returned cursor for next page: 196 - # &cursor=<hex value from response> 197 - ``` 198 - 199 - Explore all link types to a target: 200 - ```bash 201 - curl "https://constellation.microcosm.blue/links/all?target=did:plc:z72i7hdynmk6r22z27h6tvur" 202 - # returns { "app.bsky.graph.follow": { ".subject": 159 }, ... } 203 - ``` 204 - 205 - ## tips 206 - 207 - - Always use the full AT-URI for `subject` when querying about a specific record 208 - - For user-level queries (followers, blocks), use just the DID as subject 209 - - For engagement counts on a feed, fire parallel `getBacklinksCount` requests — they're fast enough that concurrency makes up for the lack of batching 210 - - Use `getBacklinkDids` when you need unique identities (e.g. "who liked this?" without counting duplicate interactions) 211 - - Constellation is read-only and unauthenticated — no API key needed 212 - - The API is marked unstable — endpoints may change. The XRPC endpoints are the preferred stable path. 67 + - unauthenticated, no API key needed 68 + - default limit 100, max 1000, pagination via opaque hex `cursor` 69 + - `subject` can be an AT-URI (for records) or bare DID (for user-level queries like followers) 70 + - legacy endpoints use dot-prefixed paths (`.subject.uri`) and `target` param; XRPC endpoints use `subject` + `source` 71 + - always check the live docs at constellation.microcosm.blue for the latest endpoints — the API is evolving
+33 -154
skills/slingshot/SKILL.md
··· 6 6 7 7 # slingshot — edge record and identity cache 8 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 9 + Slingshot is a fast cache of atproto records and identities, pre-warmed from the firehose. Use it instead of hitting individual PDS instances for reads. 14 10 15 - ### resolve handle to DID 16 - ``` 17 - GET /xrpc/com.atproto.identity.resolveHandle?handle=zzstoatzz.io 18 - ``` 19 - Returns `{ "did": "did:plc:..." }`. Always bi-directionally verified (both DNS/well-known and DID document checked) — stricter than the spec requires. 11 + **Live API docs (interactive, OpenAPI spec):** https://slingshot.microcosm.blue 12 + **Source:** https://github.com/at-microcosm/microcosm-rs/tree/main/slingshot 20 13 21 - ### resolve identity mini-doc 14 + ## what you can do 22 15 23 - Preferred path (stabilizing): 24 - ``` 25 - GET /xrpc/blue.microcosm.identity.resolveMiniDoc?identifier=zzstoatzz.io 16 + **Resolve an identity** — get DID, handle, PDS URL, and signing key in one call: 17 + ```bash 18 + curl "https://slingshot.microcosm.blue/xrpc/blue.microcosm.identity.resolveMiniDoc?identifier=zzstoatzz.io" | jq . 19 + # {"did":"did:plc:xbtmt2zjwlrfegqvch7fboei","handle":"zzstoatzz.io","pds":"https://pds.zzstoatzz.io","signing_key":"zQ3sh..."} 26 20 ``` 27 21 28 - Legacy alias (still works): 29 - ``` 30 - GET /xrpc/com.bad-example.identity.resolveMiniDoc?identifier=zzstoatzz.io 31 - ``` 32 - 33 - Returns a compact identity bundle: 34 - ```json 35 - { 36 - "did": "did:plc:...", 37 - "handle": "zzstoatzz.io", 38 - "pds": "https://pds.zzstoatzz.io", 39 - "signing_key": "zQ3sh..." 40 - } 41 - ``` 42 - 43 - Accepts either a handle or DID as the `identifier` parameter. If the handle fails bi-directional verification, `handle` will be `"handle.invalid"`. This is the most efficient way to get all identity details in one call. 44 - 45 - ## record fetching 46 - 47 - ### get a cached record (JSON) 48 - ``` 49 - GET /xrpc/com.atproto.repo.getRecord 50 - ?repo=did:plc:... 51 - &collection=app.bsky.feed.post 52 - &rkey=3lwcmto4tck2h 22 + **Fetch a record by AT-URI** — one param instead of repo/collection/rkey: 23 + ```bash 24 + curl "https://slingshot.microcosm.blue/xrpc/blue.microcosm.repo.getRecordByUri?at_uri=at://did:plc:xbtmt2zjwlrfegqvch7fboei/app.bsky.actor.profile/self" | jq . 25 + # {"uri":"at://...","cid":"bafy...","value":{...}} 53 26 ``` 54 27 55 - The `repo` parameter accepts either a DID or a handle (Slingshot resolves handles automatically). 56 - 57 - Optional `cid` parameter: if specified and the cached record has a different CID, returns 400 RecordNotFound. 58 - 59 - Returns `{ uri, cid, value }` where `value` is the record JSON. 60 - 61 - ### get a record by AT-URI (ergonomic) 62 - 63 - Preferred path (stabilizing): 64 - ``` 65 - GET /xrpc/blue.microcosm.repo.getRecordByUri 66 - ?at_uri=at://did:plc:.../app.bsky.feed.post/3lwcmto4tck2h 28 + **Fetch a record (standard atproto API):** 29 + ```bash 30 + curl "https://slingshot.microcosm.blue/xrpc/com.atproto.repo.getRecord?repo=did:plc:xbtmt2zjwlrfegqvch7fboei&collection=app.bsky.actor.profile&rkey=self" | jq . 67 31 ``` 68 32 69 - Legacy alias: 70 - ``` 71 - GET /xrpc/com.bad-example.repo.getUriRecord 72 - ?at_uri=at://did:plc:.../app.bsky.feed.post/3lwcmto4tck2h 33 + **Resolve handle to DID only:** 34 + ```bash 35 + curl "https://slingshot.microcosm.blue/xrpc/com.atproto.identity.resolveHandle?handle=zzstoatzz.io" | jq . 36 + # {"did":"did:plc:xbtmt2zjwlrfegqvch7fboei"} 73 37 ``` 74 38 75 - Same as `getRecord` but accepts a full AT-URI instead of separate repo/collection/rkey params. The identifier in the AT-URI can be a handle or DID. Optional `cid` parameter. 39 + ## the hydration pattern 76 40 77 - ### get a record with proof (DAG-CBOR) 41 + Constellation/Spacedust return references, not records. Use Slingshot to hydrate: 78 42 79 - **Note: this endpoint is documented but may still be work in progress.** 80 - 81 - ``` 82 - GET /xrpc/com.atproto.sync.getRecord 83 - ?did=did:plc:... 84 - &collection=app.bsky.feed.post 85 - &rkey=3lwcmto4tck2h 86 - ``` 87 - Returns the record with cryptographic proof. Use this when you need to verify record authenticity. 88 - 89 - ## examples 90 - 91 - Resolve a handle and fetch their profile (python): 92 - ```python 93 - import httpx 94 - 95 - # resolve identity 96 - identity = httpx.get( 97 - "https://slingshot.microcosm.blue/xrpc/blue.microcosm.identity.resolveMiniDoc", 98 - params={"identifier": "zzstoatzz.io"}, 99 - ).json() 100 - 101 - # fetch profile record 102 - profile = httpx.get( 103 - "https://slingshot.microcosm.blue/xrpc/com.atproto.repo.getRecord", 104 - params={ 105 - "repo": identity["did"], 106 - "collection": "app.bsky.actor.profile", 107 - "rkey": "self", 108 - }, 109 - ).json() 110 - 111 - print(f"{identity['handle']}: {profile['value'].get('description', '')}") 112 - ``` 113 - 114 - Fetch a record by AT-URI directly (javascript): 115 - ```javascript 116 - const base = "https://slingshot.microcosm.blue/xrpc"; 117 - 118 - // option 1: resolve + getRecord (two calls) 119 - const identity = await fetch( 120 - `${base}/blue.microcosm.identity.resolveMiniDoc?identifier=zzstoatzz.io` 121 - ).then(r => r.json()); 122 - 123 - const record = await fetch( 124 - `${base}/com.atproto.repo.getRecord?repo=${identity.did}&collection=app.bsky.feed.post&rkey=3lwcmto4tck2h` 125 - ).then(r => r.json()); 126 - 127 - // option 2: getRecordByUri (one call, handles resolve internally) 128 - const record2 = await fetch( 129 - `${base}/blue.microcosm.repo.getRecordByUri?at_uri=${encodeURIComponent("at://zzstoatzz.io/app.bsky.feed.post/3lwcmto4tck2h")}` 130 - ).then(r => r.json()); 131 - ``` 43 + 1. Query Constellation → get DIDs/record references 44 + 2. Resolve identities via `resolveMiniDoc` → handles, PDS URLs 45 + 3. Fetch records via `getRecordByUri` or `getRecord` → full content 132 46 133 47 ## when to use slingshot vs PDS directly 134 48 135 - | scenario | use | 136 - |----------|-----| 137 - | reading public records | slingshot (faster, cached) | 138 - | reading records from Constellation/Spacedust results | slingshot (hydrate AT-URIs) | 139 - | identity resolution | slingshot (bi-directional verification, fast) | 140 - | writing records | PDS directly (or pdsx) | 141 - | authenticated operations | PDS directly (or pdsx) | 142 - | listing records in a collection | PDS directly (slingshot doesn't support listRecords) | 143 - 144 - ## the hydration pattern 145 - 146 - Constellation and Spacedust return references (AT-URIs, DIDs), not full records. The standard pattern: 147 - 148 - 1. Query Constellation for backlinks → get list of AT-URIs 149 - 2. Fetch each record from Slingshot → get full content 150 - 3. Resolve DIDs via resolveMiniDoc → get display names/handles 151 - 152 - ```python 153 - # get who liked a post 154 - backlinks = httpx.get( 155 - "https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinkDids", 156 - params={ 157 - "subject": post_uri, 158 - "source": "app.bsky.feed.like:subject.uri", 159 - "limit": 10, 160 - }, 161 - ).json() 49 + - **slingshot**: reading public records, resolving identities, hydrating Constellation results 50 + - **PDS directly**: writing records, listing records in a collection (`listRecords` is not supported by Slingshot — get the PDS URL from `resolveMiniDoc` and query it directly) 51 + - **pdsx**: CRUD operations, authenticated actions 162 52 163 - # hydrate each liker's identity 164 - for did in backlinks["linking_dids"]: 165 - identity = httpx.get( 166 - "https://slingshot.microcosm.blue/xrpc/blue.microcosm.identity.resolveMiniDoc", 167 - params={"identifier": did}, 168 - ).json() 169 - print(f"liked by @{identity['handle']}") 170 - ``` 53 + ## notes 171 54 172 - ## tips 173 - 174 - - Slingshot eagerly caches from the firehose, so most recent records are available instantly 175 - - Use `resolveMiniDoc` instead of `resolveHandle` when you need more than just the DID 176 - - Use `getRecordByUri` when you already have an AT-URI — saves you from parsing it apart 177 - - Slingshot does NOT support `listRecords` — for that, query the PDS directly (resolve PDS URL via resolveMiniDoc's `pds` field) 178 - - Batch your hydration calls when possible — Slingshot is fast but network round-trips add up 179 - - No authentication needed for any Slingshot endpoints 180 - - The `com.bad-example.*` namespace is being migrated to `blue.microcosm.*` — prefer the new paths 55 + - unauthenticated, no API key needed 56 + - `repo` param in `getRecord` accepts handles (resolved automatically) or DIDs 57 + - `com.bad-example.*` namespace is migrating to `blue.microcosm.*` — both work, prefer the new one 58 + - always bi-directionally verifies identities (stricter than spec requires) 59 + - check the live API docs at slingshot.microcosm.blue for the latest endpoints
+26 -99
skills/spacedust/SKILL.md
··· 6 6 7 7 # spacedust — real-time interactions firehose 8 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. 9 + Spacedust streams link events from the entire AT Protocol network over WebSocket, with client-side filtering. 10 10 11 - **Base URL:** `wss://spacedust.microcosm.blue` 11 + **Live docs:** https://spacedust.microcosm.blue 12 + **Source:** https://github.com/at-microcosm/microcosm-rs/tree/main/spacedust 12 13 13 14 ## connecting 14 15 15 16 ``` 16 - GET /subscribe?wantedSources=...&wantedSubjectDids=... 17 + wss://spacedust.microcosm.blue/subscribe?wantedSources=...&wantedSubjectDids=... 17 18 ``` 18 19 19 - Upgrades to WebSocket. All query parameters are repeatable for multiple values. 20 - 21 - ### filter parameters 20 + All filter params are repeatable. Sources use the same `collection:path` format as Constellation. 22 21 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) | 22 + ## filter params 30 23 31 - Sources use the same `collection:path` format as Constellation (e.g. `app.bsky.feed.like:subject.uri`). 24 + | parameter | what it filters | max values | 25 + |-----------|----------------|-----------| 26 + | `wantedSubjects` | specific AT-URIs | 50,000 | 27 + | `wantedSubjectDids` | DIDs (all interactions with their content) | 10,000 | 28 + | `wantedSubjectPrefixes` | URI/DID prefixes | 100 | 29 + | `wantedSources` | interaction types | 1,000 | 30 + | `instant` | bypass 21-second delay buffer | boolean | 32 31 33 - ### filter semantics 34 - 35 - - `wantedSubjects`, `wantedSubjectPrefixes`, and `wantedSubjectDids` are **OR** with each other — a link matches if it hits ANY of these. 36 - - `wantedSources` is **AND** with the subject filters — a link must match both a subject filter AND a source filter (if both are specified). 37 - - If no subject filters are set, all subjects match. If no source filter is set, all sources match. 38 - 39 - ### filter limits 40 - 41 - | parameter | max values | 42 - |-----------|-----------| 43 - | `wantedSubjects` | 50,000 | 44 - | `wantedSubjectDids` | 10,000 | 45 - | `wantedSubjectPrefixes` | 100 | 46 - | `wantedSources` | 1,000 | 32 + **Filter logic:** subject params (subjects, DIDs, prefixes) are **OR**. Sources are **AND** with subjects. So `wantedSubjectDids=X&wantedSources=app.bsky.feed.like:subject.uri` = "likes on X's content." 47 33 48 34 ## message format 49 - 50 - Server sends JSON messages: 51 35 52 36 ```json 53 37 { ··· 63 47 } 64 48 ``` 65 49 66 - - `operation` is `create` or `delete` 67 - - `source` tells you the type of interaction 68 - - `source_record` is the AT-URI of the record that created the link 69 - - `subject` is what was interacted with 50 + `operation` is `create` or `delete`. 70 51 71 52 ## dynamic filter updates 72 53 73 - Send a JSON message on an open connection to update filters without reconnecting: 54 + Send JSON on the open connection to replace filters without reconnecting: 74 55 75 56 ```json 76 - { 77 - "type": "options_update", 78 - "payload": { 79 - "wantedSubjectDids": ["did:plc:z72i7hdynmk6r22z27h6tvur"], 80 - "wantedSources": ["app.bsky.graph.follow:subject"] 81 - } 82 - } 57 + {"type": "options_update", "payload": {"wantedSubjectDids": ["did:plc:..."], "wantedSources": ["app.bsky.graph.follow:subject"]}} 83 58 ``` 84 59 85 - This replaces the current filters entirely. 86 - 87 60 ## the 21-second delay 88 61 89 - 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.). 62 + By default, events are held 21 seconds to filter out quickly-undone interactions (~1% of all). Set `instant=true` to bypass. 90 63 91 - Set `instant=true` to bypass this if you need zero-latency events and can handle the noise. 92 - 93 - ## examples 64 + ## quick examples 94 65 95 - Watch for new followers of a user (bash): 96 66 ```bash 67 + # watch for new followers of a user 97 68 websocat "wss://spacedust.microcosm.blue/subscribe?wantedSources=app.bsky.graph.follow:subject&wantedSubjectDids=did:plc:z72i7hdynmk6r22z27h6tvur" 98 69 ``` 99 70 100 - Watch for likes on a specific post (python): 101 - ```python 102 - import asyncio 103 - import websockets 104 - import json 105 - 106 - async def watch_likes(post_uri: str): 107 - url = f"wss://spacedust.microcosm.blue/subscribe?wantedSources=app.bsky.feed.like:subject.uri&wantedSubjects={post_uri}" 108 - async with websockets.connect(url) as ws: 109 - async for message in ws: 110 - event = json.loads(message) 111 - if event["kind"] == "link": 112 - link = event["link"] 113 - print(f"{link['operation']}: {link['source_record']}") 114 - ``` 115 - 116 - Watch for likes on a specific post (javascript): 117 71 ```javascript 72 + // live like counter 118 73 const ws = new WebSocket( 119 - `wss://spacedust.microcosm.blue/subscribe?wantedSources=app.bsky.feed.like:subject.uri&wantedSubjects=${encodeURIComponent(postUri)}` 74 + `wss://spacedust.microcosm.blue/subscribe?wantedSubjects=${encodeURIComponent(postUri)}&wantedSources=app.bsky.feed.like:subject.uri` 120 75 ); 121 - ws.onmessage = (event) => { 122 - const { kind, link } = JSON.parse(event.data); 123 - if (kind === "link") { 124 - console.log(`${link.operation}: ${link.source_record}`); 125 - } 76 + ws.onmessage = (e) => { 77 + const { link } = JSON.parse(e.data); 78 + if (link.operation === "create") likeCount++; 79 + else likeCount--; 126 80 }; 127 81 ``` 128 - 129 - Dynamically update filters (javascript): 130 - ```javascript 131 - // start watching follows 132 - ws.send(JSON.stringify({ 133 - type: "options_update", 134 - payload: { 135 - wantedSubjectDids: [userDid], 136 - wantedSources: ["app.bsky.graph.follow:subject"] 137 - } 138 - })); 139 - ``` 140 - 141 - ## use cases 142 - 143 - - **notifications**: subscribe to `wantedSubjectDids` for your users, filter by source type 144 - - **live counters**: increment/decrement counts on create/delete events 145 - - **activity feeds**: show real-time interaction stream for a user or post 146 - - **monitoring**: watch for interactions on specific records or from specific users 147 - 148 - ## tips 149 - 150 - - Use `wantedSubjectDids` for user-level monitoring (all interactions with a user's content) 151 - - Use `wantedSubjects` for record-level monitoring (interactions with specific posts) 152 - - Use `wantedSources` to filter interaction type (likes only, follows only, etc.) 153 - - Combine filters: `wantedSubjectDids` + `wantedSources` = "new followers of this user" 154 - - The delay buffer means Spacedust is not suitable for sub-second latency requirements unless you set `instant=true`
+6 -24
skills/ufos/SKILL.md
··· 1 1 --- 2 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. 3 + description: Query the UFOs API for AT Protocol lexicon timeseries statistics. Use when you need to discover what lexicons/apps exist in the network or track collection activity over time. 4 4 user-invocable: true 5 5 --- 6 6 7 7 # ufos — lexicon timeseries stats 8 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 9 + UFOs tracks every collection (lexicon) ever observed in the AT Protocol firehose with timeseries stats and sample records. 22 10 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 11 + **Web explorer:** https://ufos.microcosm.blue 12 + **API:** https://ufos-api.microcosm.blue 13 + **Source:** https://github.com/at-microcosm/microcosm-rs/tree/main/ufos 27 14 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 15 + Use it to discover what atproto apps exist beyond Bluesky, check if a lexicon is active, or understand what records look like in an unfamiliar collection. Start with the web explorer for interactive browsing.