Fix CLI output normalization and wrong Nostr kinds (MCP parity)#612
Merged
Conversation
c0620a2 to
d4c9fc7
Compare
d2635b4 to
ae71c41
Compare
Comprehensive audit revealed the CLI queried wrong Nostr kinds (channels
list used kind:39002 instead of 41, dms list used 41010 instead of 41001,
workflow runs used 46020 instead of [46001,46002,46003]), used wrong tag
filters (#h instead of #d), discarded generated IDs from create commands,
and returned raw Nostr events with sig fields instead of structured data.
Adds query_multi() for compound queries, normalize_write_response() for
consistent {event_id, accepted, message} write output, normalize_events()
for stripping sig and reshaping reads, and extract_d_tag/extract_p_tags
helpers. All 25+ write handlers and 10+ read handlers now produce
structured output matching the MCP server.
Crossfire review (Codex + Gemini) surfaced 15 issues the Claude agents missed. Critical: p-tag role read from wrong index (empty relay_url at index 2 vs actual role at index 3), cmd_open_dm panics on malformed pubkey, users get returns inconsistent types, channels query wrong Nostr kind (41 vs 39000). Also: DM open now parses relay-assigned channel_id, canvas not-found returns null, thread filter restricts to message kinds, query() delegates to query_multi(), normalize_events takes &[Value], extract_channel_metadata and print_create_response DRY helpers, workflows get normalized, empty reaction content defaults to "+", reactions sorted deterministically.
Reformat long method chains and closure expressions that rustfmt flagged during CI. No logic changes.
ae71c41 to
c950d60
Compare
channels/workflows/DMs create responses now return entity IDs
(channel_id, workflow_id, dm_id) instead of bare id. All write
commands return {event_id, accepted, message}. canvas get outputs
raw markdown, not JSON. users get always returns an array.
Add expected-output comments throughout so testers can verify
correctness without reading the source.
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.
wpfleger96
added a commit
that referenced
this pull request
May 21, 2026
Brings the agent skill file to parity with all 62 CLI subcommands across 13 command groups after PR #612's output normalization and bug fixes. Adds 5 missing command groups (social, repos, upload, mem, pack), 5 missing subcommands in existing groups (channels update/ add-member/remove-member, workflows update/delete), missing flags (--file, --mention, --kinds, --depth-limit, --limit), fixes output contract inaccuracies (channels get pubkey field, workflows list pubkey, exit code 5, feed description), and restructures the Quick Reference table by output pattern for scannability.
Three changes from agent E2E testing feedback:
1. Channel names were always empty — extract_channel_metadata read
name/description from event content (always "") instead of NIP-29
tags where the relay stores them. Added extract_tag_value helper
and rewrote to use extract_tag_value(e, "name") and
extract_tag_value(e, "about").
2. channels list had no --limit flag (hardcoded 500). Added --limit
with default 500, applied to both member and non-member query paths.
3. Added global --format flag (json|compact). Compact mode reduces
read output for agent scanning: channels → {channel_id, name},
messages → {id, content, created_at}, users → {pubkey, display_name},
feed → {id, content, created_at}. Write commands unaffected.
wpfleger96
added a commit
that referenced
this pull request
May 21, 2026
Documents the new --limit flag on channels list and the global --format json|compact flag added in PR #612. Compact mode reduces read output: channels → {channel_id, name}, messages → {id, content, created_at}, users → {pubkey, display_name}.
Both flags were copied from the MCP server's param structs during parity work but neither the MCP nor the CLI ever implemented them. Silently accepting and ignoring flags is worse than a clean "unknown flag" error.
wpfleger96
added a commit
that referenced
this pull request
May 22, 2026
Brings the agent skill file to parity with all 62 CLI subcommands across 13 command groups after PR #612's output normalization and bug fixes. Adds 5 missing command groups (social, repos, upload, mem, pack), 5 missing subcommands in existing groups (channels update/ add-member/remove-member, workflows update/delete), missing flags (--file, --mention, --kinds, --depth-limit, --limit), fixes output contract inaccuracies (channels get pubkey field, workflows list pubkey, exit code 5, feed description), and restructures the Quick Reference table by output pattern for scannability.
wpfleger96
added a commit
that referenced
this pull request
May 22, 2026
Documents the new --limit flag on channels list and the global --format json|compact flag added in PR #612. Compact mode reduces read output: channels → {channel_id, name}, messages → {id, content, created_at}, users → {pubkey, display_name}.
wpfleger96
added a commit
that referenced
this pull request
May 22, 2026
Brings the agent skill file to parity with all 62 CLI subcommands across 13 command groups after PR #612's output normalization and bug fixes. Adds 5 missing command groups (social, repos, upload, mem, pack), 5 missing subcommands in existing groups (channels update/ add-member/remove-member, workflows update/delete), missing flags (--file, --mention, --kinds, --depth-limit, --limit), fixes output contract inaccuracies (channels get pubkey field, workflows list pubkey, exit code 5, feed description), and restructures the Quick Reference table by output pattern for scannability. Signed-off-by: Will Pfleger <wpfleger@block.xyz>
wpfleger96
added a commit
that referenced
this pull request
May 22, 2026
Documents the new --limit flag on channels list and the global --format json|compact flag added in PR #612. Compact mode reduces read output: channels → {channel_id, name}, messages → {id, content, created_at}, users → {pubkey, display_name}. Signed-off-by: Will Pfleger <wpfleger@block.xyz>
This was referenced May 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A full audit of the 54 CLI subcommands against their MCP equivalents revealed functional bugs throughout:
channels listqueriedkind:39002(member-lists) instead ofkind:39000(NIP-29 channel metadata), returning entirely wrong data;dms listqueriedkind:41010instead of41001;workflow runsqueried the trigger kind instead of the run-lifecycle kinds. On top of that, every read command returned raw Nostr event JSON includingsigfields agents never need, all write commands returned raw relay responses with no consistent shape, and create commands silently discarded the UUIDs they generated.The MCP server already handles all of this correctly -- this brings the CLI to parity so agents can migrate without workarounds.
Kind number fixes
channels list/get:41→39000(kind 41 is NIP-01 channel metadata, unused by Sprout; kind 39000 is NIP-29 group metadata, which is what the relay actually emits)channels members: uses#dtag filter (was#h)dms list:41010→41001workflow runs: single kind →[46001, 46002, 46003](doc-commented as returning empty -- relay stores runs in DB, not as events)Output normalization
{event_id, accepted, message}channel_id,dm_id,workflow_id) into the responsecanvas getreturns the markdown content string directly, ornullif not foundreactions getaggregates by emoji with{emoji, count, pubkeys}, sorted deterministically; empty content defaults to"+"per NIP-25users getalways returns an array (was returning a bare object for single results)workflows getreturns normalized{workflow_id, content, created_at, pubkey}[9, 40002, 40003, 40008, 45003](was including reactions and system events)Shared helpers in
client.rsquery_multi()sends multiple ORed filters in a single HTTP call;query()now delegates to itnormalize_events(&[Value])stripssigand sorts bycreated_atnormalize_write_response()extracts{event_id, accepted, message}from relay responsesextract_d_tag(),extract_p_tags()for consistent tag parsing (p-tag role is at index 3, not 2 -- index 2 is always an empty relay URL placeholder)extract_tag_value(event, key)generic tag value extractor, used byextract_channel_metadataprint_create_response()reduces repeated blocks across channel/DM/workflow commandsChannel metadata fix
extract_channel_metadatawas readingnameandaboutfrom the event'scontentfield (parsed as JSON). The relay emits kind:39000 events withcontent: ""and stores metadata in NIP-29 tags (["name", "..."],["about", "..."]). All channel names were empty strings. Fixed to read from tags viaextract_tag_value.New flags
channels list --limit <N>: limits results (default 500). Previously hardcoded.--format json|compact: global flag on all commands.compactreduces read output for agent scanning: channels →{channel_id, name}, messages →{id, content, created_at}, users →{pubkey, display_name}, feed →{id, content, created_at}. Write commands unaffected.Bugs found during E2E testing
messages searchsent nokindsfilter, triggering the relay's p-gate with 403. Now sendskinds: [9, 40002, 45001, 45003]matching the MCP server's search implementation.channels list --visibility openalways 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.--kindflag onmessages sendwas accepted but ignored (dead code). Now routes to the correct SDK builders:build_forum_postfor kind 45001,build_forum_commentfor kind 45003. This enables forum voting via CLI.Safety fixes
cmd_open_dmvalidates pubkeys before callingTag::parse()(was panicking on malformed input)channel_idfrom the responsemessagefield instead of fabricating a local UUIDSPROUT_AUTH_TAGmay be stale