diff --git a/crates/buzz-cli/src/commands/channels.rs b/crates/buzz-cli/src/commands/channels.rs index 42844bf1e0..5cc745d7b9 100644 --- a/crates/buzz-cli/src/commands/channels.rs +++ b/crates/buzz-cli/src/commands/channels.rs @@ -829,11 +829,27 @@ fn validate_ttl_seconds(secs: i64) -> Result { .map_err(|_| CliError::Usage(format!("--ttl is too large (max {} seconds)", i32::MAX))) } +fn validate_update_channel_fields( + name: Option<&str>, + description: Option<&str>, + visibility: Option<&str>, + ttl_change: Option>, +) -> Result<(), CliError> { + if name.is_none() && description.is_none() && visibility.is_none() && ttl_change.is_none() { + return Err(CliError::Usage( + "at least one field required (--name, --description, --visibility, --ttl, --no-ttl)" + .into(), + )); + } + Ok(()) +} + pub async fn cmd_update_channel( client: &BuzzClient, channel_id: &str, name: Option<&str>, description: Option<&str>, + visibility: Option<&str>, ttl: Option, no_ttl: bool, ) -> Result<(), CliError> { @@ -845,15 +861,12 @@ pub async fn cmd_update_channel( (None, false) => None, }; - if name.is_none() && description.is_none() && ttl_change.is_none() { - return Err(CliError::Usage( - "at least one field required (--name, --description, --ttl, --no-ttl)".into(), - )); - } + validate_update_channel_fields(name, description, visibility, ttl_change)?; let channel_uuid = parse_uuid(channel_id)?; - let builder = buzz_sdk::build_update_channel(channel_uuid, name, description, None, ttl_change) - .map_err(|e| CliError::Other(format!("build_update_channel failed: {e}")))?; + let builder = + buzz_sdk::build_update_channel(channel_uuid, name, description, visibility, ttl_change) + .map_err(|e| CliError::Other(format!("build_update_channel failed: {e}")))?; let event = client.sign_event(builder)?; let resp = client.submit_event(event).await?; @@ -1128,14 +1141,17 @@ pub async fn dispatch( channel, name, description, + visibility, ttl, no_ttl, } => { + let visibility = visibility.as_ref().map(|v| v.to_string()); cmd_update_channel( client, &channel, name.as_deref(), description.as_deref(), + visibility.as_deref(), ttl, no_ttl, ) @@ -1178,8 +1194,8 @@ mod tests { use super::{ apply_cardinality_rule, build_template_report, cmd_set_add_policy, finalize_roster_resolution, name_matches, resolve_roster_with_archive_filter, - validate_ttl_seconds, ArchivedExclusion, ChannelSummary, ResolvedAgent, RosterResolution, - SkippedSlug, + validate_ttl_seconds, validate_update_channel_fields, ArchivedExclusion, ChannelSummary, + ResolvedAgent, RosterResolution, SkippedSlug, }; use crate::client::BuzzClient; use crate::CliError; @@ -1291,6 +1307,21 @@ mod tests { assert!(validate_ttl_seconds(i32::MAX as i64 + 1).is_err()); } + #[test] + fn update_channel_fields_rejects_empty_update() { + let result = validate_update_channel_fields(None, None, None, None); + assert!(matches!(result, Err(CliError::Usage(_)))); + let msg = result.unwrap_err().to_string(); + assert!(msg.contains("at least one field required")); + assert!(msg.contains("--visibility")); + } + + #[test] + fn update_channel_fields_accepts_visibility_only_update() { + let result = validate_update_channel_fields(None, None, Some("open"), None); + assert!(result.is_ok(), "visibility-only update should be accepted"); + } + // --- BUZZ_ACP_ALLOWED_CHANNEL_ADD_POLICIES gate --- fn check_allowed_channel_add_policy(allowed_raw: &str, policy: &str) -> Result<(), CliError> { diff --git a/crates/buzz-cli/src/lib.rs b/crates/buzz-cli/src/lib.rs index 8a8bb053b0..1782e734ef 100644 --- a/crates/buzz-cli/src/lib.rs +++ b/crates/buzz-cli/src/lib.rs @@ -577,7 +577,10 @@ pub enum ChannelsCmd { #[arg(long, value_name = "PATH")] templates_file: Option, }, - /// Update channel name, description, or ephemeral TTL + /// Update channel name, description, visibility, or ephemeral TTL + #[command( + after_help = "Examples:\n buzz channels update --channel --name general\n buzz channels update --channel --visibility open\n buzz channels update --channel --visibility private" + )] Update { /// Channel UUID #[arg(long)] @@ -588,6 +591,9 @@ pub enum ChannelsCmd { /// New channel description #[arg(long)] description: Option, + /// New channel visibility + #[arg(long, value_enum)] + visibility: Option, /// Make the channel ephemeral (or change its lifetime): seconds until /// the relay archives it after the last message. Conflicts with --no-ttl. #[arg(long, value_name = "SECONDS", conflicts_with = "no_ttl")]