Skip to main content

pdsx

general-purpose cli for atproto record operations

📚 documentation

installation

uv add pdsx
# or run directly
uvx pdsx --help
# or from GitHub (latest)
uvx --from git+https://github.com/zzstoatzz/pdsx pdsx --help

quick start

important: flags like -r, --handle, --password go BEFORE the command (ls, get, etc.)

# read anyone's posts (no auth needed)
uvx pdsx -r zzstoatzz.io ls app.bsky.feed.post -o json | jq -r '.[].text'

# update your bio (requires auth)
export ATPROTO_HANDLE=your.handle ATPROTO_PASSWORD=your-app-password
uvx pdsx edit app.bsky.actor.profile/self description='new bio'

features

  • crud operations for atproto records (list, get, create, update, delete)
  • batch operations: delete multiple records concurrently with progress tracking
  • blob upload: upload images, videos, and other binary content
  • cursor pagination: paginate through large collections
  • MCP server: expose operations via model context protocol for AI agents
  • optional auth: reads with --repo flag don't require authentication
  • shorthand URIs: use app.bsky.feed.post/abc123 when authenticated
  • multiple output formats: compact (default), json, yaml, table
  • unix-style aliases: ls, cat, rm, edit, touch/add
  • jq-friendly json output
  • python 3.10+, type-safe

plugin (claude code / codex)

installs the hosted MCP server and the skills together — no local runtime. this is the recommended way to use pdsx with an agent: the MCP server gives it the tools, the skills teach it the parts that are easy to get wrong.

# claude code
/plugin marketplace add https://github.com/zzstoatzz/pdsx
/plugin install pdsx

# codex
codex plugin marketplace add https://github.com/zzstoatzz/pdsx
codex plugin add pdsx@pdsx

from a clone, for local development:

claude --plugin-dir .

the skills

skill reach for it when
/pdsx:reading-records inspecting what an account published, pulling records out of a PDS, fetching blob content, or exploring an unfamiliar lexicon
/pdsx:writing-records creating, updating or deleting records, uploading blobs, running batch operations, or checking that a write did what you meant
/pdsx:experimental-spaces working with permissioned data (com.atproto.space.*) — experimental, and served by almost no PDS

they load on demand, so installing all three costs nothing until one is relevant.

MCP tools or the CLI?

the MCP server covers ordinary record CRUD with structured output. reach for the CLI when you need something it doesn't expose:

you want use
read / create / update / delete one record MCP tools
arbitrary read-only XRPC MCP query tool
batch operations over JSONL, with concurrency CLI — create/update/rm reading stdin
blob upload CLI — upload-blob
permissioned data CLI — pdsx spaces …, no MCP equivalent

two things that surprise people

reads always need a target. -r/--repo is required even when you're authenticated — credentials decide what you can see, never where the read goes.

pdsx -r you.bsky.social ls app.bsky.feed.post    # your own repo, explicitly

ls returns one page. default limit is 50 and it does not follow cursors, so a collection with 200 records answers with 50 and a cursor on stderr. sort one page and you'll draw confident, wrong conclusions about "the latest" of anything.

pdsx -r zzstoatzz.io ls app.bsky.feed.post -o json | jq '.[].uri'
# stderr: next page cursor: 3mrgybvq4w22y

already using protopack?

protopack bundles the pdsx MCP server alongside the Microcosm services. installing both is fine — the server registers once, not twice, and you get protopack's skills plus these three.

that pairing is worth having: protopack's constellation skill answers "who interacted with this record" through the global backlink index, which is far better than paginating someone's posts to find replies.

MCP server

pdsx includes an MCP server for AI agent integration (e.g., claude code, cursor).

# add to claude code (read-only)
claude mcp add-json pdsx '{"type": "http", "url": "https://pdsx-by-zzstoatzz.fastmcp.app/mcp"}'

# with authentication for writes
claude mcp add-json pdsx '{
  "type": "http",
  "url": "https://pdsx-by-zzstoatzz.fastmcp.app/mcp",
  "headers": {
    "x-atproto-handle": "your.handle",
    "x-atproto-password": "your-app-password"
  }
}'

get an app password at: https://bsky.app/settings/app-passwords

📚 full MCP documentation - local setup, custom PDS, available tools, filtering, and more

running the MCP server locally

run the MCP server locally with uvx:

uvx --from 'pdsx[mcp]' pdsx-mcp

to add it to claude code as a local stdio server:

claude mcp add pdsx -- uvx --from 'pdsx[mcp]' pdsx-mcp

for authenticated writes, use the -e flag:

claude mcp add pdsx -e ATPROTO_HANDLE=your.handle -e ATPROTO_PASSWORD=your-app-password -- uvx --from 'pdsx[mcp]' pdsx-mcp
usage examples

