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..8af3a72d29 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; @@ -72,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 :", @@ -113,15 +116,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 +150,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 +166,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 +181,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 +190,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,21 +221,32 @@ 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?; } 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); let shard_str = if let Some(shard) = shard_info { format!(" ({} shards)", shard.total) @@ -224,14 +254,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 +271,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 +281,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 +290,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 +302,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 +324,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 +339,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 +370,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 +380,8 @@ fn print_quant_table(quants: &[DiscoveredQuant]) { } else { format!("{} shards", q.shard_count) }; - eprintln!( + let _ = writeln!( + err, " {:9}, {}", q.name, shard_str, @@ -350,7 +392,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 +410,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 +448,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 +490,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 +547,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 +576,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 +587,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 +602,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 +614,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(()) } } @@ -662,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-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-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 new file mode 100644 index 0000000000..f8ae14a47c --- /dev/null +++ b/crates/mesh-llm-events/src/console.rs @@ -0,0 +1,190 @@ +//! 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() { + 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(); + 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..ba29786c88 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 { @@ -229,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)) } 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": [