claude up some atproto stuff
9
fork

Configure Feed

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

fix skills against source code — restore endpoints, correct limits, add new APIs

constellation:
- restore getBacklinkDids XRPC endpoint (it exists in source)
- add getManyToMany endpoint (list items, not just counts)
- fix limits back to 100/1000 (source constants, web docs were stale)
- confirm cursor support on all paginated endpoints
- document /links/all endpoint for exploring all link types

slingshot:
- add blue.microcosm.identity.resolveMiniDoc (new stable path)
- add blue.microcosm.repo.getRecordByUri (ergonomic AT-URI endpoint)
- note com.atproto.sync.getRecord is work in progress
- note listRecords is not supported
- migrate examples from com.bad-example to blue.microcosm namespace

spacedust:
- add filter limits (subjects 50k, DIDs 10k, prefixes 100, sources 1k)
- document AND/OR semantics: subject filters OR each other, AND with sources

app-patterns:
- migrate to blue.microcosm namespace in examples

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

zzstoatzz e28b28aa e3f5f002

+153 -36
+2 -2
skills/app-patterns/SKILL.md
··· 61 61 62 62 # 1. resolve identity 63 63 identity = httpx.get( 64 - f"{slingshot}/com.bad-example.identity.resolveMiniDoc", 64 + f"{slingshot}/blue.microcosm.identity.resolveMiniDoc", 65 65 params={"identifier": handle}, 66 66 ).json() 67 67 ··· 123 123 124 124 # resolve handle → DID + PDS 125 125 identity = httpx.get( 126 - f"{slingshot}/com.bad-example.identity.resolveMiniDoc", 126 + f"{slingshot}/blue.microcosm.identity.resolveMiniDoc", 127 127 params={"identifier": handle}, 128 128 ).json() 129 129
+84 -21
skills/constellation/SKILL.md
··· 29 29 30 30 This works with any lexicon — not just `app.bsky.*`. If an app stores a link in a record field, Constellation indexes it. 31 31 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`). 33 + 32 34 ## XRPC endpoints 33 35 34 - ### get backlinks 36 + ### getBacklinks — list records linking to a target 35 37 ``` 36 38 GET /xrpc/blue.microcosm.links.getBacklinks 37 39 ?subject=at://did:plc:.../app.bsky.feed.post/3lwcmto4tck2h 38 40 &source=app.bsky.feed.like:subject.uri 39 41 &limit=100 42 + &cursor=<hex> 40 43 ``` 41 44 42 45 Parameters: 43 46 - `subject` (required) — the target AT-URI or DID being linked to. must be URL-encoded. 44 47 - `source` (required) — collection:path format. 45 48 - `did` (optional, repeatable) — filter results to specific DIDs. repeat for multiple: `&did=did:plc:...&did=did:plc:...` 46 - - `limit` (optional) — default 16, max 100. 47 - - `reverse` (optional) — boolean, return in reverse order. 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. 48 52 49 - Returns `{ items: [...], cursor: "..." }`. Items contain the source record AT-URI and metadata. 53 + Returns `{ total, records: [...], cursor }`. 50 54 51 - ### count backlinks 55 + ### getBacklinksCount — count links to a target 52 56 ``` 53 57 GET /xrpc/blue.microcosm.links.getBacklinksCount 54 58 ?subject=at://did:plc:.../app.bsky.feed.post/3lwcmto4tck2h ··· 61 65 62 66 Returns `{ total: 42 }`. 63 67 64 - ### many-to-many counts 68 + ### getBacklinkDids — list distinct DIDs linking to a target 65 69 ``` 66 - GET /xrpc/blue.microcosm.links.getManyToManyCounts 70 + GET /xrpc/blue.microcosm.links.getBacklinkDids 67 71 ?subject=at://did:plc:.../app.bsky.feed.post/3lwcmto4tck2h 68 72 &source=app.bsky.feed.like:subject.uri 69 - &pathToOther=otherThing.uri 73 + &limit=100 74 + ``` 75 + 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 70 93 ``` 71 94 72 - This is for records that link to TWO things — e.g. a list item linking to both a list and a user. It is NOT a batch endpoint for multiple subjects. 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. 103 + 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 + ``` 73 116 74 117 Parameters: 75 - - `subject` (required) — the target. 118 + - `subject` (required) — the primary target. 76 119 - `source` (required) — primary link specification. 77 - - `pathToOther` (required) — path to the secondary link field in the record. 78 - - `did` (optional, repeatable) — filter by DIDs. 79 - - `otherSubject` (optional, repeatable) — filter secondary links to specific subjects. 80 - - `limit` (optional) — default 16, max 100. 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 }`. 81 127 82 128 ## getting engagement counts for multiple posts 83 129 ··· 95 141 96 142 This is fast — Constellation responses are typically sub-10ms. 97 143 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 + 98 151 ## legacy endpoints 99 152 100 153 These older REST endpoints still work but prefer the XRPC ones above: 101 154 102 - - `GET /links` — list backlinks. params: `target`, `collection`, `path`, `did`, `limit`, `reverse` 103 - - `GET /links/count` — count backlinks. params: `target`, `collection`, `path`, `cursor` 104 - - `GET /links/distinct-dids` — unique DIDs linking to target. params: `target`, `collection`, `path` 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` 105 159 - `GET /links/all` — all sources with links to target, including counts. params: `target` 106 - - `GET /links/count/distinct-dids` — count distinct DIDs. params: `target`, `collection`, `path`, `cursor` 160 + - `GET /links/all/count` (deprecated, use /links/all) — all link counts. params: `target` 107 161 108 - Legacy uses `target`, `collection`, `path` as separate params instead of `subject` + `source`. 162 + Legacy endpoints use dot-prefixed paths (`.subject.uri`) and `target` instead of `subject`. 109 163 110 164 ## examples 111 165 ··· 135 189 const { total } = await fetch(url).then(r => r.json()); 136 190 ``` 137 191 138 - Get followers of a user: 192 + Get followers of a user (paginated): 139 193 ```bash 140 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 }, ... } 141 203 ``` 142 204 143 205 ## tips ··· 145 207 - Always use the full AT-URI for `subject` when querying about a specific record 146 208 - For user-level queries (followers, blocks), use just the DID as subject 147 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) 148 211 - Constellation is read-only and unauthenticated — no API key needed 149 - - Default limit is 16, max is 100. Always set `limit` explicitly if you want more results. 212 + - The API is marked unstable — endpoints may change. The XRPC endpoints are the preferred stable path.
+52 -13
skills/slingshot/SKILL.md
··· 16 16 ``` 17 17 GET /xrpc/com.atproto.identity.resolveHandle?handle=zzstoatzz.io 18 18 ``` 19 - Returns `{ "did": "did:plc:..." }`. Bi-directionally verified (both DNS/well-known and DID document checked). 19 + Returns `{ "did": "did:plc:..." }`. Always bi-directionally verified (both DNS/well-known and DID document checked) — stricter than the spec requires. 20 20 21 21 ### resolve identity mini-doc 22 + 23 + Preferred path (stabilizing): 24 + ``` 25 + GET /xrpc/blue.microcosm.identity.resolveMiniDoc?identifier=zzstoatzz.io 26 + ``` 27 + 28 + Legacy alias (still works): 22 29 ``` 23 30 GET /xrpc/com.bad-example.identity.resolveMiniDoc?identifier=zzstoatzz.io 24 31 ``` 32 + 25 33 Returns a compact identity bundle: 26 34 ```json 27 35 { 28 36 "did": "did:plc:...", 29 37 "handle": "zzstoatzz.io", 30 - "pds": "https://...", 38 + "pds": "https://pds.zzstoatzz.io", 31 39 "signing_key": "zQ3sh..." 32 40 } 33 41 ``` 34 42 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. 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. 36 44 37 45 ## record fetching 38 46 ··· 43 51 &collection=app.bsky.feed.post 44 52 &rkey=3lwcmto4tck2h 45 53 ``` 46 - Returns the record value as JSON. Same interface as the standard atproto endpoint but served from cache. 54 + 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 67 + ``` 68 + 69 + Legacy alias: 70 + ``` 71 + GET /xrpc/com.bad-example.repo.getUriRecord 72 + ?at_uri=at://did:plc:.../app.bsky.feed.post/3lwcmto4tck2h 73 + ``` 74 + 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. 47 76 48 77 ### get a record with proof (DAG-CBOR) 78 + 79 + **Note: this endpoint is documented but may still be work in progress.** 80 + 49 81 ``` 50 82 GET /xrpc/com.atproto.sync.getRecord 51 83 ?did=did:plc:... ··· 62 94 63 95 # resolve identity 64 96 identity = httpx.get( 65 - "https://slingshot.microcosm.blue/xrpc/com.bad-example.identity.resolveMiniDoc", 97 + "https://slingshot.microcosm.blue/xrpc/blue.microcosm.identity.resolveMiniDoc", 66 98 params={"identifier": "zzstoatzz.io"}, 67 99 ).json() 68 100 ··· 79 111 print(f"{identity['handle']}: {profile['value'].get('description', '')}") 80 112 ``` 81 113 82 - Resolve and fetch (javascript): 114 + Fetch a record by AT-URI directly (javascript): 83 115 ```javascript 84 116 const base = "https://slingshot.microcosm.blue/xrpc"; 85 117 118 + // option 1: resolve + getRecord (two calls) 86 119 const identity = await fetch( 87 - `${base}/com.bad-example.identity.resolveMiniDoc?identifier=zzstoatzz.io` 120 + `${base}/blue.microcosm.identity.resolveMiniDoc?identifier=zzstoatzz.io` 88 121 ).then(r => r.json()); 89 122 90 123 const record = await fetch( 91 124 `${base}/com.atproto.repo.getRecord?repo=${identity.did}&collection=app.bsky.feed.post&rkey=3lwcmto4tck2h` 92 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()); 93 131 ``` 94 132 95 133 ## when to use slingshot vs PDS directly ··· 101 139 | identity resolution | slingshot (bi-directional verification, fast) | 102 140 | writing records | PDS directly (or pdsx) | 103 141 | authenticated operations | PDS directly (or pdsx) | 104 - | cryptographic verification | slingshot's sync.getRecord | 142 + | listing records in a collection | PDS directly (slingshot doesn't support listRecords) | 105 143 106 144 ## the hydration pattern 107 145 ··· 114 152 ```python 115 153 # get who liked a post 116 154 backlinks = httpx.get( 117 - "https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinks", 155 + "https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinkDids", 118 156 params={ 119 157 "subject": post_uri, 120 158 "source": "app.bsky.feed.like:subject.uri", ··· 123 161 ).json() 124 162 125 163 # 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] 164 + for did in backlinks["linking_dids"]: 129 165 identity = httpx.get( 130 - "https://slingshot.microcosm.blue/xrpc/com.bad-example.identity.resolveMiniDoc", 166 + "https://slingshot.microcosm.blue/xrpc/blue.microcosm.identity.resolveMiniDoc", 131 167 params={"identifier": did}, 132 168 ).json() 133 169 print(f"liked by @{identity['handle']}") ··· 137 173 138 174 - Slingshot eagerly caches from the firehose, so most recent records are available instantly 139 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) 140 178 - Batch your hydration calls when possible — Slingshot is fast but network round-trips add up 141 179 - No authentication needed for any Slingshot endpoints 180 + - The `com.bad-example.*` namespace is being migrated to `blue.microcosm.*` — prefer the new paths
+15
skills/spacedust/SKILL.md
··· 30 30 31 31 Sources use the same `collection:path` format as Constellation (e.g. `app.bsky.feed.like:subject.uri`). 32 32 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 | 47 + 33 48 ## message format 34 49 35 50 Server sends JSON messages: