diff --git a/crates/sprout-cli/TESTING.md b/crates/sprout-cli/TESTING.md index 4281855c77..a4a9366b6e 100644 --- a/crates/sprout-cli/TESTING.md +++ b/crates/sprout-cli/TESTING.md @@ -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":"","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 '\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 ``` diff --git a/crates/sprout-cli/src/commands/channels.rs b/crates/sprout-cli/src/commands/channels.rs index 6d70925a62..fe4eac3b3d 100644 --- a/crates/sprout-cli/src/commands/channels.rs +++ b/crates/sprout-cli/src/commands/channels.rs @@ -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) }) diff --git a/crates/sprout-cli/src/commands/messages.rs b/crates/sprout-cli/src/commands/messages.rs index 22d2019899..5cdc4532a9 100644 --- a/crates/sprout-cli/src/commands/messages.rs +++ b/crates/sprout-cli/src/commands/messages.rs @@ -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, pub reply_to: Option, 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)?;