read operations (no auth with -r)

# list records from any repo (note: -r goes BEFORE ls)
pdsx -r zzstoatzz.io ls app.bsky.feed.post --limit 5 -o json

# read someone's bio
pdsx -r zzstoatzz.io ls app.bsky.actor.profile -o json | jq -r '.[0].description'

pagination

# get first page (note: -r before ls, --cursor after)
pdsx -r zzstoatzz.io ls app.bsky.feed.post --limit 2

# output includes cursor if more pages exist, copy it for next command
# next page cursor: 3m5335qycpc2z

# get next page (use actual cursor from previous output)
pdsx -r zzstoatzz.io ls app.bsky.feed.post --limit 2 --cursor 3m5335qycpc2z

post with image (end-to-end)

# download a test image
curl -sL https://picsum.photos/200/200 -o /tmp/test.jpg

# upload image and capture blob reference
pdsx upload-blob /tmp/test.jpg
# copy the blob reference from output, example:
# {"$type":"blob","ref":{"$link":"bafkreif..."},"mimeType":"image/jpeg","size":6344}

# create post with uploaded image (paste your actual blob reference)
pdsx create app.bsky.feed.post \
  text='Posted via pdsx!' \
  'embed={"$type":"app.bsky.embed.images","images":[{"alt":"test image","image":{"$type":"blob","ref":{"$link":"PASTE_YOUR_CID_HERE"},"mimeType":"image/jpeg","size":6344}}]}'

write operations (auth required)

# update your bio
pdsx edit app.bsky.actor.profile/self description='Building with pdsx!'

# create a simple post
pdsx create app.bsky.feed.post text='Hello from pdsx!'

# delete a post (use the rkey from create output)
pdsx rm app.bsky.feed.post/PASTE_RKEY_HERE

batch operations

# delete multiple records (runs concurrently)
pdsx rm app.bsky.feed.post/abc123 app.bsky.feed.post/def456 app.bsky.feed.post/ghi789

# delete from file via stdin (the Unix way)
cat uris.txt | pdsx rm

# control concurrency (default: 10)
pdsx rm uri1 uri2 uri3 --concurrency 20

# fail-fast mode (stop on first error)
pdsx rm uri1 uri2 uri3 --fail-fast

note: when authenticated, use shorthand URIs (collection/rkey) instead of full AT-URIs (at://did:plc:.../collection/rkey)

output formats

both ls and cat/get support four output formats:

compact (default for ls)

app.bsky.feed.post (3 records)
3m4ryxwq5dt2i: {"created_at":"2025-11-04T07:25:17.061883+00:00","text":"..."}

json

pdsx -r zzstoatzz.io ls app.bsky.feed.post -o json | jq '.[].text'
pdsx -r zzstoatzz.io cat app.bsky.feed.post/3m5335qycpc2z -o json

yaml

pdsx -r zzstoatzz.io ls app.bsky.feed.post --limit 3 -o yaml
pdsx -r zzstoatzz.io cat app.bsky.actor.profile/self -o yaml

table (default for cat/get)

pdsx -r zzstoatzz.io ls app.bsky.feed.post --limit 5 -o table
pdsx -r zzstoatzz.io cat app.bsky.actor.profile/self  # default
development
git clone https://github.com/zzstoatzz/pdsx
cd pdsx
uv sync
just hooks      # install pre-commit hooks — CI skips the plugin manifests
uv run pytest
uv run ty check

license

mit

Metadata

Release files for pdsx 0.1.9

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pdsx 0.1.9
File Size Uploaded
pdsx-0.1.9.tar.gz 201.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pdsx 0.1.9
File Interpreter ABI Platform
pdsx-0.1.9-py3-none-any.whl Python 3 none any Details

Total release size: 245.0 kB

Release files / pdsx-0.1.9.tar.gz

Download URL pdsx-0.1.9.tar.gz
Size 201.9 kB
Tags Source
SHA-256 checksum
How to use checksums
64fe8fd4e18516a328c1a276efe576cac669bbfcde7df2d90e26052bef43e3f3
BLAKE2b-256 checksum
How to use checksums
a7ac4f52c65a715e50d5ee0507c7db0c539069aef6d4495852025381a322238f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / pdsx-0.1.9-py3-none-any.whl

Download URL pdsx-0.1.9-py3-none-any.whl
Size 43.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0598edd1838598a586f7551af6d52abf2fdedded74f7fc0a0a5b420e132e03d7
BLAKE2b-256 checksum
How to use checksums
fd054891b3e470475d45a4f70378a7ceeac556d0478ee9583616d4aed0b01ab2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page