# Jacquard A suite of Rust crates for the AT Protocol. [Docs](https://docs.rs/jacquard/latest/jacquard/) ## Goals and Features - Validated, spec-compliant, easy to work with, and performant baseline types - Batteries-included, but easily replaceable batteries. - Easy to extend with custom lexicons using code generation or handwritten api types - Straightforward OAuth - stateless options (or options where you handle the state) for rolling your own - all the building blocks of the convenient abstractions are available - lexicon Value type for working with unknown atproto data (dag-cbor or json) - order of magnitude less boilerplate than some existing crates - use as much or as little from the crates as you need ## Example Dead simple API client. Logs in with OAuth and prints the latest 5 posts from your timeline. ```rust // Note: this requires the `loopback` feature enabled (it is currently by default) use clap::Parser; use jacquard::CowStr; use jacquard::api::app_bsky::feed::get_timeline::GetTimeline; use jacquard::client::{Agent, FileAuthStore}; use jacquard::oauth::atproto::AtprotoClientMetadata; use jacquard::oauth::client::OAuthClient; use jacquard::oauth::loopback::LoopbackConfig; use jacquard::oauth::scopes::Scope; use jacquard::types::xrpc::XrpcClient; use miette::IntoDiagnostic; #[derive(Parser, Debug)] #[command(author, version, about = "Jacquard - OAuth (DPoP) loopback demo")] struct Args { /// Handle (e.g., alice.bsky.social), DID, or PDS URL input: CowStr<'static>, /// Path to auth store file (will be created if missing) #[arg(long, default_value = "/tmp/jacquard-oauth-session.json")] store: String, } #[tokio::main] async fn main() -> miette::Result<()> { let args = Args::parse(); // File-backed auth store for testing let store = FileAuthStore::new(&args.store); let client_data = jacquard_oauth::session::ClientData { keyset: None, // Default sets normal localhost redirect URIs and "atproto transition:generic" scopes. // The localhost helper will ensure you have at least "atproto" and will fix urls config: AtprotoClientMetadata::default_localhost() }; // Build an OAuth client let oauth = OAuthClient::new(store, client_data); // Authenticate with a PDS, using a loopback server to handle the callback flow let session = oauth .login_with_local_server( args.input.clone(), Default::default(), LoopbackConfig::default(), ) .await?; // Wrap in Agent and fetch the timeline let agent: Agent<_> = Agent::from(session); let timeline = agent .send(&GetTimeline::new().limit(5).build()) .await? .into_output()?; for (i, post) in timeline.feed.iter().enumerate() { println!("\n{}. by {}", i + 1, post.post.author.handle); println!( " {}", serde_json::to_string_pretty(&post.post.record).into_diagnostic()? ); } Ok(()) } ``` ## Component crates Jacquard is broken up into several crates for modularity. The correct one to use is generally `jacquard` itself, as it re-exports the others. - `jacquard`: Main crate [![Crates.io](https://img.shields.io/crates/v/jacquard.svg)](https://crates.io/crates/jacquard) [![Documentation](https://docs.rs/jacquard/badge.svg)](https://docs.rs/jacquard) - `jacquard-common`: Foundation crate [![Crates.io](https://img.shields.io/crates/v/jacquard-common.svg)](https://crates.io/crates/jacquard-common) [![Documentation](https://docs.rs/jacquard-common/badge.svg)](https://docs.rs/jacquard-common) - `jacquard-api`: Autogenerated API bindings [![Crates.io](https://img.shields.io/crates/v/jacquard-api.svg)](https://crates.io/crates/jacquard-api) [![Documentation](https://docs.rs/jacquard-api/badge.svg)](https://docs.rs/jacquard-api) - `jacquard-oauth`: atproto OAuth implementation [![Crates.io](https://img.shields.io/crates/v/jacquard-oauth.svg)](https://crates.io/crates/jacquard-oauth) [![Documentation](https://docs.rs/jacquard-oauth/badge.svg)](https://docs.rs/jacquard-oauth) - `jacquard-identity`: Identity resolution [![Crates.io](https://img.shields.io/crates/v/jacquard-identity.svg)](https://crates.io/crates/jacquard-identity) [![Documentation](https://docs.rs/jacquard-identity/badge.svg)](https://docs.rs/jacquard-identity) - `jacquard-lexicon`: Lexicon parsing and code generation [![Crates.io](https://img.shields.io/crates/v/jacquard-lexicon.svg)](https://crates.io/crates/jacquard-lexicon) [![Documentation](https://docs.rs/jacquard-lexicon/badge.svg)](https://docs.rs/jacquard-lexicon) - `jacquard-derive`: Derive macros for lexicon types [![Crates.io](https://img.shields.io/crates/v/jacquard-derive.svg)](https://crates.io/crates/jacquard-derive) [![Documentation](https://docs.rs/jacquard-derive/badge.svg)](https://docs.rs/jacquard-derive) ## Development This repo uses [Flakes](https://nixos.asia/en/flakes) from the get-go. ```bash # Dev shell nix develop # or run via cargo nix develop -c cargo run # build nix build ``` There's also a [`justfile`](https://just.systems/) for Makefile-esque commands to be run inside of the devShell, and you can generally `cargo ...` or `just ...` whatever just fine if you don't want to use Nix and have the prerequisites installed. [![License](https://img.shields.io/crates/l/jacquard.svg)](./LICENSE)