From 50804996b322f512a6bea791485d6410e87a019b Mon Sep 17 00:00:00 2001 From: Nick DiZazzo Date: Sun, 13 Sep 2026 12:44:59 -0400 Subject: [PATCH 1/3] feat(events): add a sink-aware console writer for CLI output CLI presentation code has no sanctioned way to emit human-facing text: it reaches for println! directly, so every call site independently decides whether bytes hit the terminal. That breaks --json parseability and paints over the interactive dashboard. Add console_out()/console_err(), which hand back an io::Write that discards when a JSON sink or the interactive TUI owns the terminal, plus machine_out() for --json payloads that must always reach stdout. The sink now decides whether text is rendered, not the call site. --- crates/mesh-llm-events/src/console.rs | 187 ++++++++++++++++++++++++++ crates/mesh-llm-events/src/lib.rs | 2 + 2 files changed, 189 insertions(+) create mode 100644 crates/mesh-llm-events/src/console.rs diff --git a/crates/mesh-llm-events/src/console.rs b/crates/mesh-llm-events/src/console.rs new file mode 100644 index 0000000000..1c7d560c94 --- /dev/null +++ b/crates/mesh-llm-events/src/console.rs @@ -0,0 +1,187 @@ +//! Sanctioned writers for user-facing console text. +//! +//! CLI presentation code renders prose, tables and prompts that are meant for a +//! human reading a terminal. That text is not telemetry, so it does not belong +//! in [`crate::OutputEvent`]; but it must still respect who currently owns the +//! terminal. Writing it with `println!` hard-codes that decision at every call +//! site, which is how free-form text ends up interleaved with `--json` payloads +//! or painted over the interactive dashboard. +//! +//! These writers move the decision to the sink. A call site asks for a handle +//! and writes into it; whether the bytes reach the terminal is decided here, +//! once, from the currently installed [`crate::OutputSink`]: +//! +//! * [`console_out`] / [`console_err`] carry human-facing text. They discard +//! their input while a JSON sink is installed or while the interactive +//! dashboard owns the screen. +//! * [`machine_out`] carries machine-readable payloads โ€” the document a +//! `--json` command was asked to produce. It always reaches stdout, because +//! suppressing it would mean answering a request with nothing. +//! +//! One-shot CLI commands run before any sink is installed, so both writers pass +//! through to the terminal there. + +use std::io::{self, IsTerminal, Write}; + +use crate::{interactive_tui_active, json_mode_enabled}; + +/// Where a [`ConsoleWriter`] sends the bytes it is given. +enum ConsoleTarget { + Stdout(io::Stdout), + Stderr(io::Stderr), + /// Another surface owns the terminal; bytes are accepted and dropped. + Discard, +} + +/// A console handle obtained from the output facility. +/// +/// Implements [`Write`], so call sites use `write!` / `writeln!` exactly as +/// they would against any other stream. +pub struct ConsoleWriter { + target: ConsoleTarget, +} + +impl ConsoleWriter { + fn stdout() -> Self { + Self { + target: ConsoleTarget::Stdout(io::stdout()), + } + } + + fn stderr() -> Self { + Self { + target: ConsoleTarget::Stderr(io::stderr()), + } + } + + fn discard() -> Self { + Self { + target: ConsoleTarget::Discard, + } + } + + /// Whether this handle is attached to a terminal. + /// + /// Use this instead of probing `io::stdout()` directly when deciding + /// whether to emit ANSI styling: a suppressed handle reports `false`, so + /// escape sequences are not built for output nobody will read. + pub fn is_terminal(&self) -> bool { + match &self.target { + ConsoleTarget::Stdout(stream) => stream.is_terminal(), + ConsoleTarget::Stderr(stream) => stream.is_terminal(), + ConsoleTarget::Discard => false, + } + } + + /// Whether writes to this handle are being dropped. + /// + /// Useful for skipping expensive rendering (table layout, progress + /// redraws) that would otherwise be built and thrown away. + pub fn is_suppressed(&self) -> bool { + matches!(self.target, ConsoleTarget::Discard) + } +} + +impl Write for ConsoleWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + match &mut self.target { + ConsoleTarget::Stdout(stream) => stream.write(buf), + ConsoleTarget::Stderr(stream) => stream.write(buf), + ConsoleTarget::Discard => Ok(buf.len()), + } + } + + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + match &mut self.target { + ConsoleTarget::Stdout(stream) => stream.write_all(buf), + ConsoleTarget::Stderr(stream) => stream.write_all(buf), + ConsoleTarget::Discard => Ok(()), + } + } + + fn flush(&mut self) -> io::Result<()> { + match &mut self.target { + ConsoleTarget::Stdout(stream) => stream.flush(), + ConsoleTarget::Stderr(stream) => stream.flush(), + ConsoleTarget::Discard => Ok(()), + } + } +} + +/// Whether a surface other than plain console output currently owns the +/// terminal. +fn terminal_owned_elsewhere() -> bool { + json_mode_enabled() || interactive_tui_active() +} + +/// Human-facing console output on stdout. +/// +/// Suppressed while a JSON sink is installed or the interactive dashboard is +/// active. +pub fn console_out() -> ConsoleWriter { + if terminal_owned_elsewhere() { + ConsoleWriter::discard() + } else { + ConsoleWriter::stdout() + } +} + +/// Human-facing console output on stderr โ€” diagnostics, warnings and prompts +/// that should not contaminate a piped stdout. +/// +/// Suppressed under the same conditions as [`console_out`]. +pub fn console_err() -> ConsoleWriter { + if terminal_owned_elsewhere() { + ConsoleWriter::discard() + } else { + ConsoleWriter::stderr() + } +} + +/// Machine-readable command output on stdout. +/// +/// This is the payload a `--json` command was invoked to produce, so it is +/// never suppressed. +pub fn machine_out() -> ConsoleWriter { + ConsoleWriter::stdout() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn discarding_writer_accepts_and_drops_bytes() { + let mut writer = ConsoleWriter::discard(); + assert_eq!(writer.write(b"hello").expect("write succeeds"), 5); + writer.write_all(b"world").expect("write_all succeeds"); + writer.flush().expect("flush succeeds"); + } + + #[test] + fn discarding_writer_reports_no_terminal_and_suppression() { + let writer = ConsoleWriter::discard(); + assert!(writer.is_suppressed()); + assert!(!writer.is_terminal()); + } + + #[test] + fn stream_writers_are_not_suppressed() { + assert!(!ConsoleWriter::stdout().is_suppressed()); + assert!(!ConsoleWriter::stderr().is_suppressed()); + } + + #[test] + fn machine_output_is_never_suppressed() { + assert!(!machine_out().is_suppressed()); + } + + #[test] + fn writers_pass_through_without_an_installed_sink() { + // One-shot CLI commands run before any sink exists; console text must + // still reach the terminal there. + crate::clear_output_sink(); + assert!(!console_out().is_suppressed()); + assert!(!console_err().is_suppressed()); + } +} diff --git a/crates/mesh-llm-events/src/lib.rs b/crates/mesh-llm-events/src/lib.rs index ad373ac962..9271def2ac 100644 --- a/crates/mesh-llm-events/src/lib.rs +++ b/crates/mesh-llm-events/src/lib.rs @@ -8,6 +8,7 @@ use std::pin::Pin; use std::sync::{Arc, OnceLock, RwLock}; pub mod audit; +pub mod console; pub mod logging; pub mod terminal_progress; @@ -17,6 +18,7 @@ pub use command_lifecycle::{ CliCommandFamily, CliCommandOutcome, CliCommandSummary, emit_cli_command_event, set_cli_command_event_verbose, }; +pub use console::{ConsoleWriter, console_err, console_out, machine_out}; #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, ValueEnum)] pub enum LogFormat { From b17ce2075a929738cf41573ac9aebd9a575707de Mon Sep 17 00:00:00 2001 From: Nick DiZazzo Date: Sun, 13 Sep 2026 13:19:37 -0400 Subject: [PATCH 2/3] refactor(cli): route console output through the sink-aware writer The CLI presentation layer printed with `println!`/`eprintln!`, so each call site decided on its own whether bytes reached the terminal. That is the wrong place for the decision: it is what lets free-form text land in `--json` output and what lets library-adjacent code paint over the interactive dashboard. Convert every console print in `mesh-llm` and `mesh-llm-commands` to write into a handle from `mesh_llm_events::console_out()` / `console_err()`, and send `--json` payloads through `machine_out()`. Rendering is unchanged - the sink now decides whether the text is emitted, suppressed for a JSON sink, or held back while the dashboard owns the terminal. This is plumbing only. Captured stdout, stderr and exit status for 34 invocations across every touched command family, in both pretty and `--json` modes, before and after: byte-identical apart from one line echoing the running binary's own absolute path. The console-print ratchet drops from 690 approvals across 55 files to 133 across 29, leaving zero approvals in either converted crate. --- crates/mesh-llm-commands/src/agent_cli.rs | 108 +- crates/mesh-llm-commands/src/auth.rs | 308 +- crates/mesh-llm-commands/src/config.rs | 27 +- crates/mesh-llm-commands/src/doctor.rs | 7 +- crates/mesh-llm-commands/src/gpus.rs | 32 +- .../src/gpus/tune/benchmark/mod.rs | 8 +- .../src/gpus/tune/benchmark_progress.rs | 73 +- crates/mesh-llm-commands/src/model_package.rs | 224 +- crates/mesh-llm-commands/src/plugin.rs | 92 +- .../mesh-llm-commands/src/runtime_native.rs | 32 +- .../src/runtime_native/formatters.rs | 162 +- crates/mesh-llm-commands/src/setup/command.rs | 20 +- crates/mesh-llm-commands/src/setup/summary.rs | 55 +- crates/mesh-llm-commands/src/skills.rs | 53 +- crates/mesh-llm-commands/src/terminal.rs | 12 +- crates/mesh-llm-commands/src/uninstall.rs | 17 +- crates/mesh-llm/src/commands/discover.rs | 123 +- crates/mesh-llm/src/commands/doctor.rs | 13 +- crates/mesh-llm/src/commands/download.rs | 12 +- .../src/commands/models/formatters.rs | 4 +- .../src/commands/models/formatters_console.rs | 173 +- crates/mesh-llm/src/commands/models/mod.rs | 175 +- crates/mesh-llm/src/commands/plugin_cli.rs | 18 +- crates/mesh-llm/src/commands/runtime.rs | 57 +- crates/mesh-llm/src/lib.rs | 16 +- crates/mesh-llm/src/main.rs | 8 +- tools/xtask/data/console_print_allowlist.json | 2560 +---------------- 27 files changed, 1287 insertions(+), 3102 deletions(-) diff --git a/crates/mesh-llm-commands/src/agent_cli.rs b/crates/mesh-llm-commands/src/agent_cli.rs index 83bf472981..2f48a1e319 100644 --- a/crates/mesh-llm-commands/src/agent_cli.rs +++ b/crates/mesh-llm-commands/src/agent_cli.rs @@ -1,5 +1,6 @@ use anyhow::{Context, Result}; use mesh_llm_plugin_manager::SkillAgent; +use std::io::Write; use std::process::{Command, Stdio}; use crate::skills::install_skills_for_agent; @@ -171,7 +172,8 @@ fn write_goose_mcp_config_to_path(path: &std::path::Path, mcp_url: &str) -> Resu let mut config = read_goose_config(path)?; merge_goose_mcp_config(&mut config, mcp_url, path)?; std::fs::write(path, serde_yaml::to_string(&config)?)?; - eprintln!("โœ… Wrote mesh MCP extension to {}", path.display()); + let mut err = mesh_llm_events::console_err(); + writeln!(err, "โœ… Wrote mesh MCP extension to {}", path.display())?; Ok(()) } @@ -388,7 +390,8 @@ fn pi_missing_binary_guidance(model_arg: &str) -> Vec { fn cleanup_mesh_child(mesh_child: &mut Option) { if let Some(child) = mesh_child { - eprintln!("๐Ÿงน Stopping mesh-llm node we started..."); + let mut err = mesh_llm_events::console_err(); + let _ = writeln!(err, "๐Ÿงน Stopping mesh-llm node we started..."); let _ = child.kill(); let _ = child.wait(); } @@ -400,11 +403,15 @@ async fn check_mesh( port: u16, model: &Option, ) -> Result<(Vec, String, Option)> { + let mut err = mesh_llm_events::console_err(); let url = format!("http://127.0.0.1:{port}/v1/models"); let mut child: Option = None; if client.get(&url).send().await.is_err() { - eprintln!("๐Ÿš€ No mesh-llm on port {port}; starting background auto-join node"); + writeln!( + err, + "๐Ÿš€ No mesh-llm on port {port}; starting background auto-join node" + )?; let exe = std::env::current_exe().unwrap_or_else(|_| "mesh-llm".into()); child = Some( std::process::Command::new(&exe) @@ -434,10 +441,11 @@ async fn check_mesh( } tokio::time::sleep(std::time::Duration::from_secs(3)).await; if attempt % 5 == 4 { - eprintln!( + writeln!( + err, "โณ Waiting for mesh/models... ({:.0}s)", (attempt + 1) as f64 * 3.0 - ); + )?; } } @@ -453,8 +461,8 @@ async fn check_mesh( } let chosen = choose_requested_or_agent_model(&models, model, &mut child)?; - eprintln!(" Models: {}", models.join(", ")); - eprintln!(" Using: {chosen}"); + writeln!(err, " Models: {}", models.join(", "))?; + writeln!(err, " Using: {chosen}")?; Ok((models, chosen, child)) } @@ -539,8 +547,9 @@ async fn fetch_mesh_models( choose_agent_model(&models) }; - eprintln!(" Models: {}", models.join(", ")); - eprintln!(" Using: {chosen}"); + let mut err = mesh_llm_events::console_err(); + writeln!(err, " Models: {}", models.join(", "))?; + writeln!(err, " Using: {chosen}")?; Ok((models, chosen)) } @@ -579,13 +588,15 @@ pub async fn run_goose(model: Option, port: u16) -> Result<()> { let provider_path = goose_config_dir.join("mesh.json"); std::fs::write(&provider_path, serde_json::to_string_pretty(&provider)?)?; - eprintln!("โœ… Wrote {}", provider_path.display()); + let mut err = mesh_llm_events::console_err(); + writeln!(err, "โœ… Wrote {}", provider_path.display())?; write_goose_mcp_config(DEFAULT_MESH_MCP_URL)?; install_skills_for_agent(SkillAgent::Goose); let goose_app = std::path::Path::new("/Applications/Goose.app"); if goose_app.exists() { - eprintln!("๐Ÿชฟ Launching Goose.app..."); + writeln!(err, "๐Ÿชฟ Launching Goose.app...")?; + let _ = err.flush(); std::process::Command::new("open") .arg("-a") .arg(goose_app) @@ -593,12 +604,14 @@ pub async fn run_goose(model: Option, port: u16) -> Result<()> { .env("GOOSE_MODEL", &chosen) .spawn()?; if mesh_child.is_some() { - eprintln!( + writeln!( + err, "โ„น๏ธ mesh-llm node running in background (kill manually or use `mesh-llm stop`)" - ); + )?; } } else { - eprintln!("๐Ÿชฟ Launching goose session..."); + writeln!(err, "๐Ÿชฟ Launching goose session...")?; + let _ = err.flush(); let mut command = Command::new("goose"); command .arg("session") @@ -608,15 +621,21 @@ pub async fn run_goose(model: Option, port: u16) -> Result<()> { let status = command.status(); match status { Ok(s) if s.success() => {} - Ok(s) => eprintln!("goose exited with {s}"), + Ok(s) => writeln!(err, "goose exited with {s}")?, Err(_) => { - eprintln!("goose not found. Install: https://github.com/block/goose"); - eprintln!("Or run manually:"); - eprintln!(" GOOSE_PROVIDER=mesh GOOSE_MODEL={chosen} goose session"); + writeln!( + err, + "goose not found. Install: https://github.com/block/goose" + )?; + writeln!(err, "Or run manually:")?; + writeln!( + err, + " GOOSE_PROVIDER=mesh GOOSE_MODEL={chosen} goose session" + )?; } } if let Some(ref mut c) = mesh_child { - eprintln!("๐Ÿงน Stopping mesh-llm node we started..."); + writeln!(err, "๐Ÿงน Stopping mesh-llm node we started...")?; let _ = c.kill(); let _ = c.wait(); } @@ -661,7 +680,9 @@ pub async fn run_claude(model: Option, port: u16) -> Result<()> { let mcp_config_json = mesh_mcp_claude_config_json(DEFAULT_MESH_MCP_URL)?; install_skills_for_agent(SkillAgent::Claude); - eprintln!("๐Ÿš€ Launching Claude Code with {chosen} โ†’ {base_url}\n"); + let mut err = mesh_llm_events::console_err(); + writeln!(err, "๐Ÿš€ Launching Claude Code with {chosen} โ†’ {base_url}\n")?; + let _ = err.flush(); let mut command = Command::new("claude"); command.args([ "--model", @@ -675,15 +696,21 @@ pub async fn run_claude(model: Option, port: u16) -> Result<()> { let status = command.status(); match status { Ok(s) if s.success() => {} - Ok(s) => eprintln!("claude exited with {s}"), + Ok(s) => writeln!(err, "claude exited with {s}")?, Err(_) => { - eprintln!("claude not found. Install: https://docs.anthropic.com/en/docs/claude-code"); - eprintln!("Or run manually:"); - eprintln!(" ANTHROPIC_BASE_URL={base_url} ANTHROPIC_API_KEY= claude --model {chosen}"); + writeln!( + err, + "claude not found. Install: https://docs.anthropic.com/en/docs/claude-code" + )?; + writeln!(err, "Or run manually:")?; + writeln!( + err, + " ANTHROPIC_BASE_URL={base_url} ANTHROPIC_API_KEY= claude --model {chosen}" + )?; } } if let Some(ref mut c) = mesh_child { - eprintln!("๐Ÿงน Stopping mesh-llm node we started..."); + writeln!(err, "๐Ÿงน Stopping mesh-llm node we started...")?; let _ = c.kill(); let _ = c.wait(); } @@ -843,11 +870,13 @@ fn write_pi_config_to_path_with_limits( merge_provider(&mut config, "providers", "mesh", provider, models_path)?; std::fs::write(models_path, serde_json::to_string_pretty(&config)?)?; - eprintln!( + let mut err = mesh_llm_events::console_err(); + writeln!( + err, "โœ… Wrote mesh provider to {} ({} models)", models_path.display(), model_names.len() - ); + )?; Ok(()) } @@ -908,17 +937,19 @@ fn run_pi_with_mesh( } let model_arg = format!("mesh/{chosen}"); - eprintln!("๐Ÿš€ Launching pi with {chosen} โ†’ {base_url}\n"); + let mut err = mesh_llm_events::console_err(); + writeln!(err, "๐Ÿš€ Launching pi with {chosen} โ†’ {base_url}\n")?; + let _ = err.flush(); let mut command = Command::new("pi"); command.args(["--model", &model_arg]); configure_interactive_stdio(&mut command); let status = command.status(); match status { Ok(s) if s.success() => {} - Ok(s) => eprintln!("pi exited with {s}"), + Ok(s) => writeln!(err, "pi exited with {s}")?, Err(_) => { for line in pi_missing_binary_guidance(&model_arg) { - eprintln!("{line}"); + writeln!(err, "{line}")?; } } } @@ -959,21 +990,24 @@ pub async fn run_opencode(model: Option, host: &str, write: bool) -> Res &context_lengths, ); - eprintln!( + let mut err = mesh_llm_events::console_err(); + writeln!( + err, "๐Ÿš€ Launching OpenCode with {} โ†’ {}\n", chosen, target.api_base_url - ); + )?; + let _ = err.flush(); install_skills_for_agent(SkillAgent::Opencode); let mut command = Command::new("opencode"); configure_opencode_launch_command(&mut command, &spec); let status = command.status(); match status { Ok(s) if s.success() => {} - Ok(s) => eprintln!("opencode exited with {s}"), + Ok(s) => writeln!(err, "opencode exited with {s}")?, Err(_) => { for line in opencode_missing_binary_guidance(&chosen, &target.input, &spec) { - eprintln!("{line}"); + writeln!(err, "{line}")?; } } } @@ -1134,11 +1168,13 @@ async fn write_opencode_config_to_path( let formatted_json = serde_json::to_string_pretty(&merged_config)?; std::fs::write(config_path, &formatted_json)?; - eprintln!( + let mut err = mesh_llm_events::console_err(); + writeln!( + err, "โœ… Wrote {} ({} models)", config_path.display(), model_names.len() - ); + )?; Ok(()) } diff --git a/crates/mesh-llm-commands/src/auth.rs b/crates/mesh-llm-commands/src/auth.rs index 0b9004fa50..03c89279e4 100644 --- a/crates/mesh-llm-commands/src/auth.rs +++ b/crates/mesh-llm-commands/src/auth.rs @@ -1,4 +1,4 @@ -use std::io::IsTerminal; +use std::io::{IsTerminal, Write}; use std::path::{Path, PathBuf}; use std::result::Result as StdResult; @@ -341,47 +341,59 @@ pub(crate) fn run_init( None, )); - eprintln!(); - eprintln!("Owner keystore created."); - eprintln!("Owner ID: {owner_id}"); - eprintln!("Signing key: {sign_pk}"); - eprintln!("Encryption key: {enc_pk}"); - eprintln!("Path: {}", path.display()); - eprintln!("Encrypted: {}", if encrypted { "yes" } else { "no" }); + let mut err = mesh_llm_events::console_err(); + writeln!(err)?; + writeln!(err, "Owner keystore created.")?; + writeln!(err, "Owner ID: {owner_id}")?; + writeln!(err, "Signing key: {sign_pk}")?; + writeln!(err, "Encryption key: {enc_pk}")?; + writeln!(err, "Path: {}", path.display())?; + writeln!( + err, + "Encrypted: {}", + if encrypted { "yes" } else { "no" } + )?; match &source { PassphraseSource::Keychain { account } => { - eprintln!( + writeln!( + err, "Unlock: OS keychain (service={KEYCHAIN_SERVICE}, account={account})" - ); + )?; } PassphraseSource::Prompt => { - eprintln!("Unlock: passphrase prompt"); + writeln!(err, "Unlock: passphrase prompt")?; } PassphraseSource::None => {} } - eprintln!(); - eprintln!("Next steps:"); + writeln!(err)?; + writeln!(err, "Next steps:")?; match &source { PassphraseSource::Keychain { account } => { - eprintln!( + writeln!( + err, "- This keystore is unlock-bound to this machine's keychain. To share the same \ owner identity on another node, retrieve the passphrase from your OS keychain \ (service={KEYCHAIN_SERVICE}, account={account}) and enter it there, or re-run \ `auth init` with a manual passphrase so the same passphrase can be used everywhere." - ); + )?; } PassphraseSource::Prompt | PassphraseSource::None => { - eprintln!( + writeln!( + err, "- Copy this keystore to other trusted nodes that should share the same owner identity." - ); + )?; } } - eprintln!("- Start mesh-llm and it will automatically attest nodes from this keystore."); + writeln!( + err, + "- Start mesh-llm and it will automatically attest nodes from this keystore." + )?; if custom_owner_key { - eprintln!( + writeln!( + err, "- Pass --owner-key {} when starting mesh-llm.", path.display() - ); + )?; } Ok(()) @@ -399,72 +411,92 @@ pub(crate) fn run_status( let node_ownership_path = resolve_node_ownership_path(node_ownership)?; let trust_store_path = resolve_trust_store_path(trust_store)?; + let mut err = mesh_llm_events::console_err(); + if !keystore_exists(&owner_key_path) { - eprintln!("No owner keystore found at {}", owner_key_path.display()); - eprintln!("Run `mesh-llm auth init` to create one."); + writeln!( + err, + "No owner keystore found at {}", + owner_key_path.display() + )?; + writeln!(err, "Run `mesh-llm auth init` to create one.")?; } else { let info = keystore_metadata(&owner_key_path)?; - eprintln!("Owner keystore: {}", owner_key_path.display()); - eprintln!("Status: present"); - eprintln!( + writeln!(err, "Owner keystore: {}", owner_key_path.display())?; + writeln!(err, "Status: present")?; + writeln!( + err, "Encrypted: {}", if info.encrypted { "yes" } else { "no" } - ); - eprintln!("Owner ID: {}", info.owner_id); + )?; + writeln!(err, "Owner ID: {}", info.owner_id)?; if let Some(ref spk) = info.signing_public_key { - eprintln!("Signing key: {spk}"); + writeln!(err, "Signing key: {spk}")?; } if let Some(ref epk) = info.encryption_public_key { - eprintln!("Encryption key: {epk}"); + writeln!(err, "Encryption key: {epk}")?; } - eprintln!("Created: {}", info.created_at); + writeln!(err, "Created: {}", info.created_at)?; if info.encrypted { match load_owner_keypair_from_keychain(&owner_key_path) { Ok(_) => { - eprintln!("Keystore: valid (unlocked from OS keychain)"); + writeln!(err, "Keystore: valid (unlocked from OS keychain)")?; } Err(OwnerKeychainLoadError::Crypto(e)) => { - eprintln!( + writeln!( + err, "{}", encrypted_keystore_keychain_status(OwnerKeychainLoadError::Crypto(e)) - ); + )?; } - Err(e) => eprintln!("{}", encrypted_keystore_keychain_status(e)), + Err(e) => writeln!(err, "{}", encrypted_keystore_keychain_status(e))?, } } else { match load_keystore(&owner_key_path, None) { Ok(_) => { - eprintln!("Keystore: valid (keys loaded successfully)"); + writeln!(err, "Keystore: valid (keys loaded successfully)")?; } Err(e) => { - eprintln!("Keystore: ERROR loading keys: {e}"); + writeln!(err, "Keystore: ERROR loading keys: {e}")?; } } } } - eprintln!(); + writeln!(err)?; let node_secret_key = if node_key_path.exists() { let node_secret_key = load_node_key_from_path(&node_key_path)?; let node_id = EndpointId::from(node_secret_key.public()); - eprintln!("Node key: {}", node_key_path.display()); - eprintln!("Node ID: {}", hex::encode(node_id.as_bytes())); + writeln!(err, "Node key: {}", node_key_path.display())?; + writeln!(err, "Node ID: {}", hex::encode(node_id.as_bytes()))?; Some(node_secret_key) } else { - eprintln!("Node key: missing ({})", node_key_path.display()); + writeln!( + err, + "Node key: missing ({})", + node_key_path.display() + )?; None }; let trust_store = load_effective_trust_store(&trust_store_path)?; - eprintln!("Trust store: {}", trust_store_path.display()); - eprintln!("Trust policy: {:?}", trust_store.policy); - eprintln!("Trusted owners: {}", trust_store.trusted_owners.len()); - eprintln!("Revoked owners: {}", trust_store.revoked_owners.len()); - eprintln!("Revoked certs: {}", trust_store.revoked_node_certs.len()); - eprintln!("Revoked node IDs:{}", trust_store.revoked_node_ids.len()); + writeln!(err, "Trust store: {}", trust_store_path.display())?; + writeln!(err, "Trust policy: {:?}", trust_store.policy)?; + writeln!(err, "Trusted owners: {}", trust_store.trusted_owners.len())?; + writeln!(err, "Revoked owners: {}", trust_store.revoked_owners.len())?; + writeln!( + err, + "Revoked certs: {}", + trust_store.revoked_node_certs.len() + )?; + writeln!( + err, + "Revoked node IDs:{}", + trust_store.revoked_node_ids.len() + )?; - eprintln!(); + writeln!(err)?; if node_ownership_path.exists() { let ownership = load_node_ownership(&node_ownership_path)?; @@ -488,33 +520,40 @@ pub(crate) fn run_status( trust_store.policy, now_unix_ms(), ); - eprintln!("Node cert: {}", node_ownership_path.display()); - eprintln!("Cert ID: {}", ownership.claim.cert_id); - eprintln!("Claim node ID: {}", ownership.claim.node_endpoint_id); - eprintln!( + writeln!(err, "Node cert: {}", node_ownership_path.display())?; + writeln!(err, "Cert ID: {}", ownership.claim.cert_id)?; + writeln!(err, "Claim node ID: {}", ownership.claim.node_endpoint_id)?; + writeln!( + err, "Owner ID: {}", summary .owner_id .as_deref() .unwrap_or(ownership.claim.owner_id.as_str()) - ); - eprintln!("Status: {:?}", summary.status); - eprintln!( + )?; + writeln!(err, "Status: {:?}", summary.status)?; + writeln!( + err, "Verified: {}", if summary.verified { "yes" } else { "no" } - ); - eprintln!("Expires at: {}", ownership.claim.expires_at_unix_ms); + )?; + writeln!( + err, + "Expires at: {}", + ownership.claim.expires_at_unix_ms + )?; if let Some(node_label) = summary.node_label.as_deref() { - eprintln!("Node label: {node_label}"); + writeln!(err, "Node label: {node_label}")?; } if let Some(hostname_hint) = summary.hostname_hint.as_deref() { - eprintln!("Hostname hint: {hostname_hint}"); + writeln!(err, "Hostname hint: {hostname_hint}")?; } } else { - eprintln!( + writeln!( + err, "Node cert: missing ({})", node_ownership_path.display() - ); + )?; } Ok(()) @@ -550,14 +589,20 @@ pub(crate) fn run_sign_node( None, )); - eprintln!( + let mut err = mesh_llm_events::console_err(); + writeln!( + err, "Signed node certificate written to {}", output_path.display() - ); - eprintln!("Owner ID: {}", ownership.claim.owner_id); - eprintln!("Node ID: {}", ownership.claim.node_endpoint_id); - eprintln!("Cert ID: {}", ownership.claim.cert_id); - eprintln!("Expires at: {}", ownership.claim.expires_at_unix_ms); + )?; + writeln!(err, "Owner ID: {}", ownership.claim.owner_id)?; + writeln!(err, "Node ID: {}", ownership.claim.node_endpoint_id)?; + writeln!(err, "Cert ID: {}", ownership.claim.cert_id)?; + writeln!( + err, + "Expires at: {}", + ownership.claim.expires_at_unix_ms + )?; Ok(()) } @@ -604,17 +649,23 @@ pub(crate) fn run_verify_node( now_unix_ms(), ); - eprintln!("Certificate: {}", certificate_path.display()); - eprintln!("Owner ID: {}", ownership.claim.owner_id); - eprintln!("Node ID: {}", ownership.claim.node_endpoint_id); - eprintln!("Cert ID: {}", ownership.claim.cert_id); - eprintln!("Trust policy: {:?}", policy); - eprintln!("Status: {:?}", summary.status); - eprintln!( + let mut err = mesh_llm_events::console_err(); + writeln!(err, "Certificate: {}", certificate_path.display())?; + writeln!(err, "Owner ID: {}", ownership.claim.owner_id)?; + writeln!(err, "Node ID: {}", ownership.claim.node_endpoint_id)?; + writeln!(err, "Cert ID: {}", ownership.claim.cert_id)?; + writeln!(err, "Trust policy: {:?}", policy)?; + writeln!(err, "Status: {:?}", summary.status)?; + writeln!( + err, "Verified: {}", if summary.verified { "yes" } else { "no" } - ); - eprintln!("Expires at: {}", ownership.claim.expires_at_unix_ms); + )?; + writeln!( + err, + "Expires at: {}", + ownership.claim.expires_at_unix_ms + )?; Ok(()) } @@ -672,19 +723,25 @@ pub(crate) const RUN_ROTATE_NODE: RunRotateNodeFn = let new_key = SecretKey::generate(); save_node_key_to_path(&node_key_path, &new_key)?; - eprintln!("Node key rotated at {}", node_key_path.display()); + let mut err = mesh_llm_events::console_err(); + writeln!(err, "Node key rotated at {}", node_key_path.display())?; if let Some(previous_node_id) = previous_node_id { - eprintln!("Previous node ID:{previous_node_id}"); + writeln!(err, "Previous node ID:{previous_node_id}")?; } let new_node_id = hex::encode(EndpointId::from(new_key.public()).as_bytes()); - eprintln!("New node ID: {new_node_id}"); + writeln!(err, "New node ID: {new_node_id}")?; let owner_key_path = resolve_owner_key_path(owner_key)?; if !owner_key_path.exists() { - eprintln!("No owner keystore found at {}", owner_key_path.display()); - eprintln!( + writeln!( + err, + "No owner keystore found at {}", + owner_key_path.display() + )?; + writeln!( + err, "Run `mesh-llm auth init` or `mesh-llm auth sign-node` later to attest this node." - ); + )?; return Ok(()); } @@ -706,8 +763,8 @@ pub(crate) const RUN_ROTATE_NODE: RunRotateNodeFn = None, )); - eprintln!("New node certificate: {}", certificate_path.display()); - eprintln!("New cert ID: {}", ownership.claim.cert_id); + writeln!(err, "New node certificate: {}", certificate_path.display())?; + writeln!(err, "New cert ID: {}", ownership.claim.cert_id)?; Ok(()) }; @@ -733,7 +790,12 @@ pub(crate) fn run_revoke_owner( None, )); - eprintln!("Revoked owner {owner_id} in {}", trust_store_path.display()); + let mut err = mesh_llm_events::console_err(); + writeln!( + err, + "Revoked owner {owner_id} in {}", + trust_store_path.display() + )?; Ok(()) } @@ -769,6 +831,7 @@ pub(crate) fn run_revoke_node( // only after every requested revocation is durable. save_trust_store(&trust_store_path, &trust_store)?; + let mut err = mesh_llm_events::console_err(); if let Some(cert_id) = cert_id.as_ref() { let _ = emit_audit(audit_events::admin_action( Some("system".to_string()), @@ -777,7 +840,7 @@ pub(crate) fn run_revoke_node( true, None, )); - eprintln!("Revoked cert ID {cert_id}"); + writeln!(err, "Revoked cert ID {cert_id}")?; } if let Some(normalized) = normalized_node_id.as_ref() { let _ = emit_audit(audit_events::admin_action( @@ -787,9 +850,9 @@ pub(crate) fn run_revoke_node( true, None, )); - eprintln!("Revoked node ID {normalized}"); + writeln!(err, "Revoked node ID {normalized}")?; } - eprintln!("Updated trust store {}", trust_store_path.display()); + writeln!(err, "Updated trust store {}", trust_store_path.display())?; Ok(()) } @@ -836,10 +899,15 @@ pub(crate) fn run_rotate_owner( None, )); - eprintln!("Rotated owner keystore at {}", owner_key_path.display()); - eprintln!("New owner ID: {}", new_keypair.owner_id()); + let mut err = mesh_llm_events::console_err(); + writeln!( + err, + "Rotated owner keystore at {}", + owner_key_path.display() + )?; + writeln!(err, "New owner ID: {}", new_keypair.owner_id())?; if let Some(backup_path) = backup_path { - eprintln!("Backup: {}", backup_path.display()); + writeln!(err, "Backup: {}", backup_path.display())?; } Ok(()) @@ -856,7 +924,12 @@ pub(crate) fn run_trust_command(command: &TrustCommand) -> Result<()> { let mut store = load_effective_trust_store(&trust_store_path)?; store.add_trusted_owner(owner_id.clone(), label.clone()); save_trust_store(&trust_store_path, &store)?; - eprintln!("Trusted owner {owner_id} in {}", trust_store_path.display()); + let mut err = mesh_llm_events::console_err(); + writeln!( + err, + "Trusted owner {owner_id} in {}", + trust_store_path.display() + )?; } TrustCommand::Remove { owner_id, @@ -864,66 +937,69 @@ pub(crate) fn run_trust_command(command: &TrustCommand) -> Result<()> { } => { let trust_store_path = resolve_trust_store_path(trust_store.clone())?; let mut store = load_effective_trust_store(&trust_store_path)?; + let mut err = mesh_llm_events::console_err(); if store.remove_trusted_owner(owner_id) { save_trust_store(&trust_store_path, &store)?; - eprintln!( + writeln!( + err, "Removed trusted owner {owner_id} from {}", trust_store_path.display() - ); + )?; } else { - eprintln!("Trusted owner {owner_id} was not present."); + writeln!(err, "Trusted owner {owner_id} was not present.")?; } } TrustCommand::List { trust_store } => { let trust_store_path = resolve_trust_store_path(trust_store.clone())?; let store = load_effective_trust_store(&trust_store_path)?; - eprintln!("Trust store: {}", trust_store_path.display()); - eprintln!("Policy: {:?}", store.policy); - eprintln!(); - eprintln!("Trusted owners:"); + let mut err = mesh_llm_events::console_err(); + writeln!(err, "Trust store: {}", trust_store_path.display())?; + writeln!(err, "Policy: {:?}", store.policy)?; + writeln!(err)?; + writeln!(err, "Trusted owners:")?; if store.trusted_owners.is_empty() { - eprintln!("- none"); + writeln!(err, "- none")?; } else { for owner in &store.trusted_owners { match owner.label.as_deref() { - Some(label) => eprintln!("- {} ({label})", owner.owner_id), - None => eprintln!("- {}", owner.owner_id), + Some(label) => writeln!(err, "- {} ({label})", owner.owner_id)?, + None => writeln!(err, "- {}", owner.owner_id)?, } } } - eprintln!(); - eprintln!("Revoked owners:"); + writeln!(err)?; + writeln!(err, "Revoked owners:")?; if store.revoked_owners.is_empty() { - eprintln!("- none"); + writeln!(err, "- none")?; } else { for owner in &store.revoked_owners { match owner.reason.as_deref() { - Some(reason) => eprintln!("- {} ({reason})", owner.owner_id), - None => eprintln!("- {}", owner.owner_id), + Some(reason) => writeln!(err, "- {} ({reason})", owner.owner_id)?, + None => writeln!(err, "- {}", owner.owner_id)?, } } } - eprintln!(); - eprintln!("Revoked node certs:"); + writeln!(err)?; + writeln!(err, "Revoked node certs:")?; if store.revoked_node_certs.is_empty() { - eprintln!("- none"); + writeln!(err, "- none")?; } else { for cert in &store.revoked_node_certs { match cert.reason.as_deref() { - Some(reason) => eprintln!("- {} ({reason})", cert.cert_id), - None => eprintln!("- {}", cert.cert_id), + Some(reason) => writeln!(err, "- {} ({reason})", cert.cert_id)?, + None => writeln!(err, "- {}", cert.cert_id)?, } } } - eprintln!(); - eprintln!("Revoked node IDs:"); + writeln!(err)?; + writeln!(err, "Revoked node IDs:")?; if store.revoked_node_ids.is_empty() { - eprintln!("- none"); + writeln!(err, "- none")?; } else { for node in &store.revoked_node_ids { match node.reason.as_deref() { - Some(reason) => eprintln!("- {} ({reason})", node.node_endpoint_id), - None => eprintln!("- {}", node.node_endpoint_id), + Some(reason) => writeln!(err, "- {} ({reason})", node.node_endpoint_id)?, + None => writeln!(err, "- {}", node.node_endpoint_id)?, } } } diff --git a/crates/mesh-llm-commands/src/config.rs b/crates/mesh-llm-commands/src/config.rs index 4a7bc9a76b..ee59ae71da 100644 --- a/crates/mesh-llm-commands/src/config.rs +++ b/crates/mesh-llm-commands/src/config.rs @@ -21,7 +21,10 @@ use mesh_llm_plugin_manager::{ InstalledPluginValueKind, InstalledPluginValueSchema, PluginStore, default_store_root, }; use serde::Serialize; -use std::path::{Path, PathBuf}; +use std::{ + io::Write, + path::{Path, PathBuf}, +}; #[derive(Clone, Debug)] struct ConfigFileValidation { @@ -357,7 +360,8 @@ fn handle_validation_result( ) -> Result<()> { let report = ConfigValidateReport::from_diagnostics(path, diagnostics); if json { - println!("{}", serde_json::to_string_pretty(&report)?); + let mut out = mesh_llm_events::machine_out(); + writeln!(out, "{}", serde_json::to_string_pretty(&report)?)?; } else { print_human_report(&report); } @@ -372,22 +376,25 @@ fn handle_validation_result( fn print_validation_load_error(path: Option<&Path>, err: &anyhow::Error, json: bool) -> Result<()> { let report = ConfigValidateReport::from_error(path.map(Path::to_path_buf), err.to_string()); if json { - println!("{}", serde_json::to_string_pretty(&report)?); + let mut out = mesh_llm_events::machine_out(); + writeln!(out, "{}", serde_json::to_string_pretty(&report)?)?; return Ok(()); } let path = report.path.as_deref().unwrap_or(""); - println!("Config invalid: {path}"); - println!(" error: {err}"); + let mut out = mesh_llm_events::console_out(); + writeln!(out, "Config invalid: {path}")?; + writeln!(out, " error: {err}")?; Ok(()) } fn print_human_report(report: &ConfigValidateReport) { let path = report.path.as_deref().unwrap_or(""); + let mut out = mesh_llm_events::console_out(); if report.ok { - println!("Config valid: {path}"); + let _ = writeln!(out, "Config valid: {path}"); } else { - println!("Config invalid: {path}"); + let _ = writeln!(out, "Config invalid: {path}"); } for diagnostic in &report.diagnostics { @@ -401,7 +408,9 @@ fn print_human_diagnostic(diagnostic: &ConfigDiagnosticPayload) { .as_deref() .map(|path| format!(" at {path}")) .unwrap_or_default(); - println!( + let mut out = mesh_llm_events::console_out(); + let _ = writeln!( + out, " {} {:?}{}: {}", severity_label(diagnostic.severity), diagnostic.code, @@ -409,7 +418,7 @@ fn print_human_diagnostic(diagnostic: &ConfigDiagnosticPayload) { diagnostic.message ); if let Some(help) = diagnostic.help.as_deref() { - println!(" help: {help}"); + let _ = writeln!(out, " help: {help}"); } } diff --git a/crates/mesh-llm-commands/src/doctor.rs b/crates/mesh-llm-commands/src/doctor.rs index 5d9827a038..eb6d26a7e7 100644 --- a/crates/mesh-llm-commands/src/doctor.rs +++ b/crates/mesh-llm-commands/src/doctor.rs @@ -3,15 +3,18 @@ use anyhow::{Context, Result}; use serde_json::Value; +use std::io::Write; pub async fn run_network_doctor(port: u16, json_output: bool) -> Result<()> { let report = fetch_network_report(port).await?; if json_output { - println!("{}", serde_json::to_string_pretty(&report)?); + let mut out = mesh_llm_events::machine_out(); + writeln!(out, "{}", serde_json::to_string_pretty(&report)?)?; return Ok(()); } + let mut out = mesh_llm_events::console_out(); for line in network_report_lines(&report) { - println!("{line}"); + writeln!(out, "{line}")?; } Ok(()) } diff --git a/crates/mesh-llm-commands/src/gpus.rs b/crates/mesh-llm-commands/src/gpus.rs index 26514c02e4..dbf6023d35 100644 --- a/crates/mesh-llm-commands/src/gpus.rs +++ b/crates/mesh-llm-commands/src/gpus.rs @@ -7,7 +7,7 @@ use mesh_llm_system::{ vram::VramCapacity, }; use serde_json::{Value, json}; -use std::path::Path; +use std::{io::Write, path::Path}; pub mod tune; @@ -32,7 +32,8 @@ pub fn dispatch_gpu_command( fn run_gpu_backend_benchmark(backend: GpuBenchmarkBackend) -> Result<()> { let outputs = benchmark::run_backend_by_name(map_gpu_backend(backend))?; - println!("{}", serde_json::to_string(&outputs)?); + let mut out = mesh_llm_events::machine_out(); + writeln!(out, "{}", serde_json::to_string(&outputs)?)?; Ok(()) } @@ -54,7 +55,8 @@ pub fn run_gpus(json_output: bool, config_path: Option<&Path>) -> Result<()> { return print_json(gpus_json(&hw, &margin)); } - println!("{}", format_gpus(&hw, &margin)); + let mut out = mesh_llm_events::console_out(); + writeln!(out, "{}", format_gpus(&hw, &margin))?; Ok(()) } @@ -92,11 +94,15 @@ fn advertised_memory(hw: &HardwareSurvey, margin: &SafetyMargin) -> AdvertisedMe fn run_gpu_benchmark(json_output: bool) -> Result<()> { let hw = hardware::survey(); + let mut out = mesh_llm_events::console_out(); if hw.gpus.is_empty() { if json_output { return print_json(gpu_benchmark_empty_json()); } - println!("โš ๏ธ No GPUs detected on this node. Nothing to benchmark."); + writeln!( + out, + "โš ๏ธ No GPUs detected on this node. Nothing to benchmark." + )?; return Ok(()); } @@ -113,13 +119,18 @@ fn run_gpu_benchmark(json_output: bool) -> Result<()> { return print_json(gpu_benchmark_json(&hw, &saved)); } - println!("โœ… Refreshed GPU benchmark fingerprint."); - println!( + writeln!(out, "โœ… Refreshed GPU benchmark fingerprint.")?; + writeln!( + out, " GPUs benchmarked: {}", saved.result.mem_bandwidth_gbps.len() - ); - println!(" Total bandwidth: {}", format_bandwidth(total_bandwidth)); - println!(" Cache path: {}", saved.path.display()); + )?; + writeln!( + out, + " Total bandwidth: {}", + format_bandwidth(total_bandwidth) + )?; + writeln!(out, " Cache path: {}", saved.path.display())?; Ok(()) } @@ -245,7 +256,8 @@ fn gpu_benchmark_json(hw: &HardwareSurvey, saved: &SavedBenchmark) -> Value { } fn print_json(value: Value) -> Result<()> { - println!("{}", serde_json::to_string_pretty(&value)?); + let mut out = mesh_llm_events::machine_out(); + writeln!(out, "{}", serde_json::to_string_pretty(&value)?)?; Ok(()) } diff --git a/crates/mesh-llm-commands/src/gpus/tune/benchmark/mod.rs b/crates/mesh-llm-commands/src/gpus/tune/benchmark/mod.rs index d9b581332f..fe918ff148 100644 --- a/crates/mesh-llm-commands/src/gpus/tune/benchmark/mod.rs +++ b/crates/mesh-llm-commands/src/gpus/tune/benchmark/mod.rs @@ -8,6 +8,8 @@ mod tests; mod trial; mod trial_config; +use std::io::Write; + const MAX_BENCHMARK_TRIALS_PER_TARGET: usize = 512; pub(crate) use candidates::*; @@ -88,12 +90,14 @@ fn run_target_benchmarks( MAX_BENCHMARK_TRIALS_PER_TARGET ); } - eprintln!( + let mut err = mesh_llm_events::console_err(); + writeln!( + err, "benchmark tune: target `{}` running {} trials (throughput tolerance {:.2}%)", prepared.target.requested_input, candidates.len(), request.throughput_tolerance_pct, - ); + )?; let total = candidates.len(); let trials = candidates .into_iter() diff --git a/crates/mesh-llm-commands/src/gpus/tune/benchmark_progress.rs b/crates/mesh-llm-commands/src/gpus/tune/benchmark_progress.rs index d67fa3d231..e965012683 100644 --- a/crates/mesh-llm-commands/src/gpus/tune/benchmark_progress.rs +++ b/crates/mesh-llm-commands/src/gpus/tune/benchmark_progress.rs @@ -1,8 +1,11 @@ use super::*; +use std::io::Write; pub(crate) fn log_target_selection(requested: &str, selection: &BenchmarkSelection) { + let mut err = mesh_llm_events::console_err(); if let Some(best) = &selection.recommended { - eprintln!( + let _ = writeln!( + err, "benchmark tune: target `{requested}` recommended {} decode_tok_s={}", render_benchmark_candidate(&best.candidate), best.decode_tok_s @@ -10,7 +13,10 @@ pub(crate) fn log_target_selection(requested: &str, selection: &BenchmarkSelecti .unwrap_or_else(|| "n/a".to_string()), ); } else { - eprintln!("benchmark tune: target `{requested}` produced no successful trials"); + let _ = writeln!( + err, + "benchmark tune: target `{requested}` produced no successful trials" + ); } } @@ -21,7 +27,9 @@ pub(crate) fn run_trial_with_progress( total: usize, candidate: TuneBenchmarkCandidate, ) -> TuneBenchmarkTrial { - eprintln!( + let mut err = mesh_llm_events::console_err(); + let _ = writeln!( + err, "benchmark tune: trial {}/{} start {}", index + 1, total, @@ -33,33 +41,40 @@ pub(crate) fn run_trial_with_progress( } fn log_trial_result(index: usize, total: usize, trial: &TuneBenchmarkTrial) { + let mut err = mesh_llm_events::console_err(); match trial.status { - TuneBenchmarkTrialStatus::Succeeded => eprintln!( - "benchmark tune: trial {}/{} ok {} decode_tok_s={} ttft_ms={} decode_only_tok_s={}{}", - index + 1, - total, - render_benchmark_candidate(&trial.candidate), - trial - .decode_tok_s - .map(|rate| format!("{rate:.2}")) - .unwrap_or_else(|| "n/a".to_string()), - trial - .ttft_ms - .map(|value| format!("{value:.0}")) - .unwrap_or_else(|| "n/a".to_string()), - trial - .decode_only_tok_s - .map(|rate| format!("{rate:.2}")) - .unwrap_or_else(|| "n/a".to_string()), - render_progress_timing(trial.timings.as_ref()), - ), - TuneBenchmarkTrialStatus::Failed => eprintln!( - "benchmark tune: trial {}/{} failed {} error={}", - index + 1, - total, - render_benchmark_candidate(&trial.candidate), - trial.error.as_deref().unwrap_or("unknown"), - ), + TuneBenchmarkTrialStatus::Succeeded => { + let _ = writeln!( + err, + "benchmark tune: trial {}/{} ok {} decode_tok_s={} ttft_ms={} decode_only_tok_s={}{}", + index + 1, + total, + render_benchmark_candidate(&trial.candidate), + trial + .decode_tok_s + .map(|rate| format!("{rate:.2}")) + .unwrap_or_else(|| "n/a".to_string()), + trial + .ttft_ms + .map(|value| format!("{value:.0}")) + .unwrap_or_else(|| "n/a".to_string()), + trial + .decode_only_tok_s + .map(|rate| format!("{rate:.2}")) + .unwrap_or_else(|| "n/a".to_string()), + render_progress_timing(trial.timings.as_ref()), + ); + } + TuneBenchmarkTrialStatus::Failed => { + let _ = writeln!( + err, + "benchmark tune: trial {}/{} failed {} error={}", + index + 1, + total, + render_benchmark_candidate(&trial.candidate), + trial.error.as_deref().unwrap_or("unknown"), + ); + } } } diff --git a/crates/mesh-llm-commands/src/model_package.rs b/crates/mesh-llm-commands/src/model_package.rs index 8414ac9118..5031c03fda 100644 --- a/crates/mesh-llm-commands/src/model_package.rs +++ b/crates/mesh-llm-commands/src/model_package.rs @@ -1,3 +1,5 @@ +use std::io::Write; + use anyhow::{Context, Result, bail}; use tokio_stream::StreamExt; @@ -113,15 +115,18 @@ pub async fn dispatch_model_package(args: ModelPrepareArgs<'_>) -> Result<()> { None }; + let mut err = mesh_llm_events::console_err(); + let mut machine = mesh_llm_events::machine_out(); + // Resolve permissions. - eprintln!("๐Ÿ”‘ Checking permissions..."); + writeln!(err, "๐Ÿ”‘ Checking permissions...")?; let perms = permissions::check_permissions(&hf_client).await?; // Parse timeout. let timeout_seconds = parse_timeout(timeout)?; // Resolve source, target, and build job spec. - eprintln!("๐Ÿ” Resolving source..."); + writeln!(err, "๐Ÿ” Resolving source...")?; let params = PrepareParams { source_repo: source_repo.to_string(), source_revision: source_model_ref.revision.clone(), @@ -144,7 +149,8 @@ pub async fn dispatch_model_package(args: ModelPrepareArgs<'_>) -> Result<()> { if !submitting { let redacted = redacted_spec(&job.spec); if json { - println!( + writeln!( + machine, "{}", serde_json::to_string_pretty(&json!({ "dryRun": true, @@ -159,11 +165,14 @@ pub async fn dispatch_model_package(args: ModelPrepareArgs<'_>) -> Result<()> { "jobPlan": job.job_plan, "spec": redacted, }))? - ); + )?; } else { - eprintln!(); - eprintln!("๐Ÿ” Dry run โ€” no HF Job was submitted. Add --confirm to submit."); - println!("{}", serde_json::to_string_pretty(&redacted)?); + writeln!(err)?; + writeln!( + err, + "๐Ÿ” Dry run โ€” no HF Job was submitted. Add --confirm to submit." + )?; + writeln!(machine, "{}", serde_json::to_string_pretty(&redacted)?)?; } return Ok(()); } @@ -171,7 +180,7 @@ pub async fn dispatch_model_package(args: ModelPrepareArgs<'_>) -> Result<()> { ensure_bucket_script_current(&hf_client).await?; // Submit. - eprintln!(); + writeln!(err)?; let jobs_client = jobs_client.as_ref().expect("jobs client initialized"); let info = jobs_client.submit(&job.namespace, &job.spec).await?; let job_url = format!( @@ -180,13 +189,22 @@ pub async fn dispatch_model_package(args: ModelPrepareArgs<'_>) -> Result<()> { job.namespace, info.id ); - eprintln!("๐Ÿš€ Submitted: {}", info.id); - eprintln!(" Console: {job_url}"); - eprintln!(" Status: mesh-llm models package --status {}", info.id); - eprintln!(" Logs: mesh-llm models package --logs {}", info.id); + writeln!(err, "๐Ÿš€ Submitted: {}", info.id)?; + writeln!(err, " Console: {job_url}")?; + writeln!( + err, + " Status: mesh-llm models package --status {}", + info.id + )?; + writeln!( + err, + " Logs: mesh-llm models package --logs {}", + info.id + )?; if json { - println!( + writeln!( + machine, "{}", serde_json::to_string_pretty(&json!({ "submitted": true, @@ -202,14 +220,14 @@ pub async fn dispatch_model_package(args: ModelPrepareArgs<'_>) -> Result<()> { "experimental": job.experimental, "jobPlan": job.job_plan, }))? - ); + )?; } // Follow logs if requested. if follow { - eprintln!(); - eprintln!("๐Ÿ“œ Following logs..."); - eprintln!(); + writeln!(err)?; + writeln!(err, "๐Ÿ“œ Following logs...")?; + writeln!(err)?; follow_until_done(jobs_client, &job.namespace, &info.id).await?; } @@ -217,6 +235,7 @@ pub async fn dispatch_model_package(args: ModelPrepareArgs<'_>) -> Result<()> { } fn print_prepare_job(job: &PrepareJob, perms: &permissions::PermissionCheck) { + let mut err = mesh_llm_events::console_err(); let shard_info = model_ref::split_gguf_shard_info(&job.source_file); let shard_str = if let Some(shard) = shard_info { format!(" ({} shards)", shard.total) @@ -224,14 +243,15 @@ fn print_prepare_job(job: &PrepareJob, perms: &permissions::PermissionCheck) { String::new() }; - eprintln!(" Repo: {}", job.source_repo); - eprintln!(" Commit: {}", job.source_revision); - eprintln!(" File: {}{}", job.source_file, shard_str); + let _ = writeln!(err, " Repo: {}", job.source_repo); + let _ = writeln!(err, " Commit: {}", job.source_revision); + let _ = writeln!(err, " File: {}{}", job.source_file, shard_str); for projector in &job.projectors { - eprintln!(" MMProj: {}", projector.path); + let _ = writeln!(err, " MMProj: {}", projector.path); } - eprintln!(); - eprintln!( + let _ = writeln!(err); + let _ = writeln!( + err, "๐Ÿ”‘ Permissions: {} ({})", perms.username, if perms.is_meshllm_member { @@ -240,8 +260,9 @@ fn print_prepare_job(job: &PrepareJob, perms: &permissions::PermissionCheck) { "not in meshllm org" } ); - eprintln!(" Target: {}", job.target_repo); - eprintln!( + let _ = writeln!(err, " Target: {}", job.target_repo); + let _ = writeln!( + err, " Release: {}", if job.experimental { "experimental (public, not cataloged until HF PR merge)" @@ -249,7 +270,8 @@ fn print_prepare_job(job: &PrepareJob, perms: &permissions::PermissionCheck) { "stable" } ); - eprintln!( + let _ = writeln!( + err, " Catalog: meshllm/catalog ({})", if job.catalog_create_pr { "will open PR" @@ -257,8 +279,9 @@ fn print_prepare_job(job: &PrepareJob, perms: &permissions::PermissionCheck) { "direct commit" } ); - eprintln!(); - eprintln!( + let _ = writeln!(err); + let _ = writeln!( + err, "๐Ÿ“‹ Job: {}, timeout {}, mesh-llm@{}", job.spec.flavor, format_timeout(job.spec.timeout_seconds), @@ -268,13 +291,15 @@ fn print_prepare_job(job: &PrepareJob, perms: &permissions::PermissionCheck) { .map(|s| s.as_str()) .unwrap_or("main") ); - eprintln!( + let _ = writeln!( + err, " Hardware: {} {} ({})", job.job_plan.pretty_name, hardware_label(job.job_plan.cpu.as_deref(), job.job_plan.ram.as_deref()), job.job_plan.selection_reason ); - eprintln!( + let _ = writeln!( + err, " Pricing: ${:.6}/{}, max {}", job.job_plan.unit_cost_usd, job.job_plan.unit_label, @@ -288,11 +313,14 @@ async fn run_list_quants( source_revision: Option<&str>, json_output: bool, ) -> Result<()> { + let mut err = mesh_llm_events::console_err(); + let mut machine = mesh_llm_events::machine_out(); let inventory = prepare::list_inventory(client, source_repo, source_revision).await?; let quants = inventory.quants; if json_output { - println!( + writeln!( + machine, "{}", serde_json::to_string_pretty(&json!({ "sourceRepo": source_repo, @@ -300,24 +328,25 @@ async fn run_list_quants( "quants": quants, "projectors": inventory.projectors, }))? - ); + )?; return Ok(()); } if quants.is_empty() { - eprintln!("No GGUF files found in {source_repo}"); + writeln!(err, "No GGUF files found in {source_repo}")?; return Ok(()); } - eprintln!("๐Ÿ“ฆ Available quants in {source_repo}:"); - eprintln!(); + writeln!(err, "๐Ÿ“ฆ Available quants in {source_repo}:")?; + writeln!(err)?; print_quant_table(&quants); - eprintln!(); - eprintln!("Specify one as a model ref, e.g.:"); - eprintln!( + writeln!(err)?; + writeln!(err, "Specify one as a model ref, e.g.:")?; + writeln!( + err, " mesh-llm models package {}", source_quant_ref(source_repo, source_revision, &quants[0].name) - ); + )?; Ok(()) } @@ -330,6 +359,7 @@ fn source_quant_ref(source_repo: &str, source_revision: Option<&str>, quant: &st } fn print_quant_table(quants: &[DiscoveredQuant]) { + let mut err = mesh_llm_events::console_err(); // Find the longest name for alignment. let max_name = quants.iter().map(|q| q.name.len()).max().unwrap_or(0); @@ -339,7 +369,8 @@ fn print_quant_table(quants: &[DiscoveredQuant]) { } else { format!("{} shards", q.shard_count) }; - eprintln!( + let _ = writeln!( + err, " {:9}, {}", q.name, shard_str, @@ -350,7 +381,11 @@ fn print_quant_table(quants: &[DiscoveredQuant]) { } async fn run_update_script() -> Result<()> { - eprintln!("๐Ÿ“ค Uploading embedded script to meshllm/layer-split-output bucket..."); + let mut err = mesh_llm_events::console_err(); + writeln!( + err, + "๐Ÿ“ค Uploading embedded script to meshllm/layer-split-output bucket..." + )?; let client = ::model_package::build_hf_client()?; // Check permissions first. @@ -364,33 +399,37 @@ async fn run_update_script() -> Result<()> { } script::update_bucket_script(&client).await?; - eprintln!( + writeln!( + err, "โœ… Bucket script updated ({} bytes)", script::EMBEDDED_SCRIPT_SIZE - ); + )?; Ok(()) } async fn run_status(client: &HfJobsClient, job_id: &str, json_output: bool) -> Result<()> { + let mut err = mesh_llm_events::console_err(); + let mut machine = mesh_llm_events::machine_out(); let (namespace, id) = parse_job_id(job_id).await?; let info = client.inspect(&namespace, &id).await?; if json_output { - println!( + writeln!( + machine, "{}", serde_json::to_string_pretty(&json!({ "namespace": namespace, "job": info, }))? - ); + )?; return Ok(()); } - eprintln!("Job: {}", info.id); - eprintln!("Status: {}", info.status.stage); + writeln!(err, "Job: {}", info.id)?; + writeln!(err, "Status: {}", info.status.stage)?; if let Some(msg) = &info.status.message { - eprintln!("Message: {msg}"); + writeln!(err, "Message: {msg}")?; } if let Some(created) = &info.created_at { - eprintln!("Created: {created}"); + writeln!(err, "Created: {created}")?; } Ok(()) } @@ -398,24 +437,38 @@ async fn run_status(client: &HfJobsClient, job_id: &str, json_output: bool) -> R async fn run_logs(client: &HfJobsClient, job_id: &str, json_output: bool) -> Result<()> { use ::model_package::jobs::JobStage; + let mut out = mesh_llm_events::console_out(); + let mut err = mesh_llm_events::console_err(); + let mut machine = mesh_llm_events::machine_out(); + let (namespace, id) = parse_job_id(job_id).await?; let info = client.inspect(&namespace, &id).await?; if matches!(info.status.stage, JobStage::Running) && !json_output { - eprintln!("Job is still running; draining currently buffered logs only."); - eprintln!("Use --follow when submitting to stream until completion."); - eprintln!(); + writeln!( + err, + "Job is still running; draining currently buffered logs only." + )?; + writeln!( + err, + "Use --follow when submitting to stream until completion." + )?; + writeln!(err)?; } let mut stream = std::pin::pin!(client.stream_logs(&namespace, &id).await?); loop { match tokio::time::timeout(std::time::Duration::from_secs(5), stream.next()).await { Ok(Some(Ok(text))) if json_output => { - println!("{}", serde_json::to_string(&json!({ "data": text }))?); + writeln!( + machine, + "{}", + serde_json::to_string(&json!({ "data": text }))? + )?; } - Ok(Some(Ok(text))) => println!("{text}"), + Ok(Some(Ok(text))) => writeln!(out, "{text}")?, Ok(Some(Err(e))) => { - eprintln!("Log stream error: {e}"); + writeln!(err, "Log stream error: {e}")?; break; } Ok(None) => break, @@ -426,49 +479,55 @@ async fn run_logs(client: &HfJobsClient, job_id: &str, json_output: bool) -> Res } async fn run_cancel(client: &HfJobsClient, job_id: &str, json_output: bool) -> Result<()> { + let mut err = mesh_llm_events::console_err(); + let mut machine = mesh_llm_events::machine_out(); let (namespace, id) = parse_job_id(job_id).await?; client.cancel(&namespace, &id).await?; if json_output { - println!( + writeln!( + machine, "{}", serde_json::to_string_pretty(&json!({ "namespace": namespace, "jobId": id, "canceled": true, }))? - ); + )?; } else { - eprintln!("โœ… Job {id} canceled"); + writeln!(err, "โœ… Job {id} canceled")?; } Ok(()) } async fn run_list(client: &HfJobsClient, json_output: bool) -> Result<()> { + let mut err = mesh_llm_events::console_err(); + let mut machine = mesh_llm_events::machine_out(); // We need to know the namespace โ€” resolve via whoami. let hf_client = ::model_package::build_hf_client()?; let perms = permissions::check_permissions(&hf_client).await?; let jobs = client.list(&perms.namespace).await?; if json_output { - println!( + writeln!( + machine, "{}", serde_json::to_string_pretty(&json!({ "namespace": perms.namespace, "jobs": jobs, }))? - ); + )?; return Ok(()); } if jobs.is_empty() { - eprintln!("No jobs found in namespace '{}'", perms.namespace); + writeln!(err, "No jobs found in namespace '{}'", perms.namespace)?; return Ok(()); } - eprintln!("Recent jobs in '{}':", perms.namespace); - eprintln!(); + writeln!(err, "Recent jobs in '{}':", perms.namespace)?; + writeln!(err)?; for job in &jobs { let created = job.created_at.as_deref().unwrap_or("?"); - eprintln!(" {} {} {}", job.id, job.status.stage, created); + writeln!(err, " {} {} {}", job.id, job.status.stage, created)?; } Ok(()) } @@ -477,18 +536,21 @@ async fn run_list(client: &HfJobsClient, json_output: bool) -> Result<()> { async fn follow_until_done(client: &HfJobsClient, namespace: &str, job_id: &str) -> Result<()> { use ::model_package::jobs::JobStage; + let mut out = mesh_llm_events::console_out(); + let mut err = mesh_llm_events::console_err(); + loop { loop { let info = client.inspect(namespace, job_id).await?; match info.status.stage { JobStage::Running => break, JobStage::Completed => { - eprintln!("Job {} finished: {}", job_id, info.status.stage); + writeln!(err, "Job {} finished: {}", job_id, info.status.stage)?; return Ok(()); } JobStage::Error | JobStage::Canceled | JobStage::Deleted => { if let Some(msg) = &info.status.message { - eprintln!("Message: {msg}"); + writeln!(err, "Message: {msg}")?; } anyhow::bail!( "Job {} finished unsuccessfully: {}", @@ -503,9 +565,9 @@ async fn follow_until_done(client: &HfJobsClient, namespace: &str, job_id: &str) let mut stream = std::pin::pin!(client.stream_logs(namespace, job_id).await?); while let Some(line) = stream.next().await { match line { - Ok(text) => println!("{text}"), + Ok(text) => writeln!(out, "{text}")?, Err(e) => { - eprintln!("Log stream error: {e}"); + writeln!(err, "Log stream error: {e}")?; break; } } @@ -514,13 +576,13 @@ async fn follow_until_done(client: &HfJobsClient, namespace: &str, job_id: &str) let info = client.inspect(namespace, job_id).await?; match info.status.stage { JobStage::Completed => { - eprintln!(); - eprintln!("Job {} finished: {}", job_id, info.status.stage); + writeln!(err)?; + writeln!(err, "Job {} finished: {}", job_id, info.status.stage)?; return Ok(()); } JobStage::Error | JobStage::Canceled | JobStage::Deleted => { if let Some(msg) = &info.status.message { - eprintln!("Message: {msg}"); + writeln!(err, "Message: {msg}")?; } anyhow::bail!( "Job {} finished unsuccessfully: {}", @@ -529,10 +591,11 @@ async fn follow_until_done(client: &HfJobsClient, namespace: &str, job_id: &str) ); } _ => { - eprintln!( + writeln!( + err, "Log stream ended while job is still {}; reconnecting...", info.status.stage - ); + )?; tokio::time::sleep(std::time::Duration::from_secs(3)).await; } } @@ -540,26 +603,29 @@ async fn follow_until_done(client: &HfJobsClient, namespace: &str, job_id: &str) } async fn ensure_bucket_script_current(client: &hf_hub::HFClient) -> Result<()> { + let mut stderr = mesh_llm_events::console_err(); match script::check_bucket_script(client).await { Ok(freshness) if freshness.is_current => Ok(()), Ok(freshness) => { - eprintln!( + writeln!( + stderr, "Bucket script is out of date ({}); updating it now...", freshness .mismatch_reason .as_deref() .unwrap_or("embedded script differs from bucket script") - ); + )?; script::update_bucket_script(client).await?; - eprintln!("Bucket script updated."); + writeln!(stderr, "Bucket script updated.")?; Ok(()) } Err(err) => { - eprintln!( + writeln!( + stderr, "Could not check bucket script freshness ({err:#}); uploading current script..." - ); + )?; script::update_bucket_script(client).await?; - eprintln!("Bucket script updated."); + writeln!(stderr, "Bucket script updated.")?; Ok(()) } } diff --git a/crates/mesh-llm-commands/src/plugin.rs b/crates/mesh-llm-commands/src/plugin.rs index fe3d9ba1b4..2979370b39 100644 --- a/crates/mesh-llm-commands/src/plugin.rs +++ b/crates/mesh-llm-commands/src/plugin.rs @@ -86,10 +86,12 @@ async fn install( }; progress.finish(); if outcome.changed { - eprintln!( + let mut err = mesh_llm_events::console_err(); + writeln!( + err, "โœ… Installed {} {}", outcome.metadata.name, outcome.metadata.installed_version - ); + )?; } Ok(()) } @@ -100,10 +102,12 @@ async fn update(name: &str) -> Result<()> { let outcome = update_plugin(name, &options, &mut progress).await?; progress.finish(); if outcome.changed { - eprintln!( + let mut err = mesh_llm_events::console_err(); + writeln!( + err, "โœ… Updated {} to {}", outcome.metadata.name, outcome.metadata.installed_version - ); + )?; } Ok(()) } @@ -111,10 +115,11 @@ async fn update(name: &str) -> Result<()> { fn set_enabled(name: &str, enabled: bool) -> Result<()> { let store = PluginStore::new(default_store_root()?); let metadata = store.set_enabled(name, enabled)?; + let mut err = mesh_llm_events::console_err(); if metadata.enabled { - eprintln!("โœ… Enabled {}", metadata.name); + writeln!(err, "โœ… Enabled {}", metadata.name)?; } else { - eprintln!("โธ๏ธ Disabled {}", metadata.name); + writeln!(err, "โธ๏ธ Disabled {}", metadata.name)?; } Ok(()) } @@ -122,28 +127,30 @@ fn set_enabled(name: &str, enabled: bool) -> Result<()> { fn delete(name: &str) -> Result<()> { let store = PluginStore::new(default_store_root()?); store.delete(name)?; - eprintln!("๐Ÿ—‘๏ธ Deleted {name}"); + let mut err = mesh_llm_events::console_err(); + writeln!(err, "๐Ÿ—‘๏ธ Deleted {name}")?; Ok(()) } fn info(name: &str, runtime_rows: Option<&PluginListRows>) -> Result { let store = PluginStore::new(default_store_root()?); + let mut out = mesh_llm_events::console_out(); if let Some(metadata) = store.load_optional(name)? { - println!("name\t{}", metadata.name); - println!("version\t{}", metadata.installed_version); - println!("enabled\t{}", metadata.enabled); - println!("source\t{}", metadata.source_repository); - println!("target\t{}", metadata.target_triple); - println!("asset\t{}", metadata.downloaded_asset_name); - println!("path\t{}", metadata.install_path.display()); + writeln!(out, "name\t{}", metadata.name)?; + writeln!(out, "version\t{}", metadata.installed_version)?; + writeln!(out, "enabled\t{}", metadata.enabled)?; + writeln!(out, "source\t{}", metadata.source_repository)?; + writeln!(out, "target\t{}", metadata.target_triple)?; + writeln!(out, "asset\t{}", metadata.downloaded_asset_name)?; + writeln!(out, "path\t{}", metadata.install_path.display())?; if let Some(protocol) = metadata.last_protocol_version { - println!("protocol\t{protocol}"); + writeln!(out, "protocol\t{protocol}")?; } if let Some(status) = metadata.last_status { - println!("status\t{status}"); + writeln!(out, "status\t{status}")?; } if let Some(error) = metadata.last_error { - println!("error\t{error}"); + writeln!(out, "error\t{error}")?; } return Ok(true); } @@ -152,13 +159,13 @@ fn info(name: &str, runtime_rows: Option<&PluginListRows>) -> Result { }; if let Some(row) = runtime_rows.externals.iter().find(|row| row.name == name) { for line in runtime_plugin_info_lines(row) { - println!("{line}"); + writeln!(out, "{line}")?; } return Ok(true); } if let Some(row) = runtime_rows.inactive.iter().find(|row| row.name == name) { for line in inactive_plugin_info_lines(row) { - println!("{line}"); + writeln!(out, "{line}")?; } return Ok(true); } @@ -192,48 +199,55 @@ async fn search(query: Option<&str>) -> Result<()> { let catalog = catalog?; let hits = catalog.search(query); if hits.is_empty() { - eprintln!("๐Ÿ”Ž No plugins found"); + let mut err = mesh_llm_events::console_err(); + writeln!(err, "๐Ÿ”Ž No plugins found")?; return Ok(()); } + let mut out = mesh_llm_events::console_out(); for entry in hits { - println!( + writeln!( + out, "{}\t{}\t{}\t{} <{}>", entry.name, entry.description, entry.github_url, entry.author_name, entry.author_email - ); + )?; } Ok(()) } fn list(runtime_rows: &PluginListRows) -> Result<()> { let store = PluginStore::new(default_store_root()?); + let mut out = mesh_llm_events::console_out(); for metadata in store.list()? { let state = if metadata.enabled { "enabled" } else { "disabled" }; - println!( + writeln!( + out, "{}\tversion={}\tstate={}\tsource={}", metadata.name, metadata.installed_version, state, metadata.source_repository - ); + )?; } for spec in &runtime_rows.externals { - println!( + writeln!( + out, "{}\tkind=runtime\tcommand={}\targs={}", spec.name, spec.command, spec.args.join(" ") - ); + )?; } for summary in &runtime_rows.inactive { - println!( + writeln!( + out, "{}\tkind={}\tstate={}\terror={}", summary.name, summary.kind, summary.status, summary.error.clone().unwrap_or_default() - ); + )?; } Ok(()) } @@ -264,9 +278,10 @@ impl CliPluginProgress { self.finish(); self.active_download = Some(asset.clone()); self.last_percent = None; - eprintln!("โฌ‡๏ธ Downloading {asset}"); + let mut err = mesh_llm_events::console_err(); + let _ = writeln!(err, "โฌ‡๏ธ Downloading {asset}"); if let Some(total) = total_bytes { - eprintln!(" size: {}", format_bytes(total)); + let _ = writeln!(err, " size: {}", format_bytes(total)); } } @@ -290,8 +305,9 @@ impl CliPluginProgress { percent ), ); - eprint!("\r\x1b[2K{gauge}"); - let _ = std::io::stderr().flush(); + let mut err = mesh_llm_events::console_err(); + let _ = write!(err, "\r\x1b[2K{gauge}"); + let _ = err.flush(); } } } @@ -317,22 +333,26 @@ impl PluginProgressReporter for CliPluginProgress { } => self.download_progress(downloaded_bytes, total_bytes), PluginProgressEvent::DownloadFinished { asset } => { self.finish(); - eprintln!("โœ… Downloaded {asset}"); + let mut err = mesh_llm_events::console_err(); + let _ = writeln!(err, "โœ… Downloaded {asset}"); } PluginProgressEvent::Extracting { asset } => { self.spinner(format!("Installing {asset}")); } PluginProgressEvent::Installed { name, version } => { self.finish(); - eprintln!("๐Ÿ“ฆ Installed {name} {version}"); + let mut err = mesh_llm_events::console_err(); + let _ = writeln!(err, "๐Ÿ“ฆ Installed {name} {version}"); } PluginProgressEvent::Updated { name, from, to } => { self.finish(); - eprintln!("โฌ†๏ธ Updated {name} {from} -> {to}"); + let mut err = mesh_llm_events::console_err(); + let _ = writeln!(err, "โฌ†๏ธ Updated {name} {from} -> {to}"); } PluginProgressEvent::AlreadyCurrent { name, version } => { self.finish(); - eprintln!("โœ… {name} is up to date ({version})"); + let mut err = mesh_llm_events::console_err(); + let _ = writeln!(err, "โœ… {name} is up to date ({version})"); } } } diff --git a/crates/mesh-llm-commands/src/runtime_native.rs b/crates/mesh-llm-commands/src/runtime_native.rs index 3d8820b847..6fe6431e70 100644 --- a/crates/mesh-llm-commands/src/runtime_native.rs +++ b/crates/mesh-llm-commands/src/runtime_native.rs @@ -16,6 +16,7 @@ use mesh_llm_system::backend::BinaryFlavor; use mesh_llm_tui::terminal_progress::{ ratio_complete_u64, render_inline_gauge_with_reserved_width, }; +use std::io::Write; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; @@ -66,7 +67,8 @@ pub async fn run_native_runtime_list( let discovered_bundle_dirs = discover_native_runtime_bundle_dirs(bundle_dirs)?; print_configured_selector(configured, json_output); if !json_output && manifest_path.is_none() { - eprintln!("๐Ÿ”Ž Loading native runtime release manifest"); + let mut err = mesh_llm_events::console_err(); + writeln!(err, "๐Ÿ”Ž Loading native runtime release manifest")?; } let (manifest, sources) = load_release_manifest_with_sources(NativeRuntimeManifestOptions { @@ -164,11 +166,12 @@ pub async fn run_native_runtime_install( json_output: bool, ) -> Result<()> { let resolved_selection = resolve_runtime_selection(requested_runtime, configured)?; + let mut err = mesh_llm_events::console_err(); if !json_output && manifest_path.is_none() { - eprintln!("๐Ÿ”Ž Loading native runtime release manifest"); + writeln!(err, "๐Ÿ”Ž Loading native runtime release manifest")?; } if !json_output { - eprintln!("๐Ÿ”Ž Detecting host runtime profile"); + writeln!(err, "๐Ÿ”Ž Detecting host runtime profile")?; } print_configured_selector( NativeRuntimeConfigSelection { @@ -213,13 +216,14 @@ fn print_configured_selector(configured: NativeRuntimeConfigSelection<'_>, json_ return; } let mesh_version = configured.mesh_version_or_current(); - eprintln!("๐Ÿ”’ Using native runtime selector"); - eprintln!(" mesh version: {mesh_version}"); + let mut err = mesh_llm_events::console_err(); + let _ = writeln!(err, "๐Ÿ”’ Using native runtime selector"); + let _ = writeln!(err, " mesh version: {mesh_version}"); if let Some(skippy_abi_version) = configured.skippy_abi_version { - eprintln!(" Skippy ABI: {skippy_abi_version}"); + let _ = writeln!(err, " Skippy ABI: {skippy_abi_version}"); } if let Some(configured_selection) = configured.selection { - eprintln!(" selection: {configured_selection}"); + let _ = writeln!(err, " selection: {configured_selection}"); } } @@ -245,9 +249,10 @@ impl DownloadProgress { total: Option, finished: bool, ) { + let mut err = mesh_llm_events::console_err(); if self.native_runtime_id.is_none() { self.native_runtime_id = Some(native_runtime_id.to_string()); - eprintln!("โฌ‡๏ธ Downloading native runtime {native_runtime_id}"); + let _ = writeln!(err, "โฌ‡๏ธ Downloading native runtime {native_runtime_id}"); } if finished { self.finish(downloaded); @@ -283,19 +288,20 @@ impl DownloadProgress { ), 3, ); - eprint!("\r\x1b[2K {gauge}"); - let _ = std::io::Write::flush(&mut std::io::stderr()); + let _ = write!(err, "\r\x1b[2K {gauge}"); + let _ = err.flush(); } _ => { - eprint!("\r\x1b[2K downloaded {}", human_bytes(downloaded)); - let _ = std::io::Write::flush(&mut std::io::stderr()); + let _ = write!(err, "\r\x1b[2K downloaded {}", human_bytes(downloaded)); + let _ = err.flush(); } } } } fn finish(&mut self, downloaded: u64) { - eprintln!("\r\x1b[2K downloaded {}", human_bytes(downloaded)); + let mut err = mesh_llm_events::console_err(); + let _ = writeln!(err, "\r\x1b[2K downloaded {}", human_bytes(downloaded)); } } diff --git a/crates/mesh-llm-commands/src/runtime_native/formatters.rs b/crates/mesh-llm-commands/src/runtime_native/formatters.rs index 947cc5e9cc..325d68f25e 100644 --- a/crates/mesh-llm-commands/src/runtime_native/formatters.rs +++ b/crates/mesh-llm-commands/src/runtime_native/formatters.rs @@ -8,6 +8,7 @@ use mesh_llm_runtime_install::{ }; use serde::Serialize; use serde_json::json; +use std::io::Write; use std::path::{Path, PathBuf}; #[derive(Clone, Debug, Serialize)] @@ -91,9 +92,10 @@ impl RuntimeNativeFormatter for HumanFormatter { rows: &[AvailableRuntimeRow], sources: &NativeRuntimeCatalogSources, ) -> Result<()> { - eprintln!("๐Ÿ”Ž Catalogs consulted"); + let mut err = mesh_llm_events::console_err(); + writeln!(err, "๐Ÿ”Ž Catalogs consulted")?; for line in sources.describe() { - eprintln!(" {line}"); + writeln!(err, " {line}")?; } print_available_human(rows); Ok(()) @@ -114,21 +116,22 @@ impl RuntimeNativeFormatter for HumanFormatter { } fn render_install_error(&self, error: &Error) -> Result<()> { - eprintln!("โŒ Native runtime install failed"); - eprintln!(" Reason: {error}"); + let mut err = mesh_llm_events::console_err(); + writeln!(err, "โŒ Native runtime install failed")?; + writeln!(err, " Reason: {error}")?; // A resolution failure carries its explanation (catalogs consulted, // rejected candidates) as structure; the causes underneath, such as // the resolver's own verdict or a manifest that failed to parse, stay // one per line. if let Some(resolution) = error.downcast_ref::() { for line in resolution.explanation_lines() { - eprintln!(" {line}"); + writeln!(err, " {line}")?; } } for cause in error.chain().skip(1) { - eprintln!(" cause: {cause}"); + writeln!(err, " cause: {cause}")?; } - eprintln!(" Try: mesh-llm runtime list --available"); + writeln!(err, " Try: mesh-llm runtime list --available")?; Ok(()) } @@ -138,26 +141,33 @@ impl RuntimeNativeFormatter for HumanFormatter { mesh_version: &str, removed: bool, ) -> Result<()> { + let mut err = mesh_llm_events::console_err(); if removed { - eprintln!("โœ… Removed native runtime {native_runtime_id} for MeshLLM {mesh_version}"); + writeln!( + err, + "โœ… Removed native runtime {native_runtime_id} for MeshLLM {mesh_version}" + )?; } else { - eprintln!( + writeln!( + err, "๐Ÿ”Ž Native runtime {native_runtime_id} for MeshLLM {mesh_version} was not installed" - ); + )?; } Ok(()) } fn render_prune(&self, plan: &CachePrunePlan) -> Result<()> { + let mut err = mesh_llm_events::console_err(); if plan.remove_dirs.is_empty() { - eprintln!("โœ… Native runtime cache already pruned"); + writeln!(err, "โœ… Native runtime cache already pruned")?; } else { - eprintln!( + writeln!( + err, "โœ… Pruned {} native runtime cache version(s)", plan.remove_dirs.len() - ); + )?; for dir in &plan.remove_dirs { - eprintln!(" removed: {}", dir.display()); + writeln!(err, " removed: {}", dir.display())?; } } Ok(()) @@ -236,7 +246,8 @@ impl RuntimeNativeFormatter for JsonFormatter { } fn print_json(value: &(impl Serialize + ?Sized)) -> Result<()> { - println!("{}", serde_json::to_string_pretty(value)?); + let mut out = mesh_llm_events::machine_out(); + writeln!(out, "{}", serde_json::to_string_pretty(value)?)?; Ok(()) } @@ -248,12 +259,16 @@ fn install_status_label(status: NativeRuntimeInstallStatus) -> &'static str { } fn print_available_human(rows: &[AvailableRuntimeRow]) { + let mut out = mesh_llm_events::console_out(); if rows.is_empty() { - println!("๐Ÿ“ฆ No native runtime manifest entries found"); - println!(" Pass --manifest or --bundle-dir to inspect available runtimes."); + let _ = writeln!(out, "๐Ÿ“ฆ No native runtime manifest entries found"); + let _ = writeln!( + out, + " Pass --manifest or --bundle-dir to inspect available runtimes." + ); return; } - println!("๐Ÿ“ฆ Available native runtimes"); + let _ = writeln!(out, "๐Ÿ“ฆ Available native runtimes"); for row in rows { let marker = if row.supported { "โœ…" } else { "โš ๏ธ" }; let status = if row.supported { @@ -261,90 +276,112 @@ fn print_available_human(rows: &[AvailableRuntimeRow]) { } else { "not compatible" }; - println!( + let _ = writeln!( + out, " - {marker} {} {status} ({}, {}/{})", row.id, row.backend, row.os, row.arch ); if let Some(mesh_version) = row.mesh_version.as_deref() { - println!( + let _ = writeln!( + out, " MeshLLM: {mesh_version}; Skippy ABI: {}", row.skippy_abi ); } else { - println!(" MeshLLM: unspecified; Skippy ABI: {}", row.skippy_abi); + let _ = writeln!( + out, + " MeshLLM: unspecified; Skippy ABI: {}", + row.skippy_abi + ); } for reason in &row.rejection_reasons { - println!(" reason: {}", format_rejection(reason)); + let _ = writeln!(out, " reason: {}", format_rejection(reason)); } } } fn print_installed_human(installed: &[InstalledNativeRuntime], cache_root: &Path) { + let mut out = mesh_llm_events::console_out(); if installed.is_empty() { - println!("๐Ÿ“ฆ No local native runtimes found"); - println!(" cache: {}", cache_root.display()); + let _ = writeln!(out, "๐Ÿ“ฆ No local native runtimes found"); + let _ = writeln!(out, " cache: {}", cache_root.display()); return; } - println!("๐Ÿ“ฆ Local native runtimes"); - println!(" cache: {}", cache_root.display()); + let _ = writeln!(out, "๐Ÿ“ฆ Local native runtimes"); + let _ = writeln!(out, " cache: {}", cache_root.display()); for runtime in installed { - println!( + let _ = writeln!( + out, " - โœ… {} {} ({})", runtime.native_runtime_id, runtime.mesh_version, runtime.flavor ); - println!(" path: {}", runtime.path.display()); + let _ = writeln!(out, " path: {}", runtime.path.display()); } } fn print_install_human(outcome: &NativeRuntimeInstallOutcome) { + let mut err = mesh_llm_events::console_err(); match outcome.status { NativeRuntimeInstallStatus::AlreadyInstalled => { - eprintln!( + let _ = writeln!( + err, "โœ… Native runtime already installed: {}", outcome.runtime.native_runtime_id ); - eprintln!(" version: {}", outcome.runtime.mesh_version); - eprintln!(" flavor: {}", outcome.runtime.flavor); - eprintln!(" path: {}", outcome.runtime.path.display()); + let _ = writeln!(err, " version: {}", outcome.runtime.mesh_version); + let _ = writeln!(err, " flavor: {}", outcome.runtime.flavor); + let _ = writeln!(err, " path: {}", outcome.runtime.path.display()); } NativeRuntimeInstallStatus::Installed => { - eprintln!("โœ… Installed {}", outcome.runtime.native_runtime_id); - eprintln!(" version: {}", outcome.runtime.mesh_version); - eprintln!(" flavor: {}", outcome.runtime.flavor); - eprintln!(" path: {}", outcome.runtime.path.display()); + let _ = writeln!(err, "โœ… Installed {}", outcome.runtime.native_runtime_id); + let _ = writeln!(err, " version: {}", outcome.runtime.mesh_version); + let _ = writeln!(err, " flavor: {}", outcome.runtime.flavor); + let _ = writeln!(err, " path: {}", outcome.runtime.path.display()); } } for line in outcome.sources.describe() { - eprintln!(" catalog: {line}"); + let _ = writeln!(err, " catalog: {line}"); } } fn print_doctor_human(report: &NativeRuntimeDoctorReport) { - println!("๐Ÿฉบ MeshLLM doctor"); - println!(); - println!("Native runtime:"); - println!(" status: {}", report.status); - println!(" running MeshLLM version: {}", report.running_mesh_version); - println!( + let mut out = mesh_llm_events::console_out(); + let _ = writeln!(out, "๐Ÿฉบ MeshLLM doctor"); + let _ = writeln!(out); + let _ = writeln!(out, "Native runtime:"); + let _ = writeln!(out, " status: {}", report.status); + let _ = writeln!( + out, + " running MeshLLM version: {}", + report.running_mesh_version + ); + let _ = writeln!( + out, " selected runtime version: {}", report.selected_mesh_version ); if report.selected_mesh_version != report.running_mesh_version { - println!(" version pin: native runtime version is pinned by config"); + let _ = writeln!( + out, + " version pin: native runtime version is pinned by config" + ); } if let Some(skippy_abi) = &report.configured_skippy_abi { - println!(" configured Skippy ABI: {skippy_abi}"); + let _ = writeln!(out, " configured Skippy ABI: {skippy_abi}"); } if let Some(selection) = &report.configured_selection { - println!(" configured selection: {selection}"); + let _ = writeln!(out, " configured selection: {selection}"); } if let Some(selection) = &report.effective_selection && report.configured_selection.as_deref() != Some(selection.as_str()) { - println!(" effective selection: {selection} (from --llama-flavor)"); + let _ = writeln!( + out, + " effective selection: {selection} (from --llama-flavor)" + ); } - println!(" cache: {}", report.cache_path.display()); - println!(" host: {}/{}", report.host.os, report.host.arch); + let _ = writeln!(out, " cache: {}", report.cache_path.display()); + let _ = writeln!(out, " host: {}/{}", report.host.os, report.host.arch); let flavors = report .host .available_flavors @@ -352,38 +389,39 @@ fn print_doctor_human(report: &NativeRuntimeDoctorReport) { .map(ToString::to_string) .collect::>() .join(", "); - println!(" detected flavors: {flavors}"); + let _ = writeln!(out, " detected flavors: {flavors}"); match &report.selected_runtime_id { Some(id) => { - println!(" selected: {id}"); + let _ = writeln!(out, " selected: {id}"); if let Some(flavor) = &report.selected_runtime_flavor { - println!(" flavor: {flavor}"); + let _ = writeln!(out, " flavor: {flavor}"); } if let Some(path) = &report.selected_runtime_path { - println!(" path: {}", path.display()); + let _ = writeln!(out, " path: {}", path.display()); } } None => { - println!(" selected: none"); + let _ = writeln!(out, " selected: none"); } } - println!(" installed: {}", report.installed_count); - println!( + let _ = writeln!(out, " installed: {}", report.installed_count); + let _ = writeln!( + out, " installed for selected version: {}", report.selected_version_installed_count ); if !report.blockers.is_empty() { - println!(); - println!("Blockers:"); + let _ = writeln!(out); + let _ = writeln!(out, "Blockers:"); for blocker in &report.blockers { - println!(" - {blocker}"); + let _ = writeln!(out, " - {blocker}"); } } if !report.recommendations.is_empty() { - println!(); - println!("Recommended next steps:"); + let _ = writeln!(out); + let _ = writeln!(out, "Recommended next steps:"); for recommendation in &report.recommendations { - println!(" - {recommendation}"); + let _ = writeln!(out, " - {recommendation}"); } } } diff --git a/crates/mesh-llm-commands/src/setup/command.rs b/crates/mesh-llm-commands/src/setup/command.rs index e11763c670..57eeea024f 100644 --- a/crates/mesh-llm-commands/src/setup/command.rs +++ b/crates/mesh-llm-commands/src/setup/command.rs @@ -18,6 +18,7 @@ use crate::runtime_native::{ }; use anyhow::{Result, anyhow}; use std::future::Future; +use std::io::Write; use std::pin::Pin; #[derive(Clone, Copy, Debug)] @@ -155,14 +156,16 @@ impl<'a> CliSetupActions<'a> { .runtime_outcome .as_ref() .ok_or_else(|| anyhow!("setup runtime prune step ran before runtime install"))?; + let mut err = mesh_llm_events::console_err(); match &outcome.prune { SetupNativeRuntimePruneResult::Skipped => {} SetupNativeRuntimePruneResult::Pruned(plan) => { if self.verbose { if plan.remove_dirs.is_empty() { - eprintln!("Native runtime cache is already clean"); + writeln!(err, "Native runtime cache is already clean")?; } else { - eprintln!( + writeln!( + err, "Pruned {} inactive native runtime cache entr{}", plan.remove_dirs.len(), if plan.remove_dirs.len() == 1 { @@ -170,12 +173,15 @@ impl<'a> CliSetupActions<'a> { } else { "ies" } - ); + )?; } } } SetupNativeRuntimePruneResult::Warning(warning) => { - eprintln!("warning: native runtime installed, but cache pruning failed: {warning}"); + writeln!( + err, + "warning: native runtime installed, but cache pruning failed: {warning}" + )?; } } Ok(()) @@ -193,7 +199,11 @@ impl<'a> CliSetupActions<'a> { } fn print_service_guidance(&self) { - eprintln!("Service not installed. Run `mesh-llm setup --service` to enable it later."); + let mut err = mesh_llm_events::console_err(); + let _ = writeln!( + err, + "Service not installed. Run `mesh-llm setup --service` to enable it later." + ); } } diff --git a/crates/mesh-llm-commands/src/setup/summary.rs b/crates/mesh-llm-commands/src/setup/summary.rs index 2ea0aa241e..9354ebe031 100644 --- a/crates/mesh-llm-commands/src/setup/summary.rs +++ b/crates/mesh-llm-commands/src/setup/summary.rs @@ -6,23 +6,31 @@ use crate::runtime_native::{ }; use crate::terminal::{style_muted, style_ok, style_warn}; use mesh_llm_runtime_install::NativeRuntimeInstallStatus; +use std::io::Write; pub(crate) fn print_runtime_install_result(outcome: &SetupNativeRuntimeOutcome) { + let mut err = mesh_llm_events::console_err(); match &outcome.status { SetupNativeRuntimeStatus::Skipped => {} SetupNativeRuntimeStatus::Installed(installed) => match installed.status { - NativeRuntimeInstallStatus::Installed => eprintln!( - "{} Installed native runtime {} for mesh version {}", - style_ok("โœ“"), - installed.runtime.native_runtime_id, - installed.runtime.mesh_version - ), - NativeRuntimeInstallStatus::AlreadyInstalled => eprintln!( - "{} Native runtime {} is already installed for mesh version {}", - style_ok("โœ“"), - installed.runtime.native_runtime_id, - installed.runtime.mesh_version - ), + NativeRuntimeInstallStatus::Installed => { + let _ = writeln!( + err, + "{} Installed native runtime {} for mesh version {}", + style_ok("โœ“"), + installed.runtime.native_runtime_id, + installed.runtime.mesh_version + ); + } + NativeRuntimeInstallStatus::AlreadyInstalled => { + let _ = writeln!( + err, + "{} Native runtime {} is already installed for mesh version {}", + style_ok("โœ“"), + installed.runtime.native_runtime_id, + installed.runtime.mesh_version + ); + } }, } } @@ -32,30 +40,33 @@ pub(crate) fn print_service_install_result( verbose: bool, ) { if verbose { + let mut err = mesh_llm_events::console_err(); for line in &report.messages { - eprintln!("{line}"); + let _ = writeln!(err, "{line}"); } } } pub(crate) fn print_setup_summary(plan: &SetupPlan, actions: &CliSetupActions<'_>, verbose: bool) { - eprintln!(); + let mut err = mesh_llm_events::console_err(); + let _ = writeln!(err); if verbose { - eprintln!("Setup summary"); - eprintln!("- Runtime: {}", runtime_summary(plan, actions)); - eprintln!("- Service: {}", service_summary(plan, actions)); - eprintln!( + let _ = writeln!(err, "Setup summary"); + let _ = writeln!(err, "- Runtime: {}", runtime_summary(plan, actions)); + let _ = writeln!(err, "- Service: {}", service_summary(plan, actions)); + let _ = writeln!( + err, "- GitHub star: {}", super::github::github_summary(plan, &actions.github_outcome) ); return; } - eprintln!("{} Mesh setup complete", style_ok("โœ“")); - eprintln!(" Runtime {}", runtime_brief(plan, actions)); - eprintln!(" Service {}", service_brief(plan, actions)); + let _ = writeln!(err, "{} Mesh setup complete", style_ok("โœ“")); + let _ = writeln!(err, " Runtime {}", runtime_brief(plan, actions)); + let _ = writeln!(err, " Service {}", service_brief(plan, actions)); if let Some(github) = github_brief(actions) { - eprintln!(" GitHub star {github}"); + let _ = writeln!(err, " GitHub star {github}"); } } diff --git a/crates/mesh-llm-commands/src/skills.rs b/crates/mesh-llm-commands/src/skills.rs index 3016bdaa80..bb62fa1a9a 100644 --- a/crates/mesh-llm-commands/src/skills.rs +++ b/crates/mesh-llm-commands/src/skills.rs @@ -1,3 +1,5 @@ +use std::io::Write; + use anyhow::Result; use mesh_llm_plugin_manager::{ PluginSkillInstallOptions, SkillAgent, SkillInstallReport, SkillInstallStatus, @@ -25,7 +27,9 @@ pub fn install_skills_for_agent(agent: SkillAgent) { }) { Ok(report) => print_agent_install_summary(agent, &report), Err(error) if !json_mode_enabled() => { - eprintln!( + let mut err = mesh_llm_events::console_err(); + let _ = writeln!( + err, "Could not install mesh plugin skills for {}: {error}", agent.as_str() ); @@ -80,7 +84,9 @@ fn print_agent_install_summary(agent: SkillAgent, report: &SkillInstallReport) { }) .count(); if changed > 0 { - eprintln!( + let mut err = mesh_llm_events::console_err(); + let _ = writeln!( + err, "โœ… Installed {changed} mesh plugin skill(s) for {}", agent.as_str() ); @@ -89,61 +95,71 @@ fn print_agent_install_summary(agent: SkillAgent, report: &SkillInstallReport) { fn print_install_report(report: &SkillInstallReport, dry_run: bool) -> Result<()> { if json_mode_enabled() { - println!("{}", serde_json::to_string_pretty(report)?); + let mut out = mesh_llm_events::machine_out(); + writeln!(out, "{}", serde_json::to_string_pretty(report)?)?; return Ok(()); } + let mut err = mesh_llm_events::console_err(); + let heading = if dry_run { "๐Ÿงช Mesh plugin skill install preview" } else { "๐Ÿง  Installing mesh plugin skills" }; - eprintln!("{heading}"); + writeln!(err, "{heading}")?; if report.available_skills == 0 { - eprintln!("๐Ÿ”Ž No plugin skills found in installed plugins."); - eprintln!("๐Ÿ“ฆ Plugins can expose skills with skills//SKILL.md."); + writeln!(err, "๐Ÿ”Ž No plugin skills found in installed plugins.")?; + writeln!( + err, + "๐Ÿ“ฆ Plugins can expose skills with skills//SKILL.md." + )?; return Ok(()); } - eprintln!( + writeln!( + err, "๐Ÿ“ฆ Found {}", plural_count(report.available_skills, "plugin skill") - ); + )?; if report.targets.is_empty() { - eprintln!("๐Ÿ”Ž No supported agent skill targets detected."); - eprintln!("๐Ÿ’ก Use --agent or --all to install anyway."); + writeln!(err, "๐Ÿ”Ž No supported agent skill targets detected.")?; + writeln!(err, "๐Ÿ’ก Use --agent or --all to install anyway.")?; return Ok(()); } - eprintln!( + writeln!( + err, "๐ŸŽฏ Targeting {}:", plural_count(report.targets.len(), "agent") - ); + )?; for target in &report.targets { let reason = target .detection_reason .as_deref() .unwrap_or("explicit target"); - eprintln!( + writeln!( + err, " โ€ข {:<8} {} ({reason})", target.agent.as_str(), target.root.display() - ); + )?; } - eprintln!("๐Ÿ› ๏ธ Applying skills:"); + writeln!(err, "๐Ÿ› ๏ธ Applying skills:")?; for action in &report.actions { let Some(label) = action_status_label(&action.status, dry_run) else { continue; }; - eprintln!( + writeln!( + err, " {label:<17} {:<28} -> {:<8} {}", skill_display_name(action), action.agent.as_str(), action.destination_dir.display() - ); + )?; } print_install_summary(report, dry_run); @@ -177,7 +193,8 @@ fn print_install_summary(report: &SkillInstallReport, dry_run: bool) { if conflicts > 0 { parts.push(count_label(conflicts, "conflict", "conflicts")); } - eprintln!("โœ… Skill install {verb}: {}", parts.join(", ")); + let mut err = mesh_llm_events::console_err(); + let _ = writeln!(err, "โœ… Skill install {verb}: {}", parts.join(", ")); } fn action_status_label(status: &SkillInstallStatus, dry_run: bool) -> Option<&'static str> { diff --git a/crates/mesh-llm-commands/src/terminal.rs b/crates/mesh-llm-commands/src/terminal.rs index 7ccae0d496..28d51d984c 100644 --- a/crates/mesh-llm-commands/src/terminal.rs +++ b/crates/mesh-llm-commands/src/terminal.rs @@ -28,16 +28,16 @@ pub(crate) fn confirm_yes_no(message: &str, default: ConfirmDefault) -> Result Result return Ok(Some(default.empty_reply())), "y" | "yes" => return Ok(Some(true)), "n" | "no" => return Ok(Some(false)), - _ => eprintln!("Please answer y or n."), + _ => writeln!(err, "Please answer y or n.")?, } } } diff --git a/crates/mesh-llm-commands/src/uninstall.rs b/crates/mesh-llm-commands/src/uninstall.rs index 940a8f0b26..6bdd19fa41 100644 --- a/crates/mesh-llm-commands/src/uninstall.rs +++ b/crates/mesh-llm-commands/src/uninstall.rs @@ -2,7 +2,8 @@ use crate::terminal::{self, ConfirmDefault, style_muted, style_ok, style_warn}; use anyhow::{Context, Result, bail}; use serde::Serialize; use std::{ - fs, io, + fs, + io::{self, Write}, path::{Path, PathBuf}, process::Command, }; @@ -454,23 +455,27 @@ fn add_option_warnings(options: &UninstallOptions, outcome: &mut UninstallOutcom fn render_plan(plan: &UninstallPlan, json: bool, verbose: bool) -> Result<()> { if json { - println!("{}", serde_json::to_string_pretty(plan)?); + let mut out = mesh_llm_events::machine_out(); + writeln!(out, "{}", serde_json::to_string_pretty(plan)?)?; return Ok(()); } + let mut err = mesh_llm_events::console_err(); for line in plan_lines(plan, verbose) { - eprintln!("{line}"); + writeln!(err, "{line}")?; } Ok(()) } fn render_outcome(outcome: &UninstallOutcome, json: bool, verbose: bool) -> Result<()> { if json { - println!("{}", serde_json::to_string_pretty(outcome)?); + let mut out = mesh_llm_events::machine_out(); + writeln!(out, "{}", serde_json::to_string_pretty(outcome)?)?; return Ok(()); } - eprintln!(); + let mut err = mesh_llm_events::console_err(); + writeln!(err)?; for line in outcome_lines(outcome, verbose) { - eprintln!("{line}"); + writeln!(err, "{line}")?; } Ok(()) } diff --git a/crates/mesh-llm/src/commands/discover.rs b/crates/mesh-llm/src/commands/discover.rs index 0503faad40..59d95213ec 100644 --- a/crates/mesh-llm/src/commands/discover.rs +++ b/crates/mesh-llm/src/commands/discover.rs @@ -1,3 +1,5 @@ +use std::io::Write; + use anyhow::Result; use mesh_llm_host_runtime::command_support::discovery::{self, nostr}; @@ -45,17 +47,19 @@ async fn run_nostr_discover( ) -> Result<()> { let relays = discovery::nostr_relays(&relays); - eprintln!("๐Ÿ” Searching Nostr relays for mesh-llm meshes..."); + let mut err = mesh_llm_events::console_err(); + writeln!(err, "๐Ÿ” Searching Nostr relays for mesh-llm meshes...")?; let meshes = nostr::discover(&relays, &filter, None).await?; + let mut err = mesh_llm_events::console_err(); if meshes.is_empty() { - eprintln!("No meshes found."); + writeln!(err, "No meshes found.")?; if filter.name.is_some() || filter.model.is_some() || filter.min_vram_gb.is_some() || filter.region.is_some() { - eprintln!("Try broader filters or check relays."); + writeln!(err, "Try broader filters or check relays.")?; } return Ok(()); } @@ -66,7 +70,7 @@ async fn run_nostr_discover( .as_secs(); let last_mesh_id = discovery::load_last_mesh_id(); - eprintln!("Found {} mesh(es):\n", meshes.len()); + writeln!(err, "Found {} mesh(es):\n", meshes.len())?; for (i, mesh) in meshes.iter().enumerate() { let score = nostr::score_mesh(mesh, now, last_mesh_id.as_deref()); let age = now.saturating_sub(mesh.published_at); @@ -85,14 +89,15 @@ async fn run_nostr_discover( } else { format!("{} clients", mesh.listing.client_count) }; - eprintln!( + writeln!( + err, " [{}] {} (score: {}, {}, {})", i + 1, mesh, score, freshness, capacity - ); + )?; let token = &mesh.listing.invite_token; let display_token = if token.len() > 40 { format!("{}...{}", &token[..20], &token[token.len() - 12..]) @@ -100,23 +105,30 @@ async fn run_nostr_discover( token.clone() }; if !mesh.listing.on_disk.is_empty() { - eprintln!(" on disk: {}", mesh.listing.on_disk.join(", ")); + writeln!(err, " on disk: {}", mesh.listing.on_disk.join(", "))?; } - eprintln!(" token: {}", display_token); - eprintln!(); + writeln!(err, " token: {}", display_token)?; + writeln!(err)?; } if auto_join { let best = &meshes[0]; - eprintln!("Auto-joining best match: {}", best); - eprintln!("\nRun:"); - eprintln!(" mesh-llm --join {}", best.listing.invite_token); - println!("{}", best.listing.invite_token); + writeln!(err, "Auto-joining best match: {}", best)?; + writeln!(err, "\nRun:")?; + writeln!(err, " mesh-llm --join {}", best.listing.invite_token)?; + let mut out = mesh_llm_events::machine_out(); + writeln!(out, "{}", best.listing.invite_token)?; } else { - eprintln!("To join a mesh:"); - eprintln!(" mesh-llm --join "); - eprintln!(" mesh-llm --discover # join by mesh name"); - eprintln!(" mesh-llm client --discover # join as client by mesh name"); + writeln!(err, "To join a mesh:")?; + writeln!(err, " mesh-llm --join ")?; + writeln!( + err, + " mesh-llm --discover # join by mesh name" + )?; + writeln!( + err, + " mesh-llm client --discover # join as client by mesh name" + )?; } Ok(()) @@ -128,10 +140,12 @@ async fn run_lan_discover( supplied_join_tokens: Vec, ) -> Result<()> { let supplied_join_token = supplied_join_tokens.first().map(String::as_str); - eprintln!( + let mut err = mesh_llm_events::console_err(); + writeln!( + err, "Searching local LAN for mesh-llm meshes via {}...", discovery::LAN_SERVICE_TYPE - ); + )?; let meshes = discovery::discover_lan( &filter, supplied_join_token, @@ -139,16 +153,23 @@ async fn run_lan_discover( ) .await?; + let mut err = mesh_llm_events::console_err(); if meshes.is_empty() { - eprintln!("No LAN meshes found."); + writeln!(err, "No LAN meshes found.")?; if supplied_join_token.is_none() { - eprintln!("mDNS advertisements do not include reusable invite tokens."); - eprintln!("Pass --join to verify a LAN advertisement by token fingerprint."); + writeln!( + err, + "mDNS advertisements do not include reusable invite tokens." + )?; + writeln!( + err, + "Pass --join to verify a LAN advertisement by token fingerprint." + )?; } return Ok(()); } - eprintln!("Found {} LAN mesh(es):\n", meshes.len()); + writeln!(err, "Found {} LAN mesh(es):\n", meshes.len())?; for (i, mesh) in meshes.iter().enumerate() { let vram_gb = mesh.listing.total_vram_bytes as f64 / 1e9; let models = if mesh.listing.serving.is_empty() { @@ -161,38 +182,47 @@ async fn run_lan_discover( } else { "requires supplied token" }; - eprintln!( + writeln!( + err, " [{}] {} {} node(s), {:.0}GB capacity serving: {}", i + 1, mesh.listing.name.as_deref().unwrap_or("(unnamed)"), mesh.listing.node_count, vram_gb, models - ); - eprintln!( + )?; + writeln!( + err, " instance: {} host: {}:{} {}", mesh.instance_name, mesh.host, mesh.port, join_state - ); + )?; if let Some(version) = &mesh.published_version { - eprintln!(" version: {version}"); + writeln!(err, " version: {version}")?; } if !mesh.listing.on_disk.is_empty() { - eprintln!(" on disk: {}", mesh.listing.on_disk.join(", ")); + writeln!(err, " on disk: {}", mesh.listing.on_disk.join(", "))?; } - eprintln!(); + writeln!(err)?; } if auto_join { if let Some(token) = meshes.iter().find_map(|mesh| mesh.join_token()) { - println!("{token}"); + let mut out = mesh_llm_events::machine_out(); + writeln!(out, "{token}")?; } else { - eprintln!("No LAN mesh matched the supplied token fingerprint."); - eprintln!("mDNS intentionally does not advertise raw invite tokens."); + writeln!(err, "No LAN mesh matched the supplied token fingerprint.")?; + writeln!( + err, + "mDNS intentionally does not advertise raw invite tokens." + )?; } } else { - eprintln!("To join a LAN mesh:"); - eprintln!(" mesh-llm --join "); - eprintln!(" mesh-llm --join discover --mesh-discovery-mode mdns --auto"); + writeln!(err, "To join a LAN mesh:")?; + writeln!(err, " mesh-llm --join ")?; + writeln!( + err, + " mesh-llm --join discover --mesh-discovery-mode mdns --auto" + )?; } Ok(()) @@ -200,10 +230,11 @@ async fn run_lan_discover( /// Stop all mesh-llm instances tracked in the runtime root. pub(crate) fn run_stop() -> Result<()> { + let mut err = mesh_llm_events::console_err(); let root = match discovery::runtime_root() { Ok(root) => root, Err(_) => { - eprintln!("Nothing running."); + writeln!(err, "Nothing running.")?; return Ok(()); } }; @@ -219,19 +250,25 @@ pub(crate) fn run_stop() -> Result<()> { if outcome.is_success() { match outcome { backend::TerminationOutcome::Graceful => { - eprintln!( + writeln!( + err, " Terminated owner pid={} gracefully ({})", target.pid, target.label - ); + )?; } backend::TerminationOutcome::Killed => { - eprintln!(" Force-killed owner pid={} ({})", target.pid, target.label); + writeln!( + err, + " Force-killed owner pid={} ({})", + target.pid, target.label + )?; } backend::TerminationOutcome::NotRunning => { - eprintln!( + writeln!( + err, " Owner pid={} was already stopped ({})", target.pid, target.label - ); + )?; } backend::TerminationOutcome::Failed => unreachable!(), } @@ -240,7 +277,7 @@ pub(crate) fn run_stop() -> Result<()> { } if killed == 0 { - eprintln!("Nothing running."); + writeln!(err, "Nothing running.")?; } Ok(()) } diff --git a/crates/mesh-llm/src/commands/doctor.rs b/crates/mesh-llm/src/commands/doctor.rs index a7d02e5a39..e49a1ed073 100644 --- a/crates/mesh-llm/src/commands/doctor.rs +++ b/crates/mesh-llm/src/commands/doctor.rs @@ -1,5 +1,6 @@ use anyhow::{Context, Result}; use serde_json::{Map, Value, json}; +use std::io::Write; use std::path::{Path, PathBuf}; use std::time::{SystemTime, UNIX_EPOCH}; @@ -79,16 +80,18 @@ async fn run_split_doctor( None => Vec::new(), }; if json_output { - println!("{}", serde_json::to_string_pretty(&report)?); + let mut out = mesh_llm_events::machine_out(); + writeln!(out, "{}", serde_json::to_string_pretty(&report)?)?; } else { + let mut out = mesh_llm_events::console_out(); for line in split_readiness_lines(&report) { - println!("{line}"); + writeln!(out, "{line}")?; } if !captured_files.is_empty() { - println!(); - println!("Captured diagnostics:"); + writeln!(out)?; + writeln!(out, "Captured diagnostics:")?; for path in captured_files { - println!(" - {}", path.display()); + writeln!(out, " - {}", path.display())?; } } } diff --git a/crates/mesh-llm/src/commands/download.rs b/crates/mesh-llm/src/commands/download.rs index 732ffcb50e..ce46c4753d 100644 --- a/crates/mesh-llm/src/commands/download.rs +++ b/crates/mesh-llm/src/commands/download.rs @@ -1,3 +1,5 @@ +use std::io::Write; + use anyhow::Result; pub(crate) async fn dispatch_download_command(name: Option<&str>, draft: bool) -> Result<()> { @@ -25,20 +27,22 @@ pub(crate) async fn dispatch_download_command(name: Option<&str>, draft: bool) - mesh_llm_host_runtime::command_support::models::download_model_ref_with_progress_details(&draft_ref, true) .await?; } else { - eprintln!("โš  No draft model available for {}", query); + let mut err = mesh_llm_events::console_err(); + writeln!(err, "โš  No draft model available for {}", query)?; } } } None => { mesh_llm_host_runtime::command_support::models::remote_catalog::ensure_catalog()?; - eprintln!("Available models:"); - eprintln!(); + let mut err = mesh_llm_events::console_err(); + writeln!(err, "Available models:")?; + writeln!(err)?; for model in mesh_llm_host_runtime::command_support::models::remote_catalog::loaded_models()? { let size = model.size.as_deref().unwrap_or("?"); let description = model.description.as_deref().unwrap_or(""); - eprintln!(" {:40} {:>6} {}", model.name, size, description); + writeln!(err, " {:40} {:>6} {}", model.name, size, description)?; } } } diff --git a/crates/mesh-llm/src/commands/models/formatters.rs b/crates/mesh-llm/src/commands/models/formatters.rs index 5df1d8d791..9437017597 100644 --- a/crates/mesh-llm/src/commands/models/formatters.rs +++ b/crates/mesh-llm/src/commands/models/formatters.rs @@ -9,6 +9,7 @@ use mesh_llm_host_runtime::command_support::models::{ }; use mesh_llm_system::hardware; use serde_json::{Value, json}; +use std::io::Write; use std::path::{Path, PathBuf}; use std::time::Duration; @@ -145,7 +146,8 @@ pub(crate) fn sort_label(sort: SearchSort) -> &'static str { } pub(crate) fn print_json(value: Value) -> Result<()> { - println!("{}", serde_json::to_string_pretty(&value)?); + let mut out = mesh_llm_events::machine_out(); + writeln!(out, "{}", serde_json::to_string_pretty(&value)?)?; Ok(()) } diff --git a/crates/mesh-llm/src/commands/models/formatters_console.rs b/crates/mesh-llm/src/commands/models/formatters_console.rs index 0d043d8df4..ff33427218 100644 --- a/crates/mesh-llm/src/commands/models/formatters_console.rs +++ b/crates/mesh-llm/src/commands/models/formatters_console.rs @@ -12,7 +12,7 @@ use mesh_llm_host_runtime::command_support::models::{ remote_catalog_model_ref, }; use std::fmt::Write as FmtWrite; -use std::io::{IsTerminal, Write}; +use std::io::Write; use std::time::Duration; use tabwriter::TabWriter; @@ -186,12 +186,14 @@ impl SearchFormatter for ConsoleFormatter { filter: SearchArtifactFilter, sort: SearchSort, ) -> Result<()> { - eprintln!( + let mut err = mesh_llm_events::console_err(); + writeln!( + err, "๐Ÿ”Ž No {} catalog models matched '{}' (sorted by {}).", filter_label(filter), query, sort_label(sort) - ); + )?; Ok(()) } @@ -239,12 +241,14 @@ impl SearchFormatter for ConsoleFormatter { filter: SearchArtifactFilter, sort: SearchSort, ) -> Result<()> { - eprintln!( + let mut err = mesh_llm_events::console_err(); + writeln!( + err, "๐Ÿ”Ž No Hugging Face {} matches for '{}' (sorted by {}).", filter_label(filter), query, sort_label(sort) - ); + )?; Ok(()) } @@ -374,8 +378,9 @@ impl ModelsFormatter for ConsoleFormatter { fn render_installed(&self, rows: &[InstalledRow]) -> Result<()> { if rows.is_empty() { - println!("๐Ÿ“ฆ No installed models found"); - println!(" HF cache: {}", huggingface_cache_dir().display()); + let mut out = mesh_llm_events::console_out(); + writeln!(out, "๐Ÿ“ฆ No installed models found")?; + writeln!(out, " HF cache: {}", huggingface_cache_dir().display())?; return Ok(()); } @@ -459,56 +464,57 @@ impl ModelsFormatter for ConsoleFormatter { } fn render_show(&self, details: &ModelDetails, variants: Option<&[ModelDetails]>) -> Result<()> { + let mut out = mesh_llm_events::console_out(); if model_kind_code(details.kind) == "mlx" { - println!("๐Ÿ”Ž {}", details.exact_ref); + writeln!(out, "๐Ÿ”Ž {}", details.exact_ref)?; } else { - println!("๐Ÿ”Ž {}", details.display_name); + writeln!(out, "๐Ÿ”Ž {}", details.display_name)?; } if let Some(summary) = super::formatters::local_capacity_summary() { - println!("{}", summary); + writeln!(out, "{}", summary)?; } - println!(); - println!("Ref: {}", details.exact_ref); - println!("Type: {}", details.kind); - println!("Source: {}", format_source_label(details.source)); + writeln!(out)?; + writeln!(out, "Ref: {}", details.exact_ref)?; + writeln!(out, "Type: {}", details.kind)?; + writeln!(out, "Source: {}", format_source_label(details.source))?; if let Some(size) = details.size_label.as_deref() { - println!("Size: {size}"); + writeln!(out, "Size: {size}")?; if let Some(fit) = fit_hint_for_size_label(size) { - println!("Fit: {}", fit); + writeln!(out, "Fit: {}", fit)?; } } if let Some(description) = details.description.as_deref() { - println!("About: {description}"); + writeln!(out, "About: {description}")?; } if let Some(draft) = details.draft.as_deref() { - println!("๐Ÿง  Draft: {draft}"); + writeln!(out, "๐Ÿง  Draft: {draft}")?; } - println!("Capabilities:"); - println!(" ๐Ÿ’ฌ text"); + writeln!(out, "Capabilities:")?; + writeln!(out, " ๐Ÿ’ฌ text")?; if details.capabilities.multimodal_label().is_some() { - println!(" ๐ŸŽ›๏ธ multimodal"); + writeln!(out, " ๐ŸŽ›๏ธ multimodal")?; } if let Some(label) = details.capabilities.vision_label() { - println!(" ๐Ÿ‘๏ธ vision ({label})"); + writeln!(out, " ๐Ÿ‘๏ธ vision ({label})")?; } if let Some(label) = details.capabilities.audio_label() { - println!(" ๐Ÿ”Š audio ({label})"); + writeln!(out, " ๐Ÿ”Š audio ({label})")?; } if let Some(label) = details.capabilities.reasoning_label() { - println!(" ๐Ÿง  reasoning ({label})"); + writeln!(out, " ๐Ÿง  reasoning ({label})")?; } - println!("๐Ÿ“ฅ Download:"); + writeln!(out, "๐Ÿ“ฅ Download:")?; if model_kind_code(details.kind) == "mlx" { - println!(" mesh-llm models download {}", details.exact_ref); + writeln!(out, " mesh-llm models download {}", details.exact_ref)?; } else { - println!(" {}", details.download_url); + writeln!(out, " {}", details.download_url)?; } if let Some(variants) = variants && !variants.is_empty() { - println!(); - println!("Variants:"); + writeln!(out)?; + writeln!(out, "Variants:")?; let mut rows = Vec::new(); for variant in variants { let size = variant.size_label.as_deref().unwrap_or("-"); @@ -541,26 +547,32 @@ impl ModelsFormatter for ConsoleFormatter { )?; } table.flush()?; - print!("{}", String::from_utf8_lossy(&table.into_inner()?)); + write!(out, "{}", String::from_utf8_lossy(&table.into_inner()?))?; } Ok(()) } fn render_download(&self, input: DownloadRenderInput<'_>) -> Result<()> { - let colors = std::io::stdout().is_terminal(); - println!("{}", downloaded_model_headline(input.stats, colors)); - println!(); + let mut out = mesh_llm_events::console_out(); + let colors = out.is_terminal(); + writeln!(out, "{}", downloaded_model_headline(input.stats, colors))?; + writeln!(out)?; for line in download_summary_lines(&input, colors) { - println!("{line}"); + writeln!(out, "{line}")?; } if let Some((_draft_name, draft_path)) = input.draft { - println!(); - println!("{}", styled_download_success("โœ“ Downloaded draft", colors)); - println!( + writeln!(out)?; + writeln!( + out, + "{}", + styled_download_success("โœ“ Downloaded draft", colors) + )?; + writeln!( + out, " {} {}", styled_label("path", colors), draft_path.display() - ); + )?; } Ok(()) } @@ -571,10 +583,11 @@ impl ModelsFormatter for ConsoleFormatter { package_ref: &str, path: &std::path::Path, ) -> Result<()> { - println!("โœ… Downloaded layer package"); - println!(" requested: {model_ref}"); - println!(" package: {package_ref}"); - println!(" {}", path.display()); + let mut out = mesh_llm_events::console_out(); + writeln!(out, "โœ… Downloaded layer package")?; + writeln!(out, " requested: {model_ref}")?; + writeln!(out, " package: {package_ref}")?; + writeln!(out, " {}", path.display())?; Ok(()) } @@ -583,68 +596,86 @@ impl ModelsFormatter for ConsoleFormatter { } fn render_delete_preview(&self, resolved: &CliResolvedModel) -> Result<()> { - println!("๐Ÿ—‘๏ธ Model delete preview"); - println!(); - println!("Name: {}", resolved.display_name); + let mut out = mesh_llm_events::console_out(); + writeln!(out, "๐Ÿ—‘๏ธ Model delete preview")?; + writeln!(out)?; + writeln!(out, "Name: {}", resolved.display_name)?; if resolved.paths.len() > 1 { - println!("Paths ({}):", resolved.paths.len()); + writeln!(out, "Paths ({}):", resolved.paths.len())?; for path in &resolved.paths { - println!(" {}", path.display()); + writeln!(out, " {}", path.display())?; } } else { - println!("Path: {}", resolved.path.display()); + writeln!(out, "Path: {}", resolved.path.display())?; } - println!("Mode: installed model ref resolution"); + writeln!(out, "Mode: installed model ref resolution")?; let file_size = resolved .paths .iter() .map(|path| std::fs::metadata(path).map(|m| m.len()).unwrap_or(0)) .sum(); - println!("Size: {}", format_installed_size(file_size)); + writeln!(out, "Size: {}", format_installed_size(file_size))?; if resolved.derived_stage_paths.is_empty() { - println!("Derived stage cache files: 0"); + writeln!(out, "Derived stage cache files: 0")?; } else { - println!( + writeln!( + out, "Derived stage cache files ({}):", resolved.derived_stage_paths.len() - ); + )?; for path in &resolved.derived_stage_paths { - println!(" {}", path.display()); + writeln!(out, " {}", path.display())?; } } if !resolved.matched_records.is_empty() { - println!(); - println!("{} usage record(s) found:", resolved.matched_records.len()); + writeln!(out)?; + writeln!( + out, + "{} usage record(s) found:", + resolved.matched_records.len() + )?; for record in &resolved.matched_records { - println!( + writeln!( + out, " - {} (last used: {})", record.lookup_key, record.last_used_at - ); + )?; } } - println!(); - println!("To confirm deletion, run with --yes flag."); + writeln!(out)?; + writeln!(out, "To confirm deletion, run with --yes flag.")?; Ok(()) } fn render_delete_result(&self, result: &CliDeleteResult) -> Result<()> { - println!("โœ… Model deleted successfully"); - println!(); - println!("Deleted paths:"); + let mut out = mesh_llm_events::console_out(); + writeln!(out, "โœ… Model deleted successfully")?; + writeln!(out)?; + writeln!(out, "Deleted paths:")?; for p in &result.deleted_paths { - println!(" {}", p.display()); + writeln!(out, " {}", p.display())?; } - println!(); - println!( + writeln!(out)?; + writeln!( + out, "Reclaimed: {}", format_installed_size(result.reclaimed_bytes) - ); - println!("Metadata files removed: {}", result.removed_metadata_files); - println!("Usage records purged: {}", result.removed_usage_records); - println!( + )?; + writeln!( + out, + "Metadata files removed: {}", + result.removed_metadata_files + )?; + writeln!( + out, + "Usage records purged: {}", + result.removed_usage_records + )?; + writeln!( + out, "Derived stage cache files removed: {}", result.removed_derived_cache_files - ); + )?; Ok(()) } } diff --git a/crates/mesh-llm/src/commands/models/mod.rs b/crates/mesh-llm/src/commands/models/mod.rs index 8267e91a53..141e29ddd4 100644 --- a/crates/mesh-llm/src/commands/models/mod.rs +++ b/crates/mesh-llm/src/commands/models/mod.rs @@ -22,6 +22,7 @@ use mesh_llm_host_runtime::command_support::models::{ use mesh_llm_tui::terminal_progress::{DeterminateProgressLine, clear_stderr_line, start_spinner}; use serde_json::json; use std::io::IsTerminal; +use std::io::Write; use std::time::Duration; use std::time::Instant; @@ -117,7 +118,8 @@ pub async fn run_model_search( ); if completed == total { let _ = clear_stderr_line(); - eprintln!(" Inspected {completed}/{total} candidate repos..."); + let mut err = mesh_llm_events::console_err(); + let _ = writeln!(err, " Inspected {completed}/{total} candidate repos..."); } } }, @@ -172,18 +174,21 @@ pub async fn run_model_certify( std::fs::write(path, format!("{report_json}\n"))?; } if json_output { - println!("{report_json}"); + let mut out = mesh_llm_events::machine_out(); + writeln!(out, "{report_json}")?; } else { - println!( + let mut out = mesh_llm_events::console_out(); + writeln!( + out, "Skippy package certification: {}", status_label(report.status) - ); - println!("Model: {}", report.model_id); - println!("Package: {}", report.resolved_package_ref); - println!("Manifest: {}", report.manifest_sha256); - println!("Layers: {}", report.layer_count); + )?; + writeln!(out, "Model: {}", report.model_id)?; + writeln!(out, "Package: {}", report.resolved_package_ref)?; + writeln!(out, "Manifest: {}", report.manifest_sha256)?; + writeln!(out, "Layers: {}", report.layer_count)?; if let Some(path) = report_out { - println!("Report: {}", path.display()); + writeln!(out, "Report: {}", path.display())?; } } if report.status != CertificationGateStatus::Passed { @@ -265,20 +270,24 @@ pub async fn run_model_show(model_ref: &str, json_output: bool) -> Result<()> { let interactive = !json_output && std::io::stdout().is_terminal(); let detail_started = Instant::now(); if interactive { - eprintln!("๐Ÿ”Ž Resolving model details from Hugging Face..."); + let mut err = mesh_llm_events::console_err(); + writeln!(err, "๐Ÿ”Ž Resolving model details from Hugging Face...")?; } let details = show_exact_model(model_ref).await?; if interactive { - eprintln!( + let mut err = mesh_llm_events::console_err(); + writeln!( + err, "โœ… Resolved model details ({:.1}s)", detail_started.elapsed().as_secs_f32() - ); + )?; } let is_gguf = model_kind_code(details.kind) == "gguf"; let variants = if is_gguf { let variants_started = Instant::now(); if interactive { - eprintln!("๐Ÿ”Ž Fetching GGUF variants from Hugging Face..."); + let mut err = mesh_llm_events::console_err(); + writeln!(err, "๐Ÿ”Ž Fetching GGUF variants from Hugging Face...")?; } let variants_progress = DeterminateProgressLine::new("๐Ÿ”Ž"); let variants = show_model_variants_with_progress(&details.exact_ref, |progress| { @@ -305,17 +314,21 @@ pub async fn run_model_show(model_ref: &str, json_output: bool) -> Result<()> { .await?; if let Some(variants) = &variants { if interactive { - eprintln!( + let mut err = mesh_llm_events::console_err(); + writeln!( + err, "โœ… Fetched {} GGUF variants ({:.1}s)", variants.len(), variants_started.elapsed().as_secs_f32() - ); + )?; } } else if interactive { - eprintln!( + let mut err = mesh_llm_events::console_err(); + writeln!( + err, "โœ… No GGUF variants for this ref ({:.1}s)", variants_started.elapsed().as_secs_f32() - ); + )?; } variants } else { @@ -335,11 +348,12 @@ pub async fn run_model_download( && let Some((package_ref, package_dir)) = download_layer_package_for_model_ref(model_ref).await? { + let mut err = mesh_llm_events::console_err(); if !json_output { - eprintln!("โ„น Using repackaged model from catalog: {package_ref}"); + writeln!(err, "โ„น Using repackaged model from catalog: {package_ref}")?; } if include_draft && !json_output { - eprintln!("โš  Draft download is not available for layer packages"); + writeln!(err, "โš  Draft download is not available for layer packages")?; } return formatter.render_layer_package_download(model_ref, &package_ref, &package_dir); } @@ -381,10 +395,12 @@ pub async fn run_model_download( }; draft_out = Some((draft_name.to_string(), draft_download.path)); } else if !json_output { - eprintln!( + let mut err = mesh_llm_events::console_err(); + writeln!( + err, "โš  No draft model available for {}", details_ref.display_name - ); + )?; } } formatter.render_download(DownloadRenderInput { @@ -559,19 +575,22 @@ fn run_model_prune(yes: bool, json_output: bool) -> Result<()> { mesh_llm_host_runtime::command_support::models::skippy::materialized_stage_cache_dir(); if !yes { if json_output { - println!( + let mut out = mesh_llm_events::machine_out(); + writeln!( + out, "{}", serde_json::to_string_pretty(&json!({ "dry_run": true, "cache_dir": cache_dir, "apply": "mesh-llm models prune --yes", }))? - ); + )?; } else { - println!("๐Ÿงน Derived stage cache prune preview"); - println!("๐Ÿ“ Cache: {}", cache_dir.display()); - println!("Apply with:"); - println!(" mesh-llm models prune --yes"); + let mut out = mesh_llm_events::console_out(); + writeln!(out, "๐Ÿงน Derived stage cache prune preview")?; + writeln!(out, "๐Ÿ“ Cache: {}", cache_dir.display())?; + writeln!(out, "Apply with:")?; + writeln!(out, " mesh-llm models prune --yes")?; } return Ok(()); } @@ -579,17 +598,20 @@ fn run_model_prune(yes: bool, json_output: bool) -> Result<()> { mesh_llm_host_runtime::command_support::models::skippy::prune_unpinned_materialized_stages( )?; if json_output { - println!( + let mut out = mesh_llm_events::machine_out(); + writeln!( + out, "{}", serde_json::to_string_pretty(&json!({ "dry_run": false, "cache_dir": cache_dir, "removed_files": removed, }))? - ); + )?; } else { - println!("โœ… Derived stage cache pruned"); - println!("Removed files: {}", removed); + let mut out = mesh_llm_events::console_out(); + writeln!(out, "โœ… Derived stage cache pruned")?; + writeln!(out, "Removed files: {}", removed)?; } Ok(()) } @@ -624,95 +646,104 @@ fn render_cleanup_console( plan: &ModelCleanupPlan, result: Option<&ModelCleanupResult>, ) -> Result<()> { + let mut out = mesh_llm_events::console_out(); let executed = result.is_some(); if executed { - println!("โœ… Model cleanup complete"); + writeln!(out, "โœ… Model cleanup complete")?; } else { - println!("๐Ÿงน Model cleanup preview"); + writeln!(out, "๐Ÿงน Model cleanup preview")?; } - println!( + writeln!( + out, "๐Ÿ“ HF cache: {}", mesh_llm_host_runtime::command_support::models::huggingface_hub_cache_dir().display() - ); - println!("๐Ÿ“ Mesh cache: {}", model_usage_cache_dir().display()); - println!("๐Ÿ›ก๏ธ Scope: mesh-managed records only"); + )?; + writeln!(out, "๐Ÿ“ Mesh cache: {}", model_usage_cache_dir().display())?; + writeln!(out, "๐Ÿ›ก๏ธ Scope: mesh-managed records only")?; if let Some(unused_since) = unused_since { - println!("โฑ๏ธ Filter: unused for at least {}", unused_since); + writeln!(out, "โฑ๏ธ Filter: unused for at least {}", unused_since)?; } - println!(); + writeln!(out)?; if plan.candidates.is_empty() { - println!("No mesh-managed models matched the cleanup filters."); + writeln!(out, "No mesh-managed models matched the cleanup filters.")?; } else { for candidate in &plan.candidates { - println!("๐Ÿ“ฆ {}", candidate.display_name); + writeln!(out, "๐Ÿ“ฆ {}", candidate.display_name)?; if candidate.stale_record_only { - println!(" would remove: stale usage record only"); + writeln!(out, " would remove: stale usage record only")?; } else { - println!( + writeln!( + out, " would remove: {} across {} file{}", format_installed_size(candidate.total_bytes), candidate.file_count, if candidate.file_count == 1 { "" } else { "s" } - ); + )?; } if let Some(model_ref) = candidate.model_ref.as_deref() { - println!(" ref: {}", model_ref); + writeln!(out, " ref: {}", model_ref)?; } - println!(" source: {}", candidate.source); + writeln!(out, " source: {}", candidate.source)?; if let Some(label) = format_relative_timestamp(&candidate.last_used_at) { - println!(" last used: {}", label); + writeln!(out, " last used: {}", label)?; } - println!(" path: {}", candidate.primary_path.display()); + writeln!(out, " path: {}", candidate.primary_path.display())?; if candidate.stale_record_only { - println!( + writeln!( + out, " note: no managed files remain on disk; cleanup only removes the usage record" - ); + )?; } - println!(); + writeln!(out)?; } } if let Some(result) = result { - println!("Removed model records: {}", result.removed_candidates); - println!("Removed files: {}", result.removed_files); - println!( + writeln!(out, "Removed model records: {}", result.removed_candidates)?; + writeln!(out, "Removed files: {}", result.removed_files)?; + writeln!( + out, "Removed metadata cache files: {}", result.removed_metadata_files - ); - println!("Removed usage records: {}", result.removed_records); - println!( + )?; + writeln!(out, "Removed usage records: {}", result.removed_records)?; + writeln!( + out, "Reclaimed: {}", format_installed_size(result.reclaimed_bytes) - ); + )?; } else { - println!( + writeln!( + out, "Would remove: {} across {} file{}", format_installed_size(plan.total_bytes), plan.total_files, if plan.total_files == 1 { "" } else { "s" } - ); + )?; if plan.stale_record_only > 0 { - println!( + writeln!( + out, "Would also clear {} stale usage record{}", plan.stale_record_only, if plan.stale_record_only == 1 { "" } else { "s" } - ); + )?; } if plan.skipped_recent > 0 { - println!( + writeln!( + out, "Skipped recent mesh-managed record{}: {}", if plan.skipped_recent == 1 { "" } else { "s" }, plan.skipped_recent - ); + )?; } - println!(); - println!("Apply with:"); - print!(" mesh-llm models cleanup"); + writeln!(out)?; + writeln!(out, "Apply with:")?; + write!(out, " mesh-llm models cleanup")?; if let Some(unused_since) = unused_since { - print!(" --unused-since {}", unused_since); + write!(out, " --unused-since {}", unused_since)?; } - println!(" --yes"); + writeln!(out, " --yes")?; } Ok(()) } @@ -722,7 +753,9 @@ fn render_cleanup_json( plan: &ModelCleanupPlan, result: Option<&ModelCleanupResult>, ) -> Result<()> { - println!( + let mut out = mesh_llm_events::machine_out(); + writeln!( + out, "{}", serde_json::to_string_pretty(&json!({ "hf_cache_dir": mesh_llm_host_runtime::command_support::models::huggingface_hub_cache_dir(), @@ -733,7 +766,7 @@ fn render_cleanup_json( "plan": plan, "result": result, }))? - ); + )?; Ok(()) } diff --git a/crates/mesh-llm/src/commands/plugin_cli.rs b/crates/mesh-llm/src/commands/plugin_cli.rs index 997793790f..3d403a81c6 100644 --- a/crates/mesh-llm/src/commands/plugin_cli.rs +++ b/crates/mesh-llm/src/commands/plugin_cli.rs @@ -203,13 +203,15 @@ fn handle_plugin_cli_result(command: &str, result: plugin::ToolCallResult) -> Re match value { serde_json::Value::Null => Ok(()), serde_json::Value::String(text) => { - print!("{text}"); - std::io::stdout().flush().ok(); + let mut out = mesh_llm_events::console_out(); + write!(out, "{text}")?; + let _ = out.flush(); Ok(()) } serde_json::Value::Object(_) => handle_structured_result(command, value), other => { - println!("{}", serde_json::to_string_pretty(&other)?); + let mut out = mesh_llm_events::console_out(); + writeln!(out, "{}", serde_json::to_string_pretty(&other)?)?; Ok(()) } } @@ -229,12 +231,14 @@ fn handle_structured_result(command: &str, value: serde_json::Value) -> Result<( let response: PluginCliRunResponse = serde_json::from_value(value).context("Decode plugin CLI response")?; if let Some(stderr) = response.stderr { - eprint!("{stderr}"); - std::io::stderr().flush().ok(); + let mut err = mesh_llm_events::console_err(); + write!(err, "{stderr}")?; + let _ = err.flush(); } if let Some(stdout) = response.stdout { - print!("{stdout}"); - std::io::stdout().flush().ok(); + let mut out = mesh_llm_events::console_out(); + write!(out, "{stdout}")?; + let _ = out.flush(); } if let Some(code) = response.exit_code && code != 0 diff --git a/crates/mesh-llm/src/commands/runtime.rs b/crates/mesh-llm/src/commands/runtime.rs index 6d4aad2db3..99d6fd07a5 100644 --- a/crates/mesh-llm/src/commands/runtime.rs +++ b/crates/mesh-llm/src/commands/runtime.rs @@ -1,5 +1,6 @@ use anyhow::{Context, Result, bail}; use serde_json::json; +use std::io::Write; use std::path::Path; use mesh_llm_cli::runtime::RuntimeCommand; @@ -259,11 +260,13 @@ pub(crate) async fn run_control_scan_refresh( ) .await?; if json_output { - println!("{}", serde_json::to_string_pretty(&body)?); + let mut out = mesh_llm_events::machine_out(); + writeln!(out, "{}", serde_json::to_string_pretty(&body)?)?; return Ok(()); } + let mut out = mesh_llm_events::console_out(); for line in control_scan_refresh_lines(&body) { - println!("{line}"); + writeln!(out, "{line}")?; } Ok(()) } @@ -385,19 +388,20 @@ async fn display_runtime_result( let action_inf = if verb == "Loaded" { "load" } else { "unload" }; let is_success = resp.status().is_success(); let body = resp.json::().await.ok(); + let mut err = mesh_llm_events::console_err(); if is_success { for line in runtime_success_lines(model_name, verb, body.as_ref()) { - eprintln!("{line}"); + writeln!(err, "{line}")?; } } else { - eprintln!("โŒ Failed to {action_inf} runtime model"); - eprintln!(); - eprintln!("Model: {model_name}"); + writeln!(err, "โŒ Failed to {action_inf} runtime model")?; + writeln!(err)?; + writeln!(err, "Model: {model_name}")?; let reason = body .as_ref() .and_then(|value| value["error"].as_str().map(str::to_owned)) .unwrap_or_else(|| "unknown error".to_string()); - eprintln!("Reason: {reason}"); + writeln!(err, "Reason: {reason}")?; } Ok(()) } @@ -474,23 +478,25 @@ pub(crate) async fn run_status(port: u16) -> Result<()> { .as_array() .ok_or_else(|| anyhow::anyhow!("Invalid runtime process payload"))?; - println!("โš™๏ธ Runtime"); - println!(); + let mut out = mesh_llm_events::console_out(); + writeln!(out, "โš™๏ธ Runtime")?; + writeln!(out)?; if models.is_empty() { - println!("๐Ÿ“ฆ Models served locally: 0"); - println!(); - println!("No local models are currently being served."); + writeln!(out, "๐Ÿ“ฆ Models served locally: 0")?; + writeln!(out)?; + writeln!(out, "No local models are currently being served.")?; return Ok(()); } - println!("๐Ÿ“ฆ Models served locally: {}", models.len()); - println!(); + writeln!(out, "๐Ÿ“ฆ Models served locally: {}", models.len())?; + writeln!(out)?; - println!( + writeln!( + out, "{:<42} {:<12} {:<8} {:<10} {:<8} {:<6}", "Model", "Instance", "Backend", "State", "Pid", "Port" - ); + )?; for model in models { let name = model["name"].as_str().unwrap_or("unknown"); let instance = model["instance_id"].as_str().unwrap_or("-"); @@ -503,10 +509,11 @@ pub(crate) async fn run_status(port: u16) -> Result<()> { .as_u64() .map(|p| p.to_string()) .unwrap_or_else(|| "-".into()); - println!( + writeln!( + out, "{:<42} {:<12} {:<8} {:<10} {:<8} {:<6}", name, instance, backend, status, pid, port - ); + )?; } Ok(()) @@ -519,12 +526,14 @@ pub(crate) async fn run_control_bootstrap(port: u16, json: bool) -> Result<()> { let payload = fetch_runtime_payload(&client, port, "/api/runtime/control-bootstrap").await?; if json { - println!("{}", serde_json::to_string_pretty(&payload)?); + let mut out = mesh_llm_events::machine_out(); + writeln!(out, "{}", serde_json::to_string_pretty(&payload)?)?; return Ok(()); } + let mut out = mesh_llm_events::console_out(); for line in control_bootstrap_lines(&payload) { - println!("{line}"); + writeln!(out, "{line}")?; } Ok(()) @@ -694,10 +703,12 @@ async fn post_runtime_payload( fn print_control_response(title: &str, body: &serde_json::Value, json_output: bool) -> Result<()> { if !json_output { - println!("๐Ÿ” {title}"); - println!(); + let mut out = mesh_llm_events::console_out(); + writeln!(out, "๐Ÿ” {title}")?; + writeln!(out)?; } - println!("{}", serde_json::to_string_pretty(body)?); + let mut out = mesh_llm_events::machine_out(); + writeln!(out, "{}", serde_json::to_string_pretty(body)?)?; Ok(()) } diff --git a/crates/mesh-llm/src/lib.rs b/crates/mesh-llm/src/lib.rs index 559231d0d1..f718749258 100644 --- a/crates/mesh-llm/src/lib.rs +++ b/crates/mesh-llm/src/lib.rs @@ -1,5 +1,6 @@ #![recursion_limit = "256"] +use std::io::Write; use std::sync::Arc; use std::time::Duration; use std::{ffi::OsString, fmt, path::PathBuf}; @@ -299,7 +300,12 @@ fn maybe_print_binary_help(args: &[OsString]) -> bool { return true; } if let Some(surface) = runtime_surface_help_request(args.iter().cloned()) { - print!("{}", mesh_llm_cli::parser::runtime_surface_help(surface)); + let mut out = mesh_llm_events::console_out(); + let _ = write!( + out, + "{}", + mesh_llm_cli::parser::runtime_surface_help(surface) + ); return true; } if args.iter().any(|arg| arg == "--help-advanced") { @@ -381,9 +387,11 @@ where } fn print_advanced_help() { - print!("{}", advanced_help_text()); - print!("{}", mesh_llm_cli::parser::logging_help()); - eprintln!(); + let mut out = mesh_llm_events::console_out(); + let _ = write!(out, "{}", advanced_help_text()); + let _ = write!(out, "{}", mesh_llm_cli::parser::logging_help()); + let mut err = mesh_llm_events::console_err(); + let _ = writeln!(err); } fn advanced_help_text() -> String { diff --git a/crates/mesh-llm/src/main.rs b/crates/mesh-llm/src/main.rs index 0a969f933e..17507247f1 100644 --- a/crates/mesh-llm/src/main.rs +++ b/crates/mesh-llm/src/main.rs @@ -1,5 +1,7 @@ #![recursion_limit = "256"] +use std::io::Write; + /// Default MeshLLM application and Tokio worker thread stack size: 8 MB. /// /// The standard Tokio default is 2 MB, which is too small for several spawned @@ -68,18 +70,20 @@ fn run_on_application_thread( } fn prepare_model_download_directories() { + let mut err = mesh_llm_events::console_err(); let prepared = match mesh_llm_host_runtime::command_support::models::prepare_download_directories() { Ok(prepared) => prepared, Err(error) => { - eprintln!( + let _ = writeln!( + err, "โš  Unable to prepare model download directories: {error:#}. Model downloads may fail; set MESH_LLM_DATA_DIR to a writable directory." ); return; } }; for fallback in &prepared.fallbacks { - eprintln!("โš  {fallback}"); + let _ = writeln!(err, "โš  {fallback}"); } // SAFETY: This runs before the Tokio runtime is constructed, while the // process is still single-threaded. diff --git a/tools/xtask/data/console_print_allowlist.json b/tools/xtask/data/console_print_allowlist.json index 5407a7a3b9..978705cada 100644 --- a/tools/xtask/data/console_print_allowlist.json +++ b/tools/xtask/data/console_print_allowlist.json @@ -13,2684 +13,404 @@ "macro_name": "eprintln!" } ], - "crates/mesh-llm-commands/src/agent_cli.rs": [ - { - "line": 174, - "macro_name": "eprintln!" - }, - { - "line": 391, - "macro_name": "eprintln!" - }, - { - "line": 407, - "macro_name": "eprintln!" - }, - { - "line": 437, - "macro_name": "eprintln!" - }, - { - "line": 456, - "macro_name": "eprintln!" - }, - { - "line": 457, - "macro_name": "eprintln!" - }, - { - "line": 542, - "macro_name": "eprintln!" - }, - { - "line": 543, - "macro_name": "eprintln!" - }, - { - "line": 582, - "macro_name": "eprintln!" - }, - { - "line": 588, - "macro_name": "eprintln!" - }, - { - "line": 596, - "macro_name": "eprintln!" - }, - { - "line": 601, - "macro_name": "eprintln!" - }, - { - "line": 611, - "macro_name": "eprintln!" - }, - { - "line": 613, - "macro_name": "eprintln!" - }, - { - "line": 614, - "macro_name": "eprintln!" - }, - { - "line": 615, - "macro_name": "eprintln!" - }, - { - "line": 619, - "macro_name": "eprintln!" - }, - { - "line": 664, - "macro_name": "eprintln!" - }, - { - "line": 678, - "macro_name": "eprintln!" - }, - { - "line": 680, - "macro_name": "eprintln!" - }, - { - "line": 681, - "macro_name": "eprintln!" - }, - { - "line": 682, - "macro_name": "eprintln!" - }, - { - "line": 686, - "macro_name": "eprintln!" - }, - { - "line": 846, - "macro_name": "eprintln!" - }, - { - "line": 911, - "macro_name": "eprintln!" - }, - { - "line": 918, - "macro_name": "eprintln!" - }, - { - "line": 921, - "macro_name": "eprintln!" - }, - { - "line": 962, - "macro_name": "eprintln!" - }, + "crates/mesh-llm-events/src/terminal_progress.rs": [ { - "line": 972, - "macro_name": "eprintln!" + "line": 25, + "macro_name": "eprint!" }, { - "line": 976, - "macro_name": "eprintln!" + "line": 72, + "macro_name": "eprint!" }, { - "line": 1137, - "macro_name": "eprintln!" + "line": 118, + "macro_name": "eprint!" } ], - "crates/mesh-llm-commands/src/auth.rs": [ + "crates/mesh-llm-host-runtime/src/inference/skippy/materialization/package_download.rs": [ { - "line": 344, + "line": 163, "macro_name": "eprintln!" }, { - "line": 345, - "macro_name": "eprintln!" + "line": 207, + "macro_name": "eprint!" }, { - "line": 346, + "line": 211, "macro_name": "eprintln!" }, { - "line": 347, + "line": 316, "macro_name": "eprintln!" }, { - "line": 348, + "line": 321, "macro_name": "eprintln!" }, { - "line": 349, - "macro_name": "eprintln!" + "line": 518, + "macro_name": "eprint!" }, { - "line": 350, + "line": 521, "macro_name": "eprintln!" - }, + } + ], + "crates/mesh-llm-host-runtime/src/models/catalog.rs": [ { - "line": 353, - "macro_name": "eprintln!" + "line": 411, + "macro_name": "eprint!" }, { - "line": 358, + "line": 595, "macro_name": "eprintln!" }, { - "line": 362, + "line": 652, "macro_name": "eprintln!" }, { - "line": 363, + "line": 755, "macro_name": "eprintln!" }, { - "line": 366, + "line": 775, "macro_name": "eprintln!" }, { - "line": 374, + "line": 785, "macro_name": "eprintln!" }, { - "line": 379, + "line": 799, "macro_name": "eprintln!" }, { - "line": 381, - "macro_name": "eprintln!" - }, + "line": 842, + "macro_name": "eprint!" + } + ], + "crates/mesh-llm-host-runtime/src/models/maintenance.rs": [ { - "line": 403, + "line": 29, "macro_name": "eprintln!" }, { - "line": 404, + "line": 30, "macro_name": "eprintln!" }, { - "line": 407, + "line": 62, "macro_name": "eprintln!" }, { - "line": 408, + "line": 63, "macro_name": "eprintln!" }, { - "line": 409, + "line": 64, "macro_name": "eprintln!" }, { - "line": 413, + "line": 65, "macro_name": "eprintln!" }, { - "line": 415, + "line": 76, "macro_name": "eprintln!" }, { - "line": 418, + "line": 77, "macro_name": "eprintln!" }, { - "line": 420, + "line": 78, "macro_name": "eprintln!" }, { - "line": 424, + "line": 79, "macro_name": "eprintln!" }, { - "line": 427, + "line": 80, "macro_name": "eprintln!" }, { - "line": 432, + "line": 81, "macro_name": "eprintln!" }, { - "line": 437, + "line": 84, "macro_name": "eprintln!" }, { - "line": 440, + "line": 88, "macro_name": "eprintln!" }, { - "line": 446, + "line": 94, "macro_name": "eprintln!" }, { - "line": 451, + "line": 95, "macro_name": "eprintln!" }, { - "line": 452, + "line": 96, "macro_name": "eprintln!" }, { - "line": 455, + "line": 97, "macro_name": "eprintln!" }, { - "line": 460, + "line": 100, "macro_name": "eprintln!" }, { - "line": 461, + "line": 101, "macro_name": "eprintln!" }, { - "line": 462, + "line": 102, "macro_name": "eprintln!" }, { - "line": 463, + "line": 104, "macro_name": "eprintln!" }, { - "line": 464, + "line": 117, "macro_name": "eprintln!" }, { - "line": 465, + "line": 139, "macro_name": "eprintln!" }, { - "line": 467, + "line": 140, "macro_name": "eprintln!" }, { - "line": 491, + "line": 141, "macro_name": "eprintln!" }, { - "line": 492, + "line": 142, "macro_name": "eprintln!" }, { - "line": 493, + "line": 143, "macro_name": "eprintln!" }, { - "line": 494, + "line": 147, "macro_name": "eprintln!" }, { - "line": 501, + "line": 157, "macro_name": "eprintln!" }, { - "line": 502, + "line": 350, "macro_name": "eprintln!" }, { - "line": 506, + "line": 354, "macro_name": "eprintln!" }, { - "line": 508, + "line": 355, "macro_name": "eprintln!" }, { - "line": 511, + "line": 368, "macro_name": "eprintln!" }, { - "line": 514, + "line": 376, "macro_name": "eprintln!" }, { - "line": 553, + "line": 381, "macro_name": "eprintln!" }, { - "line": 557, + "line": 383, "macro_name": "eprintln!" - }, + } + ], + "crates/mesh-llm-host-runtime/src/models/resolve/mod.rs": [ { - "line": 558, + "line": 215, "macro_name": "eprintln!" }, { - "line": 559, + "line": 268, "macro_name": "eprintln!" - }, + } + ], + "crates/mesh-llm-host-runtime/src/models/search.rs": [ { - "line": 560, + "line": 253, "macro_name": "eprintln!" - }, + } + ], + "crates/mesh-llm-host-runtime/src/network/discovery.rs": [ { - "line": 607, + "line": 339, "macro_name": "eprintln!" - }, + } + ], + "crates/mesh-llm-host-runtime/src/network/nostr/keys.rs": [ { - "line": 608, + "line": 86, "macro_name": "eprintln!" }, { - "line": 609, + "line": 88, "macro_name": "eprintln!" }, { - "line": 610, + "line": 94, "macro_name": "eprintln!" }, { - "line": 611, + "line": 96, "macro_name": "eprintln!" }, { - "line": 612, + "line": 99, "macro_name": "eprintln!" }, { - "line": 613, + "line": 100, "macro_name": "eprintln!" - }, + } + ], + "crates/mesh-llm-host-runtime/src/network/nostr/publish.rs": [ { - "line": 617, + "line": 130, "macro_name": "eprintln!" }, { - "line": 675, + "line": 240, "macro_name": "eprintln!" }, { - "line": 677, + "line": 341, "macro_name": "eprintln!" }, { - "line": 680, + "line": 399, "macro_name": "eprintln!" }, { - "line": 684, + "line": 409, "macro_name": "eprintln!" }, { - "line": 685, + "line": 454, "macro_name": "eprintln!" }, { - "line": 709, + "line": 465, "macro_name": "eprintln!" }, { - "line": 710, + "line": 637, "macro_name": "eprintln!" }, { - "line": 736, + "line": 646, "macro_name": "eprintln!" - }, + } + ], + "crates/mesh-llm-host-runtime/src/runtime/interactive.rs": [ { - "line": 780, + "line": 212, "macro_name": "eprintln!" }, { - "line": 790, + "line": 226, "macro_name": "eprintln!" - }, + } + ], + "crates/mesh-llm-runtime-install/src/discovery.rs": [ { - "line": 792, + "line": 100, "macro_name": "eprintln!" }, { - "line": 839, + "line": 271, "macro_name": "eprintln!" - }, + } + ], + "crates/mesh-llm-system/src/autoupdate.rs": [ { - "line": 840, + "line": 150, "macro_name": "eprintln!" }, { - "line": 842, + "line": 175, "macro_name": "eprintln!" }, { - "line": 859, + "line": 197, "macro_name": "eprintln!" }, { - "line": 869, + "line": 201, "macro_name": "eprintln!" }, { - "line": 874, + "line": 208, "macro_name": "eprintln!" }, { - "line": 880, + "line": 297, "macro_name": "eprintln!" }, { - "line": 881, + "line": 304, "macro_name": "eprintln!" }, { - "line": 882, + "line": 320, "macro_name": "eprintln!" }, { - "line": 883, + "line": 324, "macro_name": "eprintln!" }, { - "line": 885, + "line": 327, "macro_name": "eprintln!" }, { - "line": 889, + "line": 331, "macro_name": "eprintln!" - }, + } + ], + "crates/mesh-llm-system/src/benchmark_prompts.rs": [ { - "line": 890, + "line": 128, "macro_name": "eprintln!" - }, + } + ], + "crates/mesh-llm-tui/src/terminal_progress.rs": [ { - "line": 894, - "macro_name": "eprintln!" + "line": 25, + "macro_name": "eprint!" }, { - "line": 895, - "macro_name": "eprintln!" + "line": 72, + "macro_name": "eprint!" }, { - "line": 897, - "macro_name": "eprintln!" - }, - { - "line": 901, - "macro_name": "eprintln!" - }, - { - "line": 902, - "macro_name": "eprintln!" - }, - { - "line": 906, - "macro_name": "eprintln!" - }, - { - "line": 907, - "macro_name": "eprintln!" - }, - { - "line": 909, - "macro_name": "eprintln!" - }, - { - "line": 913, - "macro_name": "eprintln!" - }, - { - "line": 914, - "macro_name": "eprintln!" - }, - { - "line": 918, - "macro_name": "eprintln!" - }, - { - "line": 919, - "macro_name": "eprintln!" - }, - { - "line": 921, - "macro_name": "eprintln!" - }, - { - "line": 925, - "macro_name": "eprintln!" - }, - { - "line": 926, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-commands/src/config.rs": [ - { - "line": 360, - "macro_name": "println!" - }, - { - "line": 375, - "macro_name": "println!" - }, - { - "line": 380, - "macro_name": "println!" - }, - { - "line": 381, - "macro_name": "println!" - }, - { - "line": 388, - "macro_name": "println!" - }, - { - "line": 390, - "macro_name": "println!" - }, - { - "line": 404, - "macro_name": "println!" - }, - { - "line": 412, - "macro_name": "println!" - } - ], - "crates/mesh-llm-commands/src/doctor.rs": [ - { - "line": 10, - "macro_name": "println!" - }, - { - "line": 14, - "macro_name": "println!" - } - ], - "crates/mesh-llm-commands/src/gpus.rs": [ - { - "line": 35, - "macro_name": "println!" - }, - { - "line": 57, - "macro_name": "println!" - }, - { - "line": 99, - "macro_name": "println!" - }, - { - "line": 116, - "macro_name": "println!" - }, - { - "line": 117, - "macro_name": "println!" - }, - { - "line": 121, - "macro_name": "println!" - }, - { - "line": 122, - "macro_name": "println!" - }, - { - "line": 248, - "macro_name": "println!" - } - ], - "crates/mesh-llm-commands/src/gpus/tune/benchmark/mod.rs": [ - { - "line": 91, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-commands/src/gpus/tune/benchmark_progress.rs": [ - { - "line": 5, - "macro_name": "eprintln!" - }, - { - "line": 13, - "macro_name": "eprintln!" - }, - { - "line": 24, - "macro_name": "eprintln!" - }, - { - "line": 37, - "macro_name": "eprintln!" - }, - { - "line": 56, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-commands/src/model_package.rs": [ - { - "line": 117, - "macro_name": "eprintln!" - }, - { - "line": 124, - "macro_name": "eprintln!" - }, - { - "line": 147, - "macro_name": "println!" - }, - { - "line": 164, - "macro_name": "eprintln!" - }, - { - "line": 165, - "macro_name": "eprintln!" - }, - { - "line": 166, - "macro_name": "println!" - }, - { - "line": 174, - "macro_name": "eprintln!" - }, - { - "line": 183, - "macro_name": "eprintln!" - }, - { - "line": 184, - "macro_name": "eprintln!" - }, - { - "line": 185, - "macro_name": "eprintln!" - }, - { - "line": 186, - "macro_name": "eprintln!" - }, - { - "line": 189, - "macro_name": "println!" - }, - { - "line": 210, - "macro_name": "eprintln!" - }, - { - "line": 211, - "macro_name": "eprintln!" - }, - { - "line": 212, - "macro_name": "eprintln!" - }, - { - "line": 227, - "macro_name": "eprintln!" - }, - { - "line": 228, - "macro_name": "eprintln!" - }, - { - "line": 229, - "macro_name": "eprintln!" - }, - { - "line": 231, - "macro_name": "eprintln!" - }, - { - "line": 233, - "macro_name": "eprintln!" - }, - { - "line": 234, - "macro_name": "eprintln!" - }, - { - "line": 243, - "macro_name": "eprintln!" - }, - { - "line": 244, - "macro_name": "eprintln!" - }, - { - "line": 252, - "macro_name": "eprintln!" - }, - { - "line": 260, - "macro_name": "eprintln!" - }, - { - "line": 261, - "macro_name": "eprintln!" - }, - { - "line": 271, - "macro_name": "eprintln!" - }, - { - "line": 277, - "macro_name": "eprintln!" - }, - { - "line": 295, - "macro_name": "println!" - }, - { - "line": 308, - "macro_name": "eprintln!" - }, - { - "line": 312, - "macro_name": "eprintln!" - }, - { - "line": 313, - "macro_name": "eprintln!" - }, - { - "line": 315, - "macro_name": "eprintln!" - }, - { - "line": 316, - "macro_name": "eprintln!" - }, - { - "line": 317, - "macro_name": "eprintln!" - }, - { - "line": 342, - "macro_name": "eprintln!" - }, - { - "line": 353, - "macro_name": "eprintln!" - }, - { - "line": 367, - "macro_name": "eprintln!" - }, - { - "line": 378, - "macro_name": "println!" - }, - { - "line": 387, - "macro_name": "eprintln!" - }, - { - "line": 388, - "macro_name": "eprintln!" - }, - { - "line": 390, - "macro_name": "eprintln!" - }, - { - "line": 393, - "macro_name": "eprintln!" - }, - { - "line": 405, - "macro_name": "eprintln!" - }, - { - "line": 406, - "macro_name": "eprintln!" - }, - { - "line": 407, - "macro_name": "eprintln!" - }, - { - "line": 414, - "macro_name": "println!" - }, - { - "line": 416, - "macro_name": "println!" - }, - { - "line": 418, - "macro_name": "eprintln!" - }, - { - "line": 432, - "macro_name": "println!" - }, - { - "line": 441, - "macro_name": "eprintln!" - }, - { - "line": 453, - "macro_name": "println!" - }, - { - "line": 463, - "macro_name": "eprintln!" - }, - { - "line": 467, - "macro_name": "eprintln!" - }, - { - "line": 468, - "macro_name": "eprintln!" - }, - { - "line": 471, - "macro_name": "eprintln!" - }, - { - "line": 486, - "macro_name": "eprintln!" - }, - { - "line": 491, - "macro_name": "eprintln!" - }, - { - "line": 506, - "macro_name": "println!" - }, - { - "line": 508, - "macro_name": "eprintln!" - }, - { - "line": 517, - "macro_name": "eprintln!" - }, - { - "line": 518, - "macro_name": "eprintln!" - }, - { - "line": 523, - "macro_name": "eprintln!" - }, - { - "line": 532, - "macro_name": "eprintln!" - }, - { - "line": 546, - "macro_name": "eprintln!" - }, - { - "line": 554, - "macro_name": "eprintln!" - }, - { - "line": 558, - "macro_name": "eprintln!" - }, - { - "line": 562, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-commands/src/plugin.rs": [ - { - "line": 89, - "macro_name": "eprintln!" - }, - { - "line": 103, - "macro_name": "eprintln!" - }, - { - "line": 115, - "macro_name": "eprintln!" - }, - { - "line": 117, - "macro_name": "eprintln!" - }, - { - "line": 125, - "macro_name": "eprintln!" - }, - { - "line": 132, - "macro_name": "println!" - }, - { - "line": 133, - "macro_name": "println!" - }, - { - "line": 134, - "macro_name": "println!" - }, - { - "line": 135, - "macro_name": "println!" - }, - { - "line": 136, - "macro_name": "println!" - }, - { - "line": 137, - "macro_name": "println!" - }, - { - "line": 138, - "macro_name": "println!" - }, - { - "line": 140, - "macro_name": "println!" - }, - { - "line": 143, - "macro_name": "println!" - }, - { - "line": 146, - "macro_name": "println!" - }, - { - "line": 155, - "macro_name": "println!" - }, - { - "line": 161, - "macro_name": "println!" - }, - { - "line": 195, - "macro_name": "eprintln!" - }, - { - "line": 199, - "macro_name": "println!" - }, - { - "line": 215, - "macro_name": "println!" - }, - { - "line": 222, - "macro_name": "println!" - }, - { - "line": 230, - "macro_name": "println!" - }, - { - "line": 267, - "macro_name": "eprintln!" - }, - { - "line": 269, - "macro_name": "eprintln!" - }, - { - "line": 293, - "macro_name": "eprint!" - }, - { - "line": 320, - "macro_name": "eprintln!" - }, - { - "line": 327, - "macro_name": "eprintln!" - }, - { - "line": 331, - "macro_name": "eprintln!" - }, - { - "line": 335, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-commands/src/runtime_native.rs": [ - { - "line": 69, - "macro_name": "eprintln!" - }, - { - "line": 168, - "macro_name": "eprintln!" - }, - { - "line": 171, - "macro_name": "eprintln!" - }, - { - "line": 216, - "macro_name": "eprintln!" - }, - { - "line": 217, - "macro_name": "eprintln!" - }, - { - "line": 219, - "macro_name": "eprintln!" - }, - { - "line": 222, - "macro_name": "eprintln!" - }, - { - "line": 250, - "macro_name": "eprintln!" - }, - { - "line": 286, - "macro_name": "eprint!" - }, - { - "line": 290, - "macro_name": "eprint!" - }, - { - "line": 298, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-commands/src/runtime_native/formatters.rs": [ - { - "line": 94, - "macro_name": "eprintln!" - }, - { - "line": 96, - "macro_name": "eprintln!" - }, - { - "line": 117, - "macro_name": "eprintln!" - }, - { - "line": 118, - "macro_name": "eprintln!" - }, - { - "line": 125, - "macro_name": "eprintln!" - }, - { - "line": 129, - "macro_name": "eprintln!" - }, - { - "line": 131, - "macro_name": "eprintln!" - }, - { - "line": 142, - "macro_name": "eprintln!" - }, - { - "line": 144, - "macro_name": "eprintln!" - }, - { - "line": 153, - "macro_name": "eprintln!" - }, - { - "line": 155, - "macro_name": "eprintln!" - }, - { - "line": 160, - "macro_name": "eprintln!" - }, - { - "line": 239, - "macro_name": "println!" - }, - { - "line": 252, - "macro_name": "println!" - }, - { - "line": 253, - "macro_name": "println!" - }, - { - "line": 256, - "macro_name": "println!" - }, - { - "line": 264, - "macro_name": "println!" - }, - { - "line": 269, - "macro_name": "println!" - }, - { - "line": 274, - "macro_name": "println!" - }, - { - "line": 277, - "macro_name": "println!" - }, - { - "line": 284, - "macro_name": "println!" - }, - { - "line": 285, - "macro_name": "println!" - }, - { - "line": 288, - "macro_name": "println!" - }, - { - "line": 289, - "macro_name": "println!" - }, - { - "line": 291, - "macro_name": "println!" - }, - { - "line": 295, - "macro_name": "println!" - }, - { - "line": 302, - "macro_name": "eprintln!" - }, - { - "line": 306, - "macro_name": "eprintln!" - }, - { - "line": 307, - "macro_name": "eprintln!" - }, - { - "line": 308, - "macro_name": "eprintln!" - }, - { - "line": 311, - "macro_name": "eprintln!" - }, - { - "line": 312, - "macro_name": "eprintln!" - }, - { - "line": 313, - "macro_name": "eprintln!" - }, - { - "line": 314, - "macro_name": "eprintln!" - }, - { - "line": 318, - "macro_name": "eprintln!" - }, - { - "line": 323, - "macro_name": "println!" - }, - { - "line": 324, - "macro_name": "println!" - }, - { - "line": 325, - "macro_name": "println!" - }, - { - "line": 326, - "macro_name": "println!" - }, - { - "line": 327, - "macro_name": "println!" - }, - { - "line": 328, - "macro_name": "println!" - }, - { - "line": 333, - "macro_name": "println!" - }, - { - "line": 336, - "macro_name": "println!" - }, - { - "line": 339, - "macro_name": "println!" - }, - { - "line": 344, - "macro_name": "println!" - }, - { - "line": 346, - "macro_name": "println!" - }, - { - "line": 347, - "macro_name": "println!" - }, - { - "line": 355, - "macro_name": "println!" - }, - { - "line": 358, - "macro_name": "println!" - }, - { - "line": 360, - "macro_name": "println!" - }, - { - "line": 363, - "macro_name": "println!" - }, - { - "line": 367, - "macro_name": "println!" - }, - { - "line": 370, - "macro_name": "println!" - }, - { - "line": 371, - "macro_name": "println!" - }, - { - "line": 376, - "macro_name": "println!" - }, - { - "line": 377, - "macro_name": "println!" - }, - { - "line": 379, - "macro_name": "println!" - }, - { - "line": 383, - "macro_name": "println!" - }, - { - "line": 384, - "macro_name": "println!" - }, - { - "line": 386, - "macro_name": "println!" - } - ], - "crates/mesh-llm-commands/src/setup/command.rs": [ - { - "line": 163, - "macro_name": "eprintln!" - }, - { - "line": 165, - "macro_name": "eprintln!" - }, - { - "line": 178, - "macro_name": "eprintln!" - }, - { - "line": 196, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-commands/src/setup/summary.rs": [ - { - "line": 14, - "macro_name": "eprintln!" - }, - { - "line": 20, - "macro_name": "eprintln!" - }, - { - "line": 36, - "macro_name": "eprintln!" - }, - { - "line": 42, - "macro_name": "eprintln!" - }, - { - "line": 44, - "macro_name": "eprintln!" - }, - { - "line": 45, - "macro_name": "eprintln!" - }, - { - "line": 46, - "macro_name": "eprintln!" - }, - { - "line": 47, - "macro_name": "eprintln!" - }, - { - "line": 54, - "macro_name": "eprintln!" - }, - { - "line": 55, - "macro_name": "eprintln!" - }, - { - "line": 56, - "macro_name": "eprintln!" - }, - { - "line": 58, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-commands/src/skills.rs": [ - { - "line": 28, - "macro_name": "eprintln!" - }, - { - "line": 83, - "macro_name": "eprintln!" - }, - { - "line": 92, - "macro_name": "println!" - }, - { - "line": 101, - "macro_name": "eprintln!" - }, - { - "line": 104, - "macro_name": "eprintln!" - }, - { - "line": 105, - "macro_name": "eprintln!" - }, - { - "line": 109, - "macro_name": "eprintln!" - }, - { - "line": 115, - "macro_name": "eprintln!" - }, - { - "line": 116, - "macro_name": "eprintln!" - }, - { - "line": 120, - "macro_name": "eprintln!" - }, - { - "line": 129, - "macro_name": "eprintln!" - }, - { - "line": 136, - "macro_name": "eprintln!" - }, - { - "line": 141, - "macro_name": "eprintln!" - }, - { - "line": 180, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-commands/src/terminal.rs": [ - { - "line": 32, - "macro_name": "eprint!" - }, - { - "line": 54, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-commands/src/uninstall.rs": [ - { - "line": 457, - "macro_name": "println!" - }, - { - "line": 461, - "macro_name": "eprintln!" - }, - { - "line": 468, - "macro_name": "println!" - }, - { - "line": 471, - "macro_name": "eprintln!" - }, - { - "line": 473, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-events/src/terminal_progress.rs": [ - { - "line": 25, - "macro_name": "eprint!" - }, - { - "line": 72, - "macro_name": "eprint!" - }, - { - "line": 118, - "macro_name": "eprint!" - } - ], - "crates/mesh-llm-host-runtime/src/inference/skippy/materialization/package_download.rs": [ - { - "line": 163, - "macro_name": "eprintln!" - }, - { - "line": 207, - "macro_name": "eprint!" - }, - { - "line": 211, - "macro_name": "eprintln!" - }, - { - "line": 316, - "macro_name": "eprintln!" - }, - { - "line": 321, - "macro_name": "eprintln!" - }, - { - "line": 518, - "macro_name": "eprint!" - }, - { - "line": 521, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-host-runtime/src/models/catalog.rs": [ - { - "line": 411, - "macro_name": "eprint!" - }, - { - "line": 595, - "macro_name": "eprintln!" - }, - { - "line": 652, - "macro_name": "eprintln!" - }, - { - "line": 755, - "macro_name": "eprintln!" - }, - { - "line": 775, - "macro_name": "eprintln!" - }, - { - "line": 785, - "macro_name": "eprintln!" - }, - { - "line": 799, - "macro_name": "eprintln!" - }, - { - "line": 842, - "macro_name": "eprint!" - } - ], - "crates/mesh-llm-host-runtime/src/models/maintenance.rs": [ - { - "line": 29, - "macro_name": "eprintln!" - }, - { - "line": 30, - "macro_name": "eprintln!" - }, - { - "line": 62, - "macro_name": "eprintln!" - }, - { - "line": 63, - "macro_name": "eprintln!" - }, - { - "line": 64, - "macro_name": "eprintln!" - }, - { - "line": 65, - "macro_name": "eprintln!" - }, - { - "line": 76, - "macro_name": "eprintln!" - }, - { - "line": 77, - "macro_name": "eprintln!" - }, - { - "line": 78, - "macro_name": "eprintln!" - }, - { - "line": 79, - "macro_name": "eprintln!" - }, - { - "line": 80, - "macro_name": "eprintln!" - }, - { - "line": 81, - "macro_name": "eprintln!" - }, - { - "line": 84, - "macro_name": "eprintln!" - }, - { - "line": 88, - "macro_name": "eprintln!" - }, - { - "line": 94, - "macro_name": "eprintln!" - }, - { - "line": 95, - "macro_name": "eprintln!" - }, - { - "line": 96, - "macro_name": "eprintln!" - }, - { - "line": 97, - "macro_name": "eprintln!" - }, - { - "line": 100, - "macro_name": "eprintln!" - }, - { - "line": 101, - "macro_name": "eprintln!" - }, - { - "line": 102, - "macro_name": "eprintln!" - }, - { - "line": 104, - "macro_name": "eprintln!" - }, - { - "line": 117, - "macro_name": "eprintln!" - }, - { - "line": 139, - "macro_name": "eprintln!" - }, - { - "line": 140, - "macro_name": "eprintln!" - }, - { - "line": 141, - "macro_name": "eprintln!" - }, - { - "line": 142, - "macro_name": "eprintln!" - }, - { - "line": 143, - "macro_name": "eprintln!" - }, - { - "line": 147, - "macro_name": "eprintln!" - }, - { - "line": 157, - "macro_name": "eprintln!" - }, - { - "line": 350, - "macro_name": "eprintln!" - }, - { - "line": 354, - "macro_name": "eprintln!" - }, - { - "line": 355, - "macro_name": "eprintln!" - }, - { - "line": 368, - "macro_name": "eprintln!" - }, - { - "line": 376, - "macro_name": "eprintln!" - }, - { - "line": 381, - "macro_name": "eprintln!" - }, - { - "line": 383, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-host-runtime/src/models/resolve/mod.rs": [ - { - "line": 215, - "macro_name": "eprintln!" - }, - { - "line": 268, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-host-runtime/src/models/search.rs": [ - { - "line": 253, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-host-runtime/src/network/discovery.rs": [ - { - "line": 339, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-host-runtime/src/network/nostr/keys.rs": [ - { - "line": 86, - "macro_name": "eprintln!" - }, - { - "line": 88, - "macro_name": "eprintln!" - }, - { - "line": 94, - "macro_name": "eprintln!" - }, - { - "line": 96, - "macro_name": "eprintln!" - }, - { - "line": 99, - "macro_name": "eprintln!" - }, - { - "line": 100, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-host-runtime/src/network/nostr/publish.rs": [ - { - "line": 130, - "macro_name": "eprintln!" - }, - { - "line": 240, - "macro_name": "eprintln!" - }, - { - "line": 341, - "macro_name": "eprintln!" - }, - { - "line": 399, - "macro_name": "eprintln!" - }, - { - "line": 409, - "macro_name": "eprintln!" - }, - { - "line": 454, - "macro_name": "eprintln!" - }, - { - "line": 465, - "macro_name": "eprintln!" - }, - { - "line": 637, - "macro_name": "eprintln!" - }, - { - "line": 646, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-host-runtime/src/runtime/interactive.rs": [ - { - "line": 212, - "macro_name": "eprintln!" - }, - { - "line": 226, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-runtime-install/src/discovery.rs": [ - { - "line": 100, - "macro_name": "eprintln!" - }, - { - "line": 271, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-system/src/autoupdate.rs": [ - { - "line": 150, - "macro_name": "eprintln!" - }, - { - "line": 175, - "macro_name": "eprintln!" - }, - { - "line": 197, - "macro_name": "eprintln!" - }, - { - "line": 201, - "macro_name": "eprintln!" - }, - { - "line": 208, - "macro_name": "eprintln!" - }, - { - "line": 297, - "macro_name": "eprintln!" - }, - { - "line": 304, - "macro_name": "eprintln!" - }, - { - "line": 320, - "macro_name": "eprintln!" - }, - { - "line": 324, - "macro_name": "eprintln!" - }, - { - "line": 327, - "macro_name": "eprintln!" - }, - { - "line": 331, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-system/src/benchmark_prompts.rs": [ - { - "line": 128, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm-tui/src/terminal_progress.rs": [ - { - "line": 25, - "macro_name": "eprint!" - }, - { - "line": 72, - "macro_name": "eprint!" - }, - { - "line": 118, - "macro_name": "eprint!" - } - ], - "crates/mesh-llm/src/commands/discover.rs": [ - { - "line": 48, - "macro_name": "eprintln!" - }, - { - "line": 52, - "macro_name": "eprintln!" - }, - { - "line": 58, - "macro_name": "eprintln!" - }, - { - "line": 69, - "macro_name": "eprintln!" - }, - { - "line": 88, - "macro_name": "eprintln!" - }, - { - "line": 103, - "macro_name": "eprintln!" - }, - { - "line": 105, - "macro_name": "eprintln!" - }, - { - "line": 106, - "macro_name": "eprintln!" - }, - { - "line": 111, - "macro_name": "eprintln!" - }, - { - "line": 112, - "macro_name": "eprintln!" - }, - { - "line": 113, - "macro_name": "eprintln!" - }, - { - "line": 114, - "macro_name": "println!" - }, - { - "line": 116, - "macro_name": "eprintln!" - }, - { - "line": 117, - "macro_name": "eprintln!" - }, - { - "line": 118, - "macro_name": "eprintln!" - }, - { - "line": 119, - "macro_name": "eprintln!" - }, - { - "line": 131, - "macro_name": "eprintln!" - }, - { - "line": 143, - "macro_name": "eprintln!" - }, - { - "line": 145, - "macro_name": "eprintln!" - }, - { - "line": 146, - "macro_name": "eprintln!" - }, - { - "line": 151, - "macro_name": "eprintln!" - }, - { - "line": 164, - "macro_name": "eprintln!" - }, - { - "line": 172, - "macro_name": "eprintln!" - }, - { - "line": 177, - "macro_name": "eprintln!" - }, - { - "line": 180, - "macro_name": "eprintln!" - }, - { - "line": 182, - "macro_name": "eprintln!" - }, - { - "line": 187, - "macro_name": "println!" - }, - { - "line": 189, - "macro_name": "eprintln!" - }, - { - "line": 190, - "macro_name": "eprintln!" - }, - { - "line": 193, - "macro_name": "eprintln!" - }, - { - "line": 194, - "macro_name": "eprintln!" - }, - { - "line": 195, - "macro_name": "eprintln!" - }, - { - "line": 206, - "macro_name": "eprintln!" - }, - { - "line": 222, - "macro_name": "eprintln!" - }, - { - "line": 228, - "macro_name": "eprintln!" - }, - { - "line": 231, - "macro_name": "eprintln!" - }, - { - "line": 243, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm/src/commands/doctor.rs": [ - { - "line": 82, - "macro_name": "println!" - }, - { - "line": 85, - "macro_name": "println!" - }, - { - "line": 88, - "macro_name": "println!" - }, - { - "line": 89, - "macro_name": "println!" - }, - { - "line": 91, - "macro_name": "println!" - } - ], - "crates/mesh-llm/src/commands/download.rs": [ - { - "line": 28, - "macro_name": "eprintln!" - }, - { - "line": 34, - "macro_name": "eprintln!" - }, - { - "line": 35, - "macro_name": "eprintln!" - }, - { - "line": 41, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm/src/commands/models/formatters.rs": [ - { - "line": 148, - "macro_name": "println!" - } - ], - "crates/mesh-llm/src/commands/models/formatters_console.rs": [ - { - "line": 189, - "macro_name": "eprintln!" - }, - { - "line": 242, - "macro_name": "eprintln!" - }, - { - "line": 377, - "macro_name": "println!" - }, - { - "line": 378, - "macro_name": "println!" - }, - { - "line": 463, - "macro_name": "println!" - }, - { - "line": 465, - "macro_name": "println!" - }, - { - "line": 468, - "macro_name": "println!" - }, - { - "line": 470, - "macro_name": "println!" - }, - { - "line": 471, - "macro_name": "println!" - }, - { - "line": 472, - "macro_name": "println!" - }, - { - "line": 473, - "macro_name": "println!" - }, - { - "line": 475, - "macro_name": "println!" - }, - { - "line": 477, - "macro_name": "println!" - }, - { - "line": 481, - "macro_name": "println!" - }, - { - "line": 484, - "macro_name": "println!" - }, - { - "line": 486, - "macro_name": "println!" - }, - { - "line": 487, - "macro_name": "println!" - }, - { - "line": 489, - "macro_name": "println!" - }, - { - "line": 492, - "macro_name": "println!" - }, - { - "line": 495, - "macro_name": "println!" - }, - { - "line": 498, - "macro_name": "println!" - }, - { - "line": 500, - "macro_name": "println!" - }, - { - "line": 502, - "macro_name": "println!" - }, - { - "line": 504, - "macro_name": "println!" - }, - { - "line": 510, - "macro_name": "println!" - }, - { - "line": 511, - "macro_name": "println!" - }, - { - "line": 544, - "macro_name": "print!" - }, - { - "line": 551, - "macro_name": "println!" - }, - { - "line": 552, - "macro_name": "println!" - }, - { - "line": 554, - "macro_name": "println!" - }, - { - "line": 557, - "macro_name": "println!" - }, - { - "line": 558, - "macro_name": "println!" - }, - { - "line": 559, - "macro_name": "println!" - }, - { - "line": 574, - "macro_name": "println!" - }, - { - "line": 575, - "macro_name": "println!" - }, - { - "line": 576, - "macro_name": "println!" - }, - { - "line": 577, - "macro_name": "println!" - }, - { - "line": 586, - "macro_name": "println!" - }, - { - "line": 587, - "macro_name": "println!" - }, - { - "line": 588, - "macro_name": "println!" - }, - { - "line": 590, - "macro_name": "println!" - }, - { - "line": 592, - "macro_name": "println!" - }, - { - "line": 595, - "macro_name": "println!" - }, - { - "line": 597, - "macro_name": "println!" - }, - { - "line": 603, - "macro_name": "println!" - }, - { - "line": 605, - "macro_name": "println!" - }, - { - "line": 607, - "macro_name": "println!" - }, - { - "line": 612, - "macro_name": "println!" - }, - { - "line": 616, - "macro_name": "println!" - }, - { - "line": 617, - "macro_name": "println!" - }, - { - "line": 619, - "macro_name": "println!" - }, - { - "line": 625, - "macro_name": "println!" - }, - { - "line": 626, - "macro_name": "println!" - }, - { - "line": 631, - "macro_name": "println!" - }, - { - "line": 632, - "macro_name": "println!" - }, - { - "line": 633, - "macro_name": "println!" - }, - { - "line": 635, - "macro_name": "println!" - }, - { - "line": 637, - "macro_name": "println!" - }, - { - "line": 638, - "macro_name": "println!" - }, - { - "line": 642, - "macro_name": "println!" - }, - { - "line": 643, - "macro_name": "println!" - }, - { - "line": 644, - "macro_name": "println!" - } - ], - "crates/mesh-llm/src/commands/models/mod.rs": [ - { - "line": 120, - "macro_name": "eprintln!" - }, - { - "line": 175, - "macro_name": "println!" - }, - { - "line": 177, - "macro_name": "println!" - }, - { - "line": 181, - "macro_name": "println!" - }, - { - "line": 182, - "macro_name": "println!" - }, - { - "line": 183, - "macro_name": "println!" - }, - { - "line": 184, - "macro_name": "println!" - }, - { - "line": 186, - "macro_name": "println!" - }, - { - "line": 268, - "macro_name": "eprintln!" - }, - { - "line": 272, - "macro_name": "eprintln!" - }, - { - "line": 281, - "macro_name": "eprintln!" - }, - { - "line": 308, - "macro_name": "eprintln!" - }, - { - "line": 315, - "macro_name": "eprintln!" - }, - { - "line": 339, - "macro_name": "eprintln!" - }, - { - "line": 342, - "macro_name": "eprintln!" - }, - { - "line": 384, - "macro_name": "eprintln!" - }, - { - "line": 562, - "macro_name": "println!" - }, - { - "line": 571, - "macro_name": "println!" - }, - { - "line": 572, - "macro_name": "println!" - }, - { - "line": 573, - "macro_name": "println!" - }, - { - "line": 574, - "macro_name": "println!" - }, - { - "line": 582, - "macro_name": "println!" - }, - { - "line": 591, - "macro_name": "println!" - }, - { - "line": 592, - "macro_name": "println!" - }, - { - "line": 629, - "macro_name": "println!" - }, - { - "line": 631, - "macro_name": "println!" - }, - { - "line": 633, - "macro_name": "println!" - }, - { - "line": 637, - "macro_name": "println!" - }, - { - "line": 638, - "macro_name": "println!" - }, - { - "line": 640, - "macro_name": "println!" - }, - { - "line": 642, - "macro_name": "println!" - }, - { - "line": 645, - "macro_name": "println!" - }, - { - "line": 648, - "macro_name": "println!" - }, - { - "line": 650, - "macro_name": "println!" - }, - { - "line": 652, - "macro_name": "println!" - }, - { - "line": 660, - "macro_name": "println!" - }, - { - "line": 662, - "macro_name": "println!" - }, - { - "line": 664, - "macro_name": "println!" - }, - { - "line": 666, - "macro_name": "println!" - }, - { - "line": 668, - "macro_name": "println!" - }, - { - "line": 672, - "macro_name": "println!" - }, - { - "line": 677, - "macro_name": "println!" - }, - { - "line": 678, - "macro_name": "println!" - }, - { - "line": 679, - "macro_name": "println!" - }, - { - "line": 683, - "macro_name": "println!" - }, - { - "line": 684, - "macro_name": "println!" - }, - { - "line": 689, - "macro_name": "println!" - }, - { - "line": 696, - "macro_name": "println!" - }, - { - "line": 703, - "macro_name": "println!" - }, - { - "line": 709, - "macro_name": "println!" - }, - { - "line": 710, - "macro_name": "println!" - }, - { - "line": 711, - "macro_name": "print!" - }, - { - "line": 713, - "macro_name": "print!" - }, - { - "line": 715, - "macro_name": "println!" - }, - { - "line": 725, - "macro_name": "println!" - } - ], - "crates/mesh-llm/src/commands/plugin_cli.rs": [ - { - "line": 206, - "macro_name": "print!" - }, - { - "line": 212, - "macro_name": "println!" - }, - { - "line": 232, + "line": 118, "macro_name": "eprint!" - }, - { - "line": 236, - "macro_name": "print!" - } - ], - "crates/mesh-llm/src/commands/runtime.rs": [ - { - "line": 262, - "macro_name": "println!" - }, - { - "line": 266, - "macro_name": "println!" - }, - { - "line": 390, - "macro_name": "eprintln!" - }, - { - "line": 393, - "macro_name": "eprintln!" - }, - { - "line": 394, - "macro_name": "eprintln!" - }, - { - "line": 395, - "macro_name": "eprintln!" - }, - { - "line": 400, - "macro_name": "eprintln!" - }, - { - "line": 477, - "macro_name": "println!" - }, - { - "line": 478, - "macro_name": "println!" - }, - { - "line": 481, - "macro_name": "println!" - }, - { - "line": 482, - "macro_name": "println!" - }, - { - "line": 483, - "macro_name": "println!" - }, - { - "line": 487, - "macro_name": "println!" - }, - { - "line": 488, - "macro_name": "println!" - }, - { - "line": 490, - "macro_name": "println!" - }, - { - "line": 506, - "macro_name": "println!" - }, - { - "line": 522, - "macro_name": "println!" - }, - { - "line": 527, - "macro_name": "println!" - }, - { - "line": 697, - "macro_name": "println!" - }, - { - "line": 698, - "macro_name": "println!" - }, - { - "line": 700, - "macro_name": "println!" - } - ], - "crates/mesh-llm/src/lib.rs": [ - { - "line": 302, - "macro_name": "print!" - }, - { - "line": 384, - "macro_name": "print!" - }, - { - "line": 385, - "macro_name": "print!" - }, - { - "line": 386, - "macro_name": "eprintln!" - } - ], - "crates/mesh-llm/src/main.rs": [ - { - "line": 75, - "macro_name": "eprintln!" - }, - { - "line": 82, - "macro_name": "eprintln!" } ], "crates/mesh-native-serving-plugin-host/src/lib.rs": [ From dd9b3a008f6f390486c490289f77c0b65b2411ec Mon Sep 17 00:00:00 2001 From: scama Date: Tue, 15 Sep 2026 15:12:47 +1000 Subject: [PATCH 3/3] fix(cli): make console output modes deterministic --- crates/mesh-llm-commands/src/model_package.rs | 21 +++++++++++++++++++ .../src/command_lifecycle/tests.rs | 3 +++ crates/mesh-llm-events/src/console.rs | 3 +++ crates/mesh-llm-events/src/lib.rs | 3 +++ 4 files changed, 30 insertions(+) diff --git a/crates/mesh-llm-commands/src/model_package.rs b/crates/mesh-llm-commands/src/model_package.rs index 5031c03fda..8af3a72d29 100644 --- a/crates/mesh-llm-commands/src/model_package.rs +++ b/crates/mesh-llm-commands/src/model_package.rs @@ -74,6 +74,7 @@ pub async fn dispatch_model_package(args: ModelPrepareArgs<'_>) -> Result<()> { } // โ”€โ”€ Submit flow (source ref required) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + validate_submit_output_options(follow, json)?; let source_ref = source_repo.context( "Source repo is required for job submission.\n\ Usage: mesh-llm models package :", @@ -234,6 +235,16 @@ pub async fn dispatch_model_package(args: ModelPrepareArgs<'_>) -> Result<()> { Ok(()) } +fn validate_submit_output_options(follow: bool, json: bool) -> Result<()> { + if follow && json { + bail!( + "--json cannot be combined with --follow: use the submitted job ID with \ + `mesh-llm models package --logs --json`" + ); + } + Ok(()) +} + fn print_prepare_job(job: &PrepareJob, perms: &permissions::PermissionCheck) { let mut err = mesh_llm_events::console_err(); let shard_info = model_ref::split_gguf_shard_info(&job.source_file); @@ -728,6 +739,16 @@ fn format_timeout(seconds: u64) -> String { mod tests { use super::*; + #[test] + fn json_submit_rejects_follow_before_starting_a_job() { + let error = validate_submit_output_options(true, true) + .unwrap_err() + .to_string(); + assert!(error.contains("--json cannot be combined with --follow")); + assert!(validate_submit_output_options(false, true).is_ok()); + assert!(validate_submit_output_options(true, false).is_ok()); + } + #[test] fn parse_timeout_hours() { assert_eq!(parse_timeout("3h").unwrap(), 10800); diff --git a/crates/mesh-llm-events/src/command_lifecycle/tests.rs b/crates/mesh-llm-events/src/command_lifecycle/tests.rs index bb7562b763..1ebc1f7f2c 100644 --- a/crates/mesh-llm-events/src/command_lifecycle/tests.rs +++ b/crates/mesh-llm-events/src/command_lifecycle/tests.rs @@ -45,6 +45,9 @@ impl Drop for OutputSinkResetGuard { #[test] fn public_emit_is_silent_unless_verbose_enabled() { + let _sink_lock = crate::OUTPUT_SINK_TEST_LOCK + .lock() + .expect("output sink test lock"); let sink = Arc::new(RecordingSink::new(LogFormat::Pretty)); let _sink_guard = OutputSinkResetGuard; let _verbose_guard = VerboseResetGuard; diff --git a/crates/mesh-llm-events/src/console.rs b/crates/mesh-llm-events/src/console.rs index 1c7d560c94..f8ae14a47c 100644 --- a/crates/mesh-llm-events/src/console.rs +++ b/crates/mesh-llm-events/src/console.rs @@ -178,6 +178,9 @@ mod tests { #[test] fn writers_pass_through_without_an_installed_sink() { + let _sink_lock = crate::OUTPUT_SINK_TEST_LOCK + .lock() + .expect("output sink test lock"); // One-shot CLI commands run before any sink exists; console text must // still reach the terminal there. crate::clear_output_sink(); diff --git a/crates/mesh-llm-events/src/lib.rs b/crates/mesh-llm-events/src/lib.rs index 9271def2ac..ba29786c88 100644 --- a/crates/mesh-llm-events/src/lib.rs +++ b/crates/mesh-llm-events/src/lib.rs @@ -231,6 +231,9 @@ pub trait OutputSink: Send + Sync { static OUTPUT_SINK: OnceLock>>> = OnceLock::new(); +#[cfg(test)] +pub(crate) static OUTPUT_SINK_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + fn output_sink_slot() -> &'static RwLock>> { OUTPUT_SINK.get_or_init(|| RwLock::new(None)) }