From 8c3d8f022f2bdbcfb26cd8b129b4233cc58363be Mon Sep 17 00:00:00 2001 From: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co> Date: Mon, 29 Jun 2026 10:17:50 -0400 Subject: [PATCH] feat(cli): support visibility on channels update Visibility (open/private) was a create-time-only field: `channels create` took `--visibility`, but `channels update` only handled name/description/ttl, so there was no way to flip a channel's visibility after creation from the CLI. The plumbing already existed below the CLI: `buzz_sdk::build_update_channel` accepts a `visibility` arg and emits the `["visibility", v]` tag on the kind:9002 edit-metadata event, and the relay validates the value, gates it behind owner/admin auth, applies the DB update, invalidates the visibility/accessible-channel caches, and emits a visibility_changed event. The CLI just hardcoded `None` and exposed no flag. Wire `--visibility ` through `channels update` as a clap ValueEnum (matching `channels create`), so invalid values are rejected at parse time, and thread it into build_update_channel. Visibility is also included in the no-op guard and its error message. Live-tested against a real channel: open->private->open, each flip confirmed via the stored kind:9002 visibility tag. Co-authored-by: Tyler Longwell Signed-off-by: Tyler Longwell --- crates/buzz-cli/src/commands/channels.rs | 22 ++++++++++++++++++---- crates/buzz-cli/src/lib.rs | 6 +++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/crates/buzz-cli/src/commands/channels.rs b/crates/buzz-cli/src/commands/channels.rs index 9b92a4c42..26092a2a6 100644 --- a/crates/buzz-cli/src/commands/channels.rs +++ b/crates/buzz-cli/src/commands/channels.rs @@ -349,6 +349,7 @@ pub async fn cmd_update_channel( channel_id: &str, name: Option<&str>, description: Option<&str>, + visibility: Option<&str>, ttl: Option, no_ttl: bool, ) -> Result<(), CliError> { @@ -360,15 +361,25 @@ pub async fn cmd_update_channel( (None, false) => None, }; - if name.is_none() && description.is_none() && ttl_change.is_none() { + if let Some(v) = visibility { + if v != "open" && v != "private" { + return Err(CliError::Usage(format!( + "--visibility must be 'open' or 'private' (got: {v})" + ))); + } + } + + 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, --ttl, --no-ttl)".into(), + "at least one field required (--name, --description, --visibility, --ttl, --no-ttl)" + .into(), )); } 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?; @@ -621,14 +632,17 @@ pub async fn dispatch( channel, name, description, + visibility, ttl, no_ttl, } => { + let vis = visibility.as_ref().map(|v| v.to_string()); cmd_update_channel( client, &channel, name.as_deref(), description.as_deref(), + vis.as_deref(), ttl, no_ttl, ) diff --git a/crates/buzz-cli/src/lib.rs b/crates/buzz-cli/src/lib.rs index 191cc73d9..90515d583 100644 --- a/crates/buzz-cli/src/lib.rs +++ b/crates/buzz-cli/src/lib.rs @@ -414,7 +414,7 @@ pub enum ChannelsCmd { #[arg(long, value_name = "SECONDS")] ttl: Option, }, - /// Update channel name, description, or ephemeral TTL + /// Update channel name, description, visibility, or ephemeral TTL Update { /// Channel UUID #[arg(long)] @@ -425,6 +425,10 @@ pub enum ChannelsCmd { /// New channel description #[arg(long)] description: Option, + /// Change channel visibility: "open" (publicly listable/joinable) or + /// "private" (invite-only). Requires owner/admin. + #[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")]