Conversation
FeedCmd had exactly one variant, Get, which issues a single REST query and returns. An agent that wants to react to mentions had to poll feed get on a timer and dedupe across window boundaries, paying a latency floor equal to the poll interval and carrying its own cursor. buzz-ws-client is already a buzz-cli dependency and already exposes everything a subscription needs (connect_authenticated, send_raw, next_event, disconnect, and a RelayMessage enum). Only the one-shot publish half was being used. Add buzz feed watch: authenticate, send a NIP-01 REQ with the same filter cmd_get_feed builds, then print one JSON event per line and flush. Eose is silent, notices and closures go to stderr, and an Auth challenge triggers re-authentication. Ctrl-C closes the subscription and disconnects cleanly. NDJSON rather than a JSON array because the stream never terminates, so the array would never close. feed get keeps its array output unchanged. parse_feed_types and build_feed_filter are extracted so get and watch validate identical input and select identical events; a regression test pins the no-flags filter to what feed get sent before. tokio gains the signal feature for ctrl_c. Signed-off-by: Matt Van Horn <mvanhorn@gmail.com>
…atch-stream # Conflicts: # crates/buzz-cli/src/lib.rs
|
I opened #6979 as a complementary bounded one-shot primitive. It adds |
|
Thanks for flagging #6979 -- I don't think these collide. Yours is a bounded one-shot: wait for the first matching reply in one channel/thread, then exit. This one is a continuous, unfiltered activity stream that keeps running. Different lifetimes and different consumers. On naming: On the shared primitive -- yes, worth doing, but better as a follow-up than as a dependency between two open PRs. If both land, the common subscription/reconnect layer is a clean extraction with tests already covering both call sites. If maintainers would rather see it factored first, I'm happy to do that instead; I'd just want that to be their call. |
Summary
Simulated demo. Terminal frames are verbatim captured output — the opening is this branch's parent commit answering
unrecognized subcommand 'watch'. The event-delivery diagram is labelled illustrative — not a live capture because no relay was run, and the closing frame states that the receive loop has no automated coverage yet.FeedCmdhas exactly one variant,Get, which issues a single REST query and returns. An agent that wants to react to mentions has to pollbuzz feed get --since <ts>on a timer, which costs a latency floor equal to the poll interval, delivers duplicates at window boundaries that the caller has to dedupe by event id, and pushes cursor management onto the caller.The pieces for a subscription are already in the tree and unused.
buzz-ws-clientis already abuzz-clidependency ("WebSocket client — ephemeral event publish") and already exposesconnect_authenticated,send_raw,next_event,disconnect, and aRelayMessageenum coveringEvent/Eose/Closed/Notice/Auth/Count. Today the CLI uses exactly one of these, for one-shot ephemeral publish. The subscription half of the same client was never wired up.buzz feed watchauthenticates, sends a NIP-01REQwith the same filtercmd_get_feedbuilds, and prints one JSON event per line, flushing immediately:Eoseis silent (it only marks the end of backfill).jq --unbufferedconsumer.Authchallenge triggers re-authentication.--sincereplays backfill first and then stays live, so an agent restarting after a crash resumes from its last seen timestamp in one command.NDJSON rather than a JSON array because the stream never terminates, so the array would never close.
buzz feed getkeeps its existing array output untouched.parse_feed_typesandbuild_feed_filterare extracted fromcmd_get_feedsogetandwatchreject identical bad input and select identical events.tokiogains thesignalandtimefeatures forctrl_cand the idle deadline.Related issue
None found. Searched open issues and PRs for
feed watch,stream subscribe cli— zero hits. #944 (feat(agent): SSE streaming for LLM completions, closed unmerged) is LLM token streaming insidebuzz-agentand shares no surface with a relay subscription.Every comparable platform ships a listen primitive: Slack Socket Mode,
mmctl websocket, Zulip'scall_on_each_message.Testing
cargo test -p buzz-cli— 258 passed, 0 failed. New unit tests incommands::feed::tests, no relay required:CliError::Usage; surrounding whitespace trimmed (parity with today'sfeed getbehavior)feed getsent before this change (regression guard)since,#h,feed_types,limit) appear only when suppliedjust fmtandcargo clippy -p buzz-cli --all-targets -- -D warningsare both clean.Local verification:
Input validation returns before any relay connection is attempted.
Three details in the receive loop are worth a reviewer's attention, since they are the parts a stub test would not have caught:
WsClientError::Timeoutcounts as an idle tick. Any other error — a closed socket, a transport failure — returnsErrrather than being retried, so a dropped connection surfaces instead of spinning.select!branch rather than a check afternext_eventreturns, becausenext_eventanswers relay Pings internally and restarts its own timeout; a chatty relay would otherwise keep it pending past the deadline forever. The deadline resets only on a deliveredEvent.AUTHand withholdsOKcannot swallow a SIGINT or stretch--idle-timeout.The receive loop itself has no automated coverage — that needs a stub WebSocket server.
Cargo.tomlalready carriesaxumas a dev-dependency for "minimal HTTP test server for retry/policy integration tests", so atests/feed_watch.rsdriving a stub relay is the shape to follow if you would like that before merge. Happy to add it.No UI change.
crates/buzz-cli/README.mddocuments the subcommand and the NDJSON contract.AI was used for assistance. I wrote, ran, and reviewed the final code and tests.