npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@volter/twin-bluesky

v0.1.37

Published

Local Bluesky twin (an account's PDS over XRPC: app-password sessions, service-auth tokens, post/follow/profile records with facets and image, video or link-card embeds, blob upload and ranged getBlob; the App View's profile, author feed and thread; the v

Readme

@volter/twin-bluesky — an account's posts on Bluesky

A Bluesky account through its PDS (sessions from an app password, service-auth tokens, repo records and blobs over XRPC), the App View reads bsky.app draws a profile, a feed and a thread from, and Bluesky's video service — with a bsky.app-style mirror to preview each post as bsky.app shows it before a deploy sends it — offline, against local state.

bun packages/twin/bluesky/src/cli.ts serve --root /tmp/world     # the XRPC surface
bun packages/twin/bluesky/src/cli.ts mirror --root /tmp/world    # bsky.app, on the same state

The official client (@atproto/api) reaches it through the hosts on the descriptor: bsky.social and *.host.bsky.network (XRPC), api.bsky.app / public.api.bsky.app (actor and feed reads), video.bsky.app (the video service) and cdn.bsky.app (/img/). The video service and the CDN are other origins at Bluesky; the twin serves them under its own base at /video.bsky.app/… and /cdn.bsky.app/…, and a request the World routed from the real host lands there.

bsky.app itself is claimed for its two public pages, the ones an app links a person to once it has posted (Postiz: https://bsky.app/profile/<did>/post/<rkey>): /profile/<did or handle>/post/<rkey> (the post: author, text with its facets, image / video / link-card embed, full time) and /profile/<did or handle> (the profile header over its posts, newest first). They are drawn server-side from the App View this twin serves (src/bluesky-web.ts), with no session, as the public App View answers. A post or actor the twin does not hold is a 200 page saying "Post not found" / "Profile not found", as bsky.app (an SPA) answers it (observed 2026-09-28; src/bluesky-web.ts has the requests). A link facet or link card whose URI is not http(s) is drawn as text, never linked. Media loads from cdn.bsky.app, the video service's poster and the PDS's getBlob.

Coverage

| Area | What the twin serves | |---|---| | sessions | createSession with a handle, DID or email and an app password (scope com.atproto.appPass) or the account password (com.atproto.access); every password session carries the email, as the PDS answers it: HS256 access (120 min) and refresh (90 days) JWTs, the DID and a DID document naming the PDS endpoint (the twin's base). refreshSession, getSession. Expired tokens are 400 ExpiredToken, others InvalidToken; no bearer is 401 AuthMissing; a bad password 401 "Invalid identifier or password"; the Authorization header is parsed as the PDS parses it (DPoP / OAuth and Basic refused by name). resolveHandle for the handles the twin holds. | | service auth | getServiceAuth (aud, lxm, exp with the PDS's BadExpiration rules): a JWT with iss, aud, exp, lxm, jti, signed by the repo key the DID document names. Bluesky signs ES256K; WebCrypto has no secp256k1, so the twin's repo key is P-256 (ES256), the other curve atproto accepts. | | records | createRecord, putRecord, deleteRecord, getRecord, listRecords for app.bsky.feed.post (text, byte-indexed link / mention / tag facets, langs, tags, a reply, an images, video or external embed), app.bsky.graph.follow and app.bsky.actor.profile/self (name, bio, pronouns, website, self-labels, avatar, banner; swapRecord honoured as upsertProfile sends it), each validated against its lexicon. Following a subject again replaces the earlier follow, as the PDS's backlink check does. Keys are TIDs of the write's world instant, never reissued; the CID is the record's DAG-CBOR CID (it matches a reference encoder). A blob a record names must be one the account uploaded (400 BlobNotFound). Deleting what is not there answers 200 with no commit, as the PDS does. | | blobs | uploadBlob (up to the PDS's 52,428,800 bytes; the CID is the raw sha2-256 CID of the bytes) and com.atproto.sync.getBlob for a blob a live record names (a temporary upload is "Blob not found"), with HTTP Range (206/416; an open-ended range capped at 8 MiB, longer answers streamed in 8 MiB reads). | | App View | getProfile (counts computed from the follow and post records the twin holds, never told), getAuthorFeed (newest first, cursor, the posts_* filters), getPostThread (parents and replies). Views carry embeds as bsky.app receives them: video#view (playlist and thumbnail on the video host), images#view (thumb and fullsize on the CDN), external#view. The CDN serves the stored image as it is (no resizing); the video thumbnail is a stand-in poster (the twin decodes no frames); the HLS playlist is not modelled — the file is served by getBlob. | | video service | uploadVideo (a service token for the PDS's did:web and com.atproto.repo.uploadBlob, did and name in the query, video/mp4 bytes), getJobStatus (ENCODING → SCANNING → UPLOADING → COMPLETED with the blob, one second of world time each — a frozen World clock holds a job where it is), getUploadLimits (25 videos / 10 GB a day less today's jobs; an unconfirmed email cannot upload). The same bytes again answer 409 already_exists with the job. A file that is not an MP4, or over ten minutes, fails the job (validation_failure). | | errors | XRPC's {error, message}; 501 MethodNotImplemented for an unknown method; a real Bluesky method or feature the twin does not model is refused by name as 501 [twin gap] …, never answered as a success; 405 under --read-only. | | connector | Perform (the kernel's exchange strategy trades the sealed handle + app password at createSession; the pack never holds either): the World's account for the root is the one of the root's DID, or of its handle under a DID the World minted (translated to the root's DID at the boundary, both ways); only that account crosses — another account the World holds performs as nothing; an account the vendor does not know (asked through getProfile) is kept out of what crosses: a reply into its thread is refused before any bytes go out, a follow of it crosses nothing, a mention of it is dropped, while an account seeded under a real DID crosses as written; a World holding no account for the root fails the entry loudly; deletes read the key first and never remove a different record; a reply to a post that is not at Bluesky is refused (settled — the queue moves on) and a pin of one is left out; posts and follows cross as createRecord under their local key, read first so a retry adopts rather than posts twice; a post deleted before the deploy never crosses (one whose create already crossed is adopted, then deleted); the video's service token lasts 30 minutes of the vendor's clock; deletes as deleteRecord on the repo getSession names, the profile as putRecord (and its deletion as deleteRecord). Blobs cross first — images through uploadBlob; a video through the video service with a service token, presigned (another origin; sent without the session), then getJobStatus until COMPLETED, bounded at 120 s (a retryable failure past it). Refresh: the account, its profile record, and every post and follow record (listRecords, with the App View's indexedAt); observed under the World's account for the root (a record the translation changes is content-addressed anew); a World holding accounts but none of the root's DID or current handle is refused loudly, never forked into a second account. Both charge the pack's rate budget, one ledger per sealed credential. | | mirror | bsky.app over the twin's own XRPC (every answer's HTTP Date is the World's instant): sign in with the handle and an app password (an ended session, or the Sign out button, returns to the form); the profile (banner, avatar, name, handle, counts, bio, tabs) over its feed with the inline video player (poster, controls, the file from getBlob), images and link cards; a post's thread page. Relative times read that clock. | | /_twin/* | Twin-only control plane: seed an account (handle, DID, email, password), an app password on it, and an access token by value. Passwords and tokens land in the log only as SHA-256s under _ bookkeeping types. |

src/bluesky-capabilities.ts is the denominator: 121 capabilities, 68 done. What is not modelled is enumerated there as a todo — likes, reposts and quotes, the multipart video upload bsky.app now uses, OAuth, the timeline and feed generators, lists, blocks and mutes, notifications, DID resolution, app-password management, account creation, applyWrites, sync of whole repos and the firehose, the HLS playlist, labels and moderation, chat, and the mirror's composer — not omitted.

The rate budget (src/bluesky-budget.ts): Bluesky publishes 3000 requests / 5 min per IP, 30 createSession / 5 min per account and 5,000 write points an hour; the declaration stays at the kernel fallback's shape and prices writes by Bluesky's points (create 3, update 2, delete 1).

No official Python SDK exists (atproto on PyPI is a community SDK), so adoption claims none.