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 <open|private>` 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 <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
This commit is contained in:
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d
2026-06-29 10:17:50 -04:00
co-authored by Tyler Longwell
parent e3fc0e0278
commit 8c3d8f022f
2 changed files with 23 additions and 5 deletions
+18 -4
View File
@@ -349,6 +349,7 @@ pub async fn cmd_update_channel(
channel_id: &str,
name: Option<&str>,
description: Option<&str>,
visibility: Option<&str>,
ttl: Option<i64>,
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,
)
+5 -1
View File
@@ -414,7 +414,7 @@ pub enum ChannelsCmd {
#[arg(long, value_name = "SECONDS")]
ttl: Option<i64>,
},
/// 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<String>,
/// Change channel visibility: "open" (publicly listable/joinable) or
/// "private" (invite-only). Requires owner/admin.
#[arg(long, value_enum)]
visibility: Option<ChannelVisibility>,
/// 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")]