1use clap::Parser;
2use jacquard::CowStr;
3use jacquard::api::app_bsky::actor::profile::Profile;
4use jacquard::client::{Agent, FileAuthStore};
5use jacquard::oauth::atproto::AtprotoClientMetadata;
6use jacquard::oauth::client::OAuthClient;
7use jacquard::oauth::loopback::LoopbackConfig;
8use jacquard::types::string::AtUri;
9use jacquard::xrpc::XrpcClient;
10use miette::IntoDiagnostic;
11
12#[derive(Parser, Debug)]
13#[command(author, version, about = "Update profile display name and description")]
14struct Args {
15 /// Handle (e.g., alice.bsky.social), DID, or PDS URL
16 input: CowStr<'static>,
17
18 /// New display name
19 #[arg(long)]
20 display_name: Option<String>,
21
22 /// New bio/description
23 #[arg(long)]
24 description: Option<String>,
25
26 /// Path to auth store file (will be created if missing)
27 #[arg(long, default_value = "/tmp/jacquard-oauth-session.json")]
28 store: String,
29}
30
31#[tokio::main]
32async fn main() -> miette::Result<()> {
33 let args = Args::parse();
34
35 let oauth = OAuthClient::with_default_config(FileAuthStore::new(&args.store));
36 let session = oauth
37 .login_with_local_server(args.input, Default::default(), LoopbackConfig::default())
38 .await?;
39
40 let agent: Agent<_> = Agent::from(session);
41
42 // Get session info to build the at:// URI for the profile record
43 let (did, _) = agent
44 .info()
45 .await
46 .ok_or_else(|| miette::miette!("No session info available"))?;
47
48 // Profile records use "self" as the rkey
49 let uri_string = format!("at://{}/app.bsky.actor.profile/self", did);
50 let uri = AtUri::new(&uri_string)?;
51
52 // Update profile in-place using the fetch-modify-put pattern
53 agent
54 .update_record::<Profile>(uri, |profile| {
55 if let Some(name) = &args.display_name {
56 profile.display_name = Some(CowStr::from(name.clone()));
57 }
58 if let Some(desc) = &args.description {
59 profile.description = Some(CowStr::from(desc.clone()));
60 }
61 })
62 .await?;
63
64 println!("✓ Profile updated successfully");
65
66 Ok(())
67}