mirror of
https://github.com/block/buzz.git
synced 2026-11-04 00:12:02 +00:00
fix(cli): fix search p-gate, visibility filter, and forum post routing
Three bugs found during E2E testing against a local relay: 1. `messages search` sent no `kinds` filter, triggering the relay's p-gate (403). Now sends `kinds: [9, 40002, 45001, 45003]` matching the MCP server's search implementation. 2. `channels list --visibility open` always returned `[]` because the filter checked for `["visibility", "open"]` tags, but the relay emits `["public"]`/`["private"]` per NIP-29. Rewritten to match the relay's actual tag convention. 3. `--kind` flag on `messages send` was dead code (`#[allow(dead_code)]`). Now routes to `build_forum_post` (kind 45001) and `build_forum_comment` (kind 45003) SDK builders, enabling forum voting via CLI. TESTING.md updated with relay behavior notes for channels leave (last-owner guard), workflows trigger (DB indexing delay), workflows update (missing --channel flag), and corrected expected error messages.
This commit is contained in:
@@ -154,8 +154,11 @@ sprout channels join --channel "$CHANNEL_ID" | jq .
|
||||
# Expected: {"event_id":"...","accepted":true,"message":"..."}
|
||||
|
||||
# channels leave
|
||||
# NOTE: Fails with 400 "cannot remove the last owner" if this identity is the
|
||||
# sole owner (which it is after channels create). To test leave successfully,
|
||||
# first add-member a second pubkey as owner. The relay enforces ≥1 owner.
|
||||
sprout channels leave --channel "$CHANNEL_ID" | jq .
|
||||
# Expected: {"event_id":"...","accepted":true,"message":"..."}
|
||||
# Expected: {"event_id":"...","accepted":true,"message":"..."} (or 400 if last owner)
|
||||
|
||||
# Re-join so we can send messages
|
||||
sprout channels join --channel "$CHANNEL_ID" | jq .
|
||||
@@ -370,8 +373,8 @@ sprout workflows list --channel "$CHANNEL_ID" | jq .
|
||||
sprout workflows get --workflow "$WF_ID" | jq .
|
||||
# Expected: {"workflow_id":"...","content":"<yaml>","created_at":N,"pubkey":"..."} or null
|
||||
|
||||
# workflows update
|
||||
sprout workflows update --workflow "$WF_ID" \
|
||||
# workflows update (requires --channel)
|
||||
sprout workflows update --channel "$CHANNEL_ID" --workflow "$WF_ID" \
|
||||
--yaml 'name: test-wf-updated
|
||||
trigger:
|
||||
on: webhook
|
||||
@@ -381,6 +384,9 @@ steps:
|
||||
text: "Updated"' | jq .
|
||||
|
||||
# workflows trigger
|
||||
# NOTE: May return 400 "workflow not found" — the relay indexes workflow
|
||||
# definitions into a DB table asynchronously. If the definition event hasn't
|
||||
# been indexed yet, the trigger handler won't find it.
|
||||
sprout workflows trigger --workflow "$WF_ID" | jq .
|
||||
|
||||
# workflows runs
|
||||
@@ -439,9 +445,9 @@ sprout messages delete --event "not-hex" 2>&1; echo "exit: $?"
|
||||
# stderr: {"error":"user_error","message":"must be a 64-character hex string: not-hex"}
|
||||
# exit: 1
|
||||
|
||||
# Exit 1: Invalid --type value
|
||||
# Exit 1: Invalid --type value (clap validates the enum — multi-line error)
|
||||
sprout channels create --name x --type invalid --visibility open 2>&1; echo "exit: $?"
|
||||
# stderr: {"error":"user_error","message":"--type must be 'stream' or 'forum' (got: invalid)"}
|
||||
# stderr: {"error":"user_error","message":"error: invalid value 'invalid' for '--type <CHANNEL_TYPE>'\n [possible values: stream, forum]\n..."}
|
||||
# exit: 1
|
||||
|
||||
# Exit 1: Invalid --direction value
|
||||
@@ -456,7 +462,7 @@ sprout users set-profile 2>&1; echo "exit: $?"
|
||||
# Exit 3: No auth configured
|
||||
env -u SPROUT_PRIVATE_KEY \
|
||||
cargo run -p sprout-cli -- channels list 2>&1; echo "exit: $?"
|
||||
# stderr: {"error":"auth_error","message":"SPROUT_PRIVATE_KEY is required (use --private-key or set env var)"}
|
||||
# stderr: {"error":"auth_error","message":"auth error: SPROUT_PRIVATE_KEY is required (use --private-key or set env var)"}
|
||||
# exit: 3
|
||||
|
||||
# Not-found returns null, not an error (exit 0)
|
||||
@@ -479,7 +485,7 @@ SPROUT_PRIVATE_KEY="nsec1..." sprout channels list | jq .
|
||||
# No auth → exit 3
|
||||
env -u SPROUT_PRIVATE_KEY \
|
||||
cargo run -p sprout-cli -- channels list 2>&1; echo "exit: $?"
|
||||
# stderr: {"error":"auth_error","message":"SPROUT_PRIVATE_KEY is required (use --private-key or set env var)"}
|
||||
# stderr: {"error":"auth_error","message":"auth error: SPROUT_PRIVATE_KEY is required (use --private-key or set env var)"}
|
||||
# exit: 3
|
||||
```
|
||||
|
||||
|
||||
@@ -69,15 +69,20 @@ pub async fn cmd_list_channels(
|
||||
.iter()
|
||||
.filter(|e| {
|
||||
if let Some(vis) = visibility {
|
||||
// Client-side visibility filter: check for ["visibility", vis] in tags
|
||||
// NIP-29: relay emits ["public"] or ["private"] single-element tags
|
||||
let nip29_tag = match vis {
|
||||
"open" => "public",
|
||||
_ => vis,
|
||||
};
|
||||
e.get("tags")
|
||||
.and_then(|t| t.as_array())
|
||||
.map(|tags| {
|
||||
tags.iter().any(|tag| {
|
||||
tag.as_array()
|
||||
.map(|a| {
|
||||
a.first().and_then(|v| v.as_str()) == Some("visibility")
|
||||
&& a.get(1).and_then(|v| v.as_str()) == Some(vis)
|
||||
a.len() == 1
|
||||
&& a.first().and_then(|v| v.as_str())
|
||||
== Some(nip29_tag)
|
||||
})
|
||||
.unwrap_or(false)
|
||||
})
|
||||
|
||||
@@ -299,6 +299,7 @@ pub async fn cmd_search(
|
||||
) -> Result<(), CliError> {
|
||||
let limit = limit.unwrap_or(20).min(100);
|
||||
let filter = serde_json::json!({
|
||||
"kinds": [9, 40002, 45001, 45003],
|
||||
"search": query,
|
||||
"limit": limit
|
||||
});
|
||||
@@ -315,7 +316,6 @@ pub async fn cmd_search(
|
||||
pub struct SendMessageParams {
|
||||
pub channel_id: String,
|
||||
pub content: String,
|
||||
#[allow(dead_code)] // reserved for future kind routing
|
||||
pub kind: Option<u16>,
|
||||
pub reply_to: Option<String>,
|
||||
pub broadcast: bool,
|
||||
@@ -381,15 +381,42 @@ pub async fn cmd_send_message(
|
||||
merge_mentions(&mut merged, &auto_resolved, MENTION_CAP);
|
||||
let mention_refs: Vec<&str> = merged.iter().map(|s| s.as_str()).collect();
|
||||
|
||||
let builder = sprout_sdk::build_message(
|
||||
channel_uuid,
|
||||
&final_content,
|
||||
thread_ref.as_ref(),
|
||||
&mention_refs,
|
||||
p.broadcast,
|
||||
&media_tags,
|
||||
)
|
||||
.map_err(|e| CliError::Other(format!("build_message failed: {e}")))?;
|
||||
let builder = match p.kind {
|
||||
Some(45001) => sprout_sdk::build_forum_post(
|
||||
channel_uuid,
|
||||
&final_content,
|
||||
&mention_refs,
|
||||
&media_tags,
|
||||
)
|
||||
.map_err(|e| CliError::Other(format!("build_forum_post failed: {e}")))?,
|
||||
Some(45003) => {
|
||||
let tr = thread_ref.as_ref().ok_or_else(|| {
|
||||
CliError::Usage("--reply-to is required for forum comments (kind 45003)".into())
|
||||
})?;
|
||||
sprout_sdk::build_forum_comment(
|
||||
channel_uuid,
|
||||
&final_content,
|
||||
tr,
|
||||
&mention_refs,
|
||||
&media_tags,
|
||||
)
|
||||
.map_err(|e| CliError::Other(format!("build_forum_comment failed: {e}")))?
|
||||
}
|
||||
None | Some(9) => sprout_sdk::build_message(
|
||||
channel_uuid,
|
||||
&final_content,
|
||||
thread_ref.as_ref(),
|
||||
&mention_refs,
|
||||
p.broadcast,
|
||||
&media_tags,
|
||||
)
|
||||
.map_err(|e| CliError::Other(format!("build_message failed: {e}")))?,
|
||||
Some(k) => {
|
||||
return Err(CliError::Usage(format!(
|
||||
"--kind {k} is not supported (use 9, 45001, or 45003)"
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
let event = client.sign_event(builder)?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user