Skip to content

feat(cli): add channels search for human-readable channel lookup#712

Open
tlongwell-block wants to merge 1 commit into
mainfrom
dawn/channels-search
Open

feat(cli): add channels search for human-readable channel lookup#712
tlongwell-block wants to merge 1 commit into
mainfrom
dawn/channels-search

Conversation

@tlongwell-block
Copy link
Copy Markdown
Collaborator

Summary

Adds sprout channels search --query <name> so humans and agents can resolve a channel UUID from a human-readable name without opening channel settings or guessing.

Acceptance from the brief in cli-channel-lookup:

  • ✅ Search by partial and exact channel name.
  • ✅ Output includes UUID and display name.
  • ✅ Works for visible channels, including private channels the caller can see (relay-side access control on kind:39000 already enforces this).
  • ✅ JSON output consistent with existing CLI style.

What's in the diff

  • New ChannelsCmd::Search { query, exact, include_archived, limit } variant in crates/sprout-cli/src/lib.rs.
  • New cmd_search_channels + ChannelSummary::from_event + name_matches in crates/sprout-cli/src/commands/channels.rs, plus a dispatch arm.
  • 7 unit tests covering tag parsing (happy path, archived, private, missing required tags, malformed tags) and the name-match predicate.

Stable JSON projection per result:

{
  "channel_id": "",
  "name": "",
  "channel_type": "stream" | "forum" | …,
  "visibility": "public" | "private",
  "archived": false,
  "about": "" | null,
  "topic": "" | null,
  "purpose": "" | null
}

Sorted by name, then channel_id for determinism.

Design notes

  • Source of truth is kind:39000 (NIP-29 group metadata). The relay emits it from side_effects::emit_group_discovery_events with name, about, t, private/public, archived, topic, purpose. The existing channels list/get query kind:39002 (membership) and so have no name tag at all — addressing that is intentionally out of scope here to avoid breaking scripts.
  • NIP-50 search is not used. The Typesense index sets query_by: "content", and kind:39000 events have empty content — the name lives in tags. A plain {kinds:[39000]} query plus client-side post-filter is simpler and correct.
  • No new auth. crates/sprout-relay/src/handlers/req.rs already runs per-event accessible_channels checks on globally-filtered queries, so private channels the caller can't see are dropped server-side.
  • Limit default 1000. The DB clamps un-tuned query_events at 1000 anyway; making it explicit avoids surprise truncation.
  • --exact returns all case-insensitive exact matches. Channel names are not unique (no DB UNIQUE constraint, no "name taken" path on the relay — only repos have that), so the flag narrows, not necessarily to one.

Example

$ sprout channels search --query cli
[
  {"channel_id":"d71e3abe-…","name":"cli-channel-lookup","channel_type":"stream","visibility":"private",…},
  {"channel_id":"7bca45ee-…","name":"cli-refactor-and-migration","channel_type":"stream","visibility":"public",…}
]

Verification

  • cargo build -p sprout-cli
  • cargo test -p sprout-cli --lib channels — 7/7 ✓
  • cargo clippy -p sprout-cli --all-targets -- -D warnings
  • cargo fmt -p sprout-cli --check
  • Live tested against the relay: substring, --exact, --query "" (rejected), unknown-query → [], --help.

Review

Plan was iterated to 9/10+ with Max (GPT-5.5) before implementation; diff was independently reviewed by Max (build, clippy, fmt, tests, live behavior, DCO trailer) and signed off. Thread: https://github.com/block/sprout channel cli-channel-lookup.

Follow-ups (not in this PR)

  • channels list / channels get currently return raw kind:39002 events with no name. Worth switching to kind:39000 in a small follow-up, but it changes the JSON shape and is intentionally separated to avoid breaking scripts in this PR.

Adds a new subcommand that searches NIP-29 kind:39000 group-metadata
events by channel name. Returns a stable JSON projection
({channel_id, name, channel_type, visibility, archived, about,
topic, purpose}), sorted by name + channel_id for determinism.

- Case-insensitive substring match by default; `--exact` for exact.
- `--include-archived` (default: exclude archived).
- `--limit` defaults to 1000 (the DB's soft cap for un-tuned
  REQ queries; explicit value avoids surprise truncation).
- Relay-side access control already filters kind:39000 events to
  channels the caller can see, so no new auth logic is required.

NIP-50 search is not used: the relay's Typesense index indexes
`content`, and kind:39000's name lives in tags with empty content.

Unit-tested: tag extraction (happy path, archived, private,
missing required tags, malformed tags) and the name-match predicate.

Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
@tlongwell-block tlongwell-block requested a review from a team as a code owner May 21, 2026 21:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant