kyyn_core/progress.rs
1//! Host-side progress reporting for long-running plugin work — a FACILITY,
2//! not part of the plugin wire contract. Plugins call [`report`] at natural
3//! milestones (a page fetched, a file downloaded); the host decides where
4//! the lines go. Default: stderr, which every surface tolerates (the CLI
5//! shows it live; MCP stdout stays pure JSON-RPC). Future stdio plugins map
6//! their stderr onto the same sink — the contract itself stays untouched.
7
8use std::sync::RwLock;
9
10#[allow(clippy::type_complexity)]
11static SINK: RwLock<Option<Box<dyn Fn(&str) + Send + Sync>>> = RwLock::new(None);
12
13/// Route progress lines somewhere other than stderr (a UI, a log).
14pub fn set_sink(f: impl Fn(&str) + Send + Sync + 'static) {
15 *SINK.write().expect("progress sink lock") = Some(Box::new(f));
16}
17
18/// One short line of progress ("142 messages (3 pages)…"). Cheap enough to
19/// call in loops at coarse milestones; never call per record.
20pub fn report(msg: &str) {
21 match &*SINK.read().expect("progress sink lock") {
22 Some(f) => f(msg),
23 None => eprintln!(" … {msg}"),
24 }
25}