Files
buzz/crates/buzz-cli/src
Blake McAnally 303157f53d feat(cli): send a thread message to the channel (#8778)
> Replaces #8769, which was opened from a fork.

## Summary

This change adds `buzz messages send --from-thread <root-event-id>`.
The flag is the CLI form of desktop "Send to channel".
It publishes a new top-level message in the channel.
The message carries the tag `["buzz:sent-from-thread", <root-id>,
<excerpt>]`.
The message has no NIP-10 reply tags.

## Design

Desktop puts the thread root id in the tag. It does not use the selected
message id.
Desktop publishes kind 9. The CLI does the same.
The CLI checks the root event before it publishes anything.
The root must exist. The root must have the same `h` tag as `--channel`.
The root must be a thread root. If the id belongs to a reply, the CLI
fails and prints the real root id.
A missing root exits with a not-found error. Other failures exit with a
usage error.
No event is published after a failed check.
The excerpt comes from the root content.
It is a Rust port of `summarizeThreadRoot` in `sentFromThread.ts`.
The port clips by code point, as desktop does. It breaks words by UTF-16
unit, as desktop does.
`--from-thread` conflicts with `--reply-to` and `--broadcast`. It works
only with kind 9.
The relay does not validate this tag. Desktop's Tauri code validates it
(`message_tags.rs`).
The CLI output follows those rules: 64 hex characters, an excerpt of at
most 64 characters, and no control characters.
The change adds no dependency and no SDK change. The CLI appends the tag
to the SDK `build_message` builder.
The README describes the flag. This PR does not change `desktop/`
(deprecated; see #8840).

## Testing

What ran:
- `cargo test -p buzz-cli`: 508 passed.
- `cargo clippy -p buzz-cli --all-targets -- -D warnings`: clean.
- `cargo fmt --all --check`: clean.
- `just file-size-check`: passed.

Test coverage:
- One test calls `cmd_send_message` against a fake relay. It covers
success, a reply id, another channel, a missing root, an invalid id, and
a non-kind-9 send. The failure cases publish nothing. The success case
publishes one event with the tag and without `e` or `broadcast` tags.
- One test checks the clap conflicts.
- Unit tests cover the excerpt boundaries (63, 64, 65 characters, the
word-break threshold, and multi-byte and emoji text).
- One test compares the Rust excerpt with 639 vectors. Desktop's own
`summarizeThreadRoot` generated the vectors. The first run found an
ordering bug (desktop replaces each pattern over the whole text in
turn). The code now does the same.

Interop evidence:
- A node script runs the real `buzz` binary against a local fake relay.
It is not a live relay.
- The script passes the published tags to desktop's
`getSentFromThreadReference` and `getSentFromThreadRootId`.
- Desktop accepts the tag, returns the same root id, and returns the
same excerpt as `summarizeThreadRoot`.
- Output: `["buzz:sent-from-thread","abab…ab","Launch plan for the doc 😀
word word word word word word word…"]` then `INTEROP OK`.

Mutation checks. Each guard was broken once. A test failed each time.
Then the guard was restored.
- Channel check removed: success test fails.
- Reply-root check removed: success test fails.
- Kind check removed: success test fails.
- `--broadcast` conflict removed: clap test fails.
- Word-break threshold `>` changed to `>=`: three excerpt tests fail.
- UTF-16 break changed to code-point break: two excerpt tests fail.
- Tag not attached: success test fails.

## Manual test steps

Use a test channel. You need `BUZZ_RELAY_URL` and `BUZZ_PRIVATE_KEY`
set. Build with `cargo build -p buzz-cli`.

1. Post a root message. Copy its event id as `ROOT`.
`buzz messages send --channel <CHANNEL> --content "Launch plan:
[doc](https://example.com)"`
2. Send it to the channel.
`buzz messages send --channel <CHANNEL> --content "Decision: ship it"
--from-thread $ROOT`
Success: the output has `"accepted":true`. A new top-level message
appears in the channel.
3. Read it back.
   `buzz --format json messages get --channel <CHANNEL> --limit 1`
Success: the tags include `["buzz:sent-from-thread","<ROOT>","Launch
plan: doc"]`. There are no `e` tags.
4. Open the channel in desktop.
   Success: the message shows the "sent from thread" line for that root.
5. Try a reply id.
`buzz messages send --channel <CHANNEL> --content x --from-thread
<REPLY_ID>`
Success: exit code 1. The error names the root id. Nothing is published.
6. Try a root from another channel, and try `--reply-to` together with
`--from-thread`.
   Success: both fail and publish nothing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: bmcanally <bmcanally@squareup.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 17:28:19 -06:00
..
…