claude up some atproto stuff
6
fork

Configure Feed

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

add wisp skill, tighten all skills for conciseness

remove code examples that teach Claude things it already knows
(fetch, Promise.all, WebSocket). keep only what it genuinely
doesn't have: endpoint URLs, param formats, filter logic,
decision guidance.

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

zzstoatzz e70785f2 df82cafc

+95 -195
+1 -1
.claude-plugin/plugin.json
··· 9 9 "repository": "https://tangled.org/zzstoatzz.io/protopack", 10 10 "homepage": "https://microcosm.blue", 11 11 "license": "MIT", 12 - "keywords": ["atproto", "bluesky", "microcosm", "pdsx", "decentralized"], 12 + "keywords": ["atproto", "bluesky", "microcosm", "pdsx", "wisp", "decentralized"], 13 13 "skills": "./skills/", 14 14 "mcpServers": "./.mcp.json" 15 15 }
+2
README.md
··· 32 32 | `/protopack:slingshot` | fast record fetching and identity resolution | 33 33 | `/protopack:ufos` | lexicon timeseries statistics and ecosystem discovery | 34 34 | `/protopack:pub-search` | search published writing across the atmosphere for prior art | 35 + | `/protopack:wisp` | deploy static sites to AT Protocol via wisp.place | 35 36 | `/protopack:app-patterns` | common patterns for building atproto apps | 36 37 37 38 ### bundled MCP servers ··· 63 64 - [pdsx](https://github.com/zzstoatzz/pdsx) — AT Protocol record operations 64 65 - [atproto-mcp](https://github.com/ashex/atproto-mcp) — AT Protocol documentation search 65 66 - [pub-search](https://pub-search.waow.tech) — atmosphere publishing search 67 + - [wisp.place](https://wisp.place) — static site hosting on AT Protocol by [@nekomimi.pet](https://bsky.app/profile/nekomimi.pet)
+9 -43
skills/app-patterns/SKILL.md
··· 14 14 2. **Slingshot** — hydrate references into full records and identities 15 15 3. **Spacedust** — real-time updates via WebSocket 16 16 17 - ## pattern: engagement counters 18 - 19 - Get initial counts from Constellation, then keep them live with Spacedust: 20 - 21 - ```javascript 22 - // initial counts (parallel) 23 - const [likes, reposts] = await Promise.all([ 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()), 26 - ]); 27 - 28 - // live updates 29 - const ws = new WebSocket( 30 - `wss://spacedust.microcosm.blue/subscribe?wantedSubjects=${encodeURIComponent(postUri)}&wantedSources=app.bsky.feed.like:subject.uri&wantedSources=app.bsky.feed.repost:subject.uri` 31 - ); 32 - ws.onmessage = (e) => { 33 - const { link } = JSON.parse(e.data); 34 - // increment/decrement based on link.operation and link.source 35 - }; 36 - ``` 37 - 38 - ## pattern: identity-first fetch 39 - 40 - Almost everything starts with resolving a handle: 41 - 42 - ```javascript 43 - const base = "https://slingshot.microcosm.blue/xrpc"; 17 + Typical flow: get initial counts from Constellation, resolve identities/records via Slingshot, then open a Spacedust WebSocket to keep things live. 44 18 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()); 19 + For writes and `listRecords`, go through the user's PDS directly (get PDS URL from Slingshot's `resolveMiniDoc`). 47 20 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()); 50 - 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()); 53 - ``` 54 - 55 - ## working with pdsx and atproto-mcp 21 + ## bundled tools 56 22 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. 23 + - **pdsx** — record CRUD (create, update, delete), auth, batch operations 24 + - **atproto-mcp** — search atproto docs, lexicon schemas, cookbook examples 25 + - **pub-search** — search published writing across atmosphere platforms for prior art 60 26 61 27 ## notes 62 28 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 65 - - for lexicons beyond `app.bsky.*`, use UFOs to discover what exists, then Constellation to query backlinks 29 + - all Microcosm services are unauthenticated 30 + - for lexicons beyond `app.bsky.*`, use UFOs to discover what exists 31 + - check each service's live docs for the latest API details
+13 -36
skills/constellation/SKILL.md
··· 6 6 7 7 # constellation — global backlink index 8 8 9 - Constellation indexes every link in the AT Protocol network. Use it to answer "who interacted with this?" for any atproto record. 9 + Indexes every link in the AT Protocol network. Answers "who interacted with this?" for any record. 10 10 11 - **Live API docs (interactive, try requests in browser):** https://constellation.microcosm.blue 11 + **Live API docs (interactive):** https://constellation.microcosm.blue 12 12 **Source:** https://github.com/at-microcosm/microcosm-rs/tree/main/constellation 13 13 14 - ## key concept: the source format 14 + ## the source format 15 15 16 - All queries use `source` in the format `{collection}:{path}` (path omits leading dot): 16 + All queries use `source` as `{collection}:{path}` (path omits leading dot): 17 17 18 18 | interaction | source | 19 19 |-------------|--------| ··· 25 25 26 26 Works with any lexicon, not just Bluesky. 27 27 28 - ## what you can do 28 + ## endpoints 29 29 30 - **Count interactions** — `getBacklinksCount` with `subject` + `source`: 30 + **Count interactions** — `getBacklinksCount`: 31 31 ```bash 32 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 33 # {"total":16} 34 34 ``` 35 35 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"} 40 - ``` 36 + **List who interacted** — `getBacklinks` returns `{total, records: [{did, collection, rkey}], cursor}`. 41 37 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 46 - ``` 38 + **Explore all link types** — `/links/all?target={did}` shows every collection+path linking to a DID with counts. 47 39 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 . 51 - ``` 40 + **Distinct DIDs** — `/links/distinct-dids` (legacy; XRPC `getBacklinkDids` is in source but not yet deployed). Uses dot-prefixed paths (`.subject.uri`) and `target` param. 52 41 53 - ## getting counts for multiple posts 54 - 55 - No batch endpoint — fire parallel requests: 56 - ```javascript 57 - const counts = await Promise.all( 58 - postUris.flatMap(uri => [ 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()), 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()), 61 - ]) 62 - ); 63 - ``` 42 + No batch endpoint — use parallel requests for multiple subjects. 64 43 65 44 ## notes 66 45 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 46 + - unauthenticated, default limit 100, max 1000, pagination via opaque hex `cursor` 47 + - `subject` can be an AT-URI or bare DID 48 + - check the live docs for the latest — the API is evolving
+16 -50
skills/pub-search/SKILL.md
··· 6 6 7 7 # pub-search — search the atmosphere's published writing 8 8 9 - pub-search indexes all content published on AT Protocol publishing platforms. When people build things on atproto, they write about it — announcements, tutorials, deep dives, design docs. This is how you find prior art. 10 - 11 - **Indexed platforms:** Leaflet, Whitewind, Pckt, Offprint, Greengale, and other standard.site publishers. 9 + Indexes content from Leaflet, Whitewind, Pckt, Offprint, Greengale, and other standard.site publishers. Use it to find prior art when building on atproto. 12 10 13 11 ## MCP tools 14 12 15 - pub-search is bundled as an MCP server with this plugin. The tools are available directly: 13 + Available directly via the bundled MCP server: 16 14 17 15 | tool | what it does | 18 16 |------|-------------| 19 - | `search` | keyword search with BM25 ranking + recency boost (~9ms) | 20 - | `search_semantic` | meaning-based vector search — finds related content even without keyword overlap (~350ms) | 21 - | `search_hybrid` | combines keyword + semantic via reciprocal rank fusion — best of both | 22 - | `get_document` | fetch the full text of a document by AT-URI | 23 - | `find_similar` | find semantically related documents to a given URI | 17 + | `search` | keyword search with BM25 ranking (~9ms) | 18 + | `search_semantic` | meaning-based vector search — finds related content without keyword overlap (~350ms) | 19 + | `search_hybrid` | combines both via reciprocal rank fusion — best default for research | 20 + | `get_document` | fetch full text by AT-URI | 21 + | `find_similar` | find related documents to a given URI | 24 22 | `get_tags` | list all tags with counts | 25 - | `get_stats` | index statistics and performance metrics | 26 - | `get_popular` | popular recent search queries | 23 + | `get_stats` | index statistics | 27 24 28 - ## when to use which search mode 25 + ## choosing a search mode 29 26 30 - - **keyword** (`search`): fast, good when you know the terms. "microcosm constellation API" 31 - - **semantic** (`search_semantic`): finds related content by meaning. "how to build real-time features on decentralized social" finds posts about Spacedust even if they don't use those exact words 32 - - **hybrid** (`search_hybrid`): when you want thoroughness. combines both, results annotated with source ("keyword", "semantic", or "keyword+semantic") 27 + - **keyword**: fast, use when you know the terms 28 + - **semantic**: slower, finds conceptually related content even without matching words 29 + - **hybrid**: best for research — catches both exact and related matches 33 30 34 31 ## filters 35 32 36 - - `platform`: filter to a specific platform ("leaflet", "whitewind", "pckt", "offprint", "greengale") 37 - - `tag`: filter by tag (keyword search only) 38 - - `since`: ISO date string for recency filtering (keyword search only) 39 - - `limit`: max results (default varies by tool) 33 + `platform`, `tag`, `since` (ISO date), `limit` — available on keyword search. Semantic and hybrid support `platform` and `limit`. 40 34 41 35 ## research workflow 42 36 43 - When building something on atproto, search for prior art first: 44 - 45 - 1. **search for the concept**: `search_hybrid("backlink index atproto")` — find writeups about similar features 46 - 2. **read the full posts**: `get_document(uri)` on the most relevant results 47 - 3. **explore related content**: `find_similar(uri)` to discover adjacent ideas 48 - 4. **check tags**: `get_tags()` to see what topics are well-covered 49 - 50 - ## examples 51 - 52 - Find posts about building with Microcosm: 53 - ``` 54 - search_hybrid("microcosm constellation building apps") 55 - ``` 56 - 57 - Find tutorials about AT Protocol identity: 58 - ``` 59 - search_semantic("how identity resolution works in atproto") 60 - ``` 61 - 62 - Find recent Leaflet posts about a topic: 63 - ``` 64 - search("oauth atproto", platform="leaflet", since="2025-01-01") 65 - ``` 66 - 67 - ## tips 68 - 69 - - semantic search is slower but finds things keyword search misses — use it when exploring a concept 70 - - hybrid search is the best default for research — it catches both exact matches and related content 71 - - use `get_document` to read full articles, not just snippets 72 - - `find_similar` is great for expanding your search once you find one good result 73 - - results include a `url` field — useful for linking to sources 37 + 1. `search_hybrid` for the concept 38 + 2. `get_document` on the best results to read full text 39 + 3. `find_similar` to expand from a good result
+11 -30
skills/slingshot/SKILL.md
··· 6 6 7 7 # slingshot — edge record and identity cache 8 8 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. 9 + Fast cache of atproto records and identities, pre-warmed from the firehose. Use instead of hitting individual PDS instances for reads. 10 10 11 11 **Live API docs (interactive, OpenAPI spec):** https://slingshot.microcosm.blue 12 12 **Source:** https://github.com/at-microcosm/microcosm-rs/tree/main/slingshot 13 13 14 - ## what you can do 14 + ## endpoints 15 15 16 - **Resolve an identity** — get DID, handle, PDS URL, and signing key in one call: 16 + **Resolve identity** — DID, handle, PDS URL, signing key in one call: 17 17 ```bash 18 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..."} 20 19 ``` 21 20 22 - **Fetch a record by AT-URI** — one param instead of repo/collection/rkey: 21 + **Fetch record by AT-URI:** 23 22 ```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":{...}} 23 + curl "https://slingshot.microcosm.blue/xrpc/blue.microcosm.repo.getRecordByUri?at_uri=at://did:plc:.../app.bsky.actor.profile/self" | jq . 26 24 ``` 27 25 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 . 31 - ``` 26 + **Standard atproto getRecord** also works — `repo` accepts handles or DIDs. 32 27 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"} 37 - ``` 38 - 39 - ## the hydration pattern 40 - 41 - Constellation/Spacedust return references, not records. Use Slingshot to hydrate: 42 - 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 28 + **Resolve handle to DID only** — `com.atproto.identity.resolveHandle` 46 29 47 30 ## when to use slingshot vs PDS directly 48 31 49 32 - **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) 33 + - **PDS directly**: writing records, `listRecords` (not supported by Slingshot — get the PDS URL from `resolveMiniDoc`) 51 34 - **pdsx**: CRUD operations, authenticated actions 52 35 53 36 ## notes 54 37 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 38 + - unauthenticated 39 + - `com.bad-example.*` namespace is migrating to `blue.microcosm.*` — both work, prefer new 40 + - check the live API docs for the latest endpoints
+7 -35
skills/spacedust/SKILL.md
··· 6 6 7 7 # spacedust — real-time interactions firehose 8 8 9 - Spacedust streams link events from the entire AT Protocol network over WebSocket, with client-side filtering. 9 + Streams link events from the entire AT Protocol network over WebSocket with client-side filtering. 10 10 11 11 **Live docs:** https://spacedust.microcosm.blue 12 12 **Source:** https://github.com/at-microcosm/microcosm-rs/tree/main/spacedust ··· 17 17 wss://spacedust.microcosm.blue/subscribe?wantedSources=...&wantedSubjectDids=... 18 18 ``` 19 19 20 - All filter params are repeatable. Sources use the same `collection:path` format as Constellation. 20 + Sources use the same `collection:path` format as Constellation. 21 21 22 22 ## filter params 23 23 ··· 29 29 | `wantedSources` | interaction types | 1,000 | 30 30 | `instant` | bypass 21-second delay buffer | boolean | 31 31 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." 32 + **Filter logic:** subject params are **OR**. Sources are **AND** with subjects. So `wantedSubjectDids=X&wantedSources=app.bsky.feed.like:subject.uri` = "likes on X's content." 33 33 34 34 ## message format 35 35 36 36 ```json 37 - { 38 - "kind": "link", 39 - "origin": "live", 40 - "link": { 41 - "operation": "create", 42 - "source": "app.bsky.feed.like:subject.uri", 43 - "source_record": "at://did:plc:.../app.bsky.feed.like/3lv4ouczo2b2a", 44 - "source_rev": "...", 45 - "subject": "at://did:plc:.../app.bsky.feed.post/3lgwdn7vd722r" 46 - } 47 - } 37 + {"kind":"link","origin":"live","link":{"operation":"create","source":"app.bsky.feed.like:subject.uri","source_record":"at://did:plc:.../app.bsky.feed.like/3lv4ouczo2b2a","subject":"at://did:plc:.../app.bsky.feed.post/3lgwdn7vd722r"}} 48 38 ``` 49 39 50 40 `operation` is `create` or `delete`. ··· 57 47 {"type": "options_update", "payload": {"wantedSubjectDids": ["did:plc:..."], "wantedSources": ["app.bsky.graph.follow:subject"]}} 58 48 ``` 59 49 60 - ## the 21-second delay 61 - 62 - By default, events are held 21 seconds to filter out quickly-undone interactions (~1% of all). Set `instant=true` to bypass. 63 - 64 - ## quick examples 50 + ## notes 65 51 66 - ```bash 67 - # watch for new followers of a user 68 - websocat "wss://spacedust.microcosm.blue/subscribe?wantedSources=app.bsky.graph.follow:subject&wantedSubjectDids=did:plc:z72i7hdynmk6r22z27h6tvur" 69 - ``` 70 - 71 - ```javascript 72 - // live like counter 73 - const ws = new WebSocket( 74 - `wss://spacedust.microcosm.blue/subscribe?wantedSubjects=${encodeURIComponent(postUri)}&wantedSources=app.bsky.feed.like:subject.uri` 75 - ); 76 - ws.onmessage = (e) => { 77 - const { link } = JSON.parse(e.data); 78 - if (link.operation === "create") likeCount++; 79 - else likeCount--; 80 - }; 81 - ``` 52 + - events are buffered 21 seconds to filter quickly-undone interactions; `instant=true` bypasses this 53 + - unauthenticated, no API key needed
+36
skills/wisp/SKILL.md
··· 1 + --- 2 + name: wisp 3 + description: Deploy static sites to the AT Protocol via wisp.place. Use when building or deploying a static site, managing wisp.place domains, or the user mentions wisp.place hosting. 4 + user-invocable: true 5 + --- 6 + 7 + # wisp — static site hosting on AT Protocol 8 + 9 + Stores static sites as blobs on the user's PDS with a `place.wisp.fs` manifest record. The `wispctl` CLI handles build-artifact upload, manifest creation, and domain management. 10 + 11 + **Source:** https://tangled.org/nekomimi.pet/wisp.place-monorepo 12 + **CLI binaries:** https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/ 13 + 14 + ## deploy 15 + 16 + ```bash 17 + wispctl deploy <handle> --path ./build --site my-site 18 + ``` 19 + 20 + Use `--spa` for single-page apps. Redeployments only upload changed files. Sites go live at `https://sites.wisp.place/{did}/{site-name}`. 21 + 22 + ## domains 23 + 24 + ```bash 25 + wispctl domain claim-subdomain <handle> --subdomain alice # alice.wisp.place 26 + wispctl domain claim <handle> --domain example.com # returns DNS instructions 27 + wispctl domain add-site <handle> --domain example.com --site my-site 28 + ``` 29 + 30 + ## auth 31 + 32 + The CLI opens a browser for AT Protocol OAuth on first use. Sessions are cached. Don't automate the auth flow — ensure the user has run wispctl once interactively. 33 + 34 + ## other commands 35 + 36 + `wispctl list sites`, `wispctl list domains`, `wispctl domain status`, `wispctl serve` (local preview). All accept `--json`.