diff --git a/crates/buzz-cli/src/commands/agents.rs b/crates/buzz-cli/src/commands/agents.rs index 568249d0829..0ea9191f9c4 100644 --- a/crates/buzz-cli/src/commands/agents.rs +++ b/crates/buzz-cli/src/commands/agents.rs @@ -164,6 +164,55 @@ pub async fn dispatch(command: AgentsCmd, client: &BuzzClient) -> Result<(), Cli } AgentsCmd::Archived => cmd_archived(client).await, + + AgentsCmd::PublishProfile { + name, + agent_type, + channel_ids, + status, + respond_to, + respond_to_allowlist, + channel_add_policy, + } => { + match respond_to.as_str() { + "owner-only" | "allowlist" | "anyone" => {} + _ => { + return Err(CliError::Usage(format!( + "--respond-to must be 'owner-only', 'allowlist', or 'anyone' (got: {respond_to})" + ))) + } + } + match channel_add_policy.as_str() { + "anyone" | "owner_only" | "nobody" => {} + _ => { + return Err(CliError::Usage(format!( + "--channel-add-policy must be 'anyone', 'owner_only', or 'nobody' (got: {channel_add_policy})" + ))) + } + } + let content = json!({ + "name": name, + "agent_type": agent_type, + "channels": [], + "channel_ids": channel_ids, + "capabilities": [], + "status": status, + "respond_to": respond_to, + "respond_to_allowlist": respond_to_allowlist, + "channel_add_policy": channel_add_policy, + }) + .to_string(); + use nostr::{EventBuilder, Kind}; + let builder = EventBuilder::new( + Kind::Custom(buzz_sdk::kind::KIND_AGENT_PROFILE as u16), + &content, + ) + .tags([]); + let event = client.sign_event(builder)?; + let resp = client.submit_event(event).await?; + println!("{resp}"); + Ok(()) + } } } diff --git a/crates/buzz-cli/src/lib.rs b/crates/buzz-cli/src/lib.rs index 3893c5b6425..3cf82199275 100644 --- a/crates/buzz-cli/src/lib.rs +++ b/crates/buzz-cli/src/lib.rs @@ -365,6 +365,38 @@ Examples:\n \ buzz agents archived" )] Archived, + /// Publish this identity's relay agent profile (kind:10100) — makes a headless + /// (non-managed) agent discoverable and @mentionable by Buzz Desktop clients. + #[command( + after_help = "kind:10100 is a replaceable event: this fully overwrites any \ +previous agent profile for the signing identity.\n\n\ +Examples:\n \ +buzz agents publish-profile --name Coder --channel-ids --respond-to allowlist \ +--respond-to-allowlist , --channel-add-policy owner_only" + )] + PublishProfile { + /// Display name shown in Buzz Desktop + #[arg(long)] + name: String, + /// Free-form agent type label (default: "agent") + #[arg(long, default_value = "agent")] + agent_type: String, + /// Channel UUIDs this agent should be considered mentionable in (comma-separated) + #[arg(long, value_delimiter = ',')] + channel_ids: Vec, + /// Presence status to report + #[arg(long, default_value = "online")] + status: String, + /// Who this agent responds to: owner-only, allowlist, or anyone + #[arg(long, default_value = "allowlist")] + respond_to: String, + /// Pubkeys allowed to trigger this agent when --respond-to=allowlist (comma-separated) + #[arg(long, value_delimiter = ',')] + respond_to_allowlist: Vec, + /// Who may add this agent to new channels: anyone, owner_only, or nobody + #[arg(long, default_value = "owner_only")] + channel_add_policy: String, + }, } #[derive(Subcommand)] @@ -2175,6 +2207,7 @@ mod tests { "archived", "draft-create", "draft-update", + "publish-profile", "unarchive" ] ); @@ -2313,7 +2346,7 @@ mod tests { #[test] fn subcommand_counts_are_stable() { let expected: Vec<(&str, usize)> = vec![ - ("agents", 5), + ("agents", 6), ("canvas", 2), ("channels", 16), ("dms", 4),