Files
buzz/crates/buzz-cli/src/commands/pack.rs
Will Pfleger d99ad131f1 refactor: rename sprout backend to buzz (#958)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <wpfleger@block.xyz>
Signed-off-by: Will Pfleger <wpfleger@squareup.com>
Signed-off-by: Will Pfleger <wpfleger96@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1fgdl5qqnh3k3f2xkqrvt7cujalhm623x4s7fdjdj5yrtp5fzjl9qrjpucw <4a1bfa0013bc6d14a8d600d8bf6392efefbd2a26ac3c96c9b2a106b0d12297ca@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub16v54tttfqacx9ycvc3k0ut0npj564ahcuajzy6qjvh57ntmsf4uq4806j2 <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Will Pfleger <wpfleger96@gmail.com>
2026-06-10 19:29:51 -04:00

152 lines
4.9 KiB
Rust

//! `buzz pack` subcommands — local persona pack operations.
//!
//! These commands operate on local pack directories. No relay connection needed.
use std::path::Path;
use crate::error::CliError;
/// Run `buzz pack validate <path>`.
///
/// Calls `validate_pack()` from the persona crate, prints diagnostics,
/// and exits with the appropriate code:
/// - 0: valid (may have warnings)
/// - 1: errors found
pub fn cmd_validate(path: &str) -> Result<(), CliError> {
let pack_dir = Path::new(path);
if !pack_dir.exists() {
return Err(CliError::Usage(format!("path does not exist: {path}")));
}
if !pack_dir.is_dir() {
return Err(CliError::Usage(format!("not a directory: {path}")));
}
let report = buzz_persona::validate::validate_pack(pack_dir);
for diag in &report.diagnostics {
match diag {
buzz_persona::validate::ValidationDiagnostic::Error(msg) => {
eprintln!(" ERROR: {msg}");
}
buzz_persona::validate::ValidationDiagnostic::Warning(msg) => {
eprintln!(" WARN: {msg}");
}
}
}
if report.has_errors() {
return Err(CliError::Usage("Validation failed.".into()));
} else if report.has_warnings() {
println!("Valid (with warnings).");
} else {
println!("Valid.");
}
Ok(())
}
/// Run `buzz pack inspect <path>`.
///
/// Loads and resolves a pack, then pretty-prints a summary of each persona's
/// effective configuration.
pub fn cmd_inspect(path: &str) -> Result<(), CliError> {
let pack_dir = Path::new(path);
if !pack_dir.exists() {
return Err(CliError::Usage(format!("path does not exist: {path}")));
}
if !pack_dir.is_dir() {
return Err(CliError::Usage(format!("not a directory: {path}")));
}
// Resolve the pack — shows fully effective config (post-merge, post-split).
let pack = buzz_persona::resolve::resolve_pack(pack_dir)
.map_err(|e| CliError::Other(format!("failed to resolve pack: {e}")))?;
// Header
println!("Pack: {} ({})", pack.name, pack.id);
println!("Version: {}", pack.version);
println!("Personas: {}", pack.personas.len());
println!();
// Per-persona summary (fully resolved effective config)
for persona in &pack.personas {
println!(" {}", persona.name);
println!(" Display: {}", persona.display_name);
println!(" Description: {}", persona.description);
if let Some(ref llm_provider) = persona.llm_provider {
if let Some(ref model) = persona.model {
println!(" Model: {llm_provider}:{model}");
} else {
println!(" Provider: {llm_provider}");
}
} else if let Some(ref model) = persona.model {
println!(" Model: {model}");
}
if let Some(temp) = persona.temperature {
println!(" Temperature: {temp}");
}
if let Some(ctx) = persona.max_context_tokens {
println!(" Max context tokens: {ctx}");
}
if !persona.subscribe.is_empty() {
println!(" Subscribe: {}", persona.subscribe.join(", "));
}
let rt = &persona.triggers;
let mut parts = Vec::new();
if rt.mentions {
parts.push("mentions".to_string());
}
if !rt.keywords.is_empty() {
parts.push(format!("keywords {:?}", rt.keywords));
}
if rt.all_messages {
parts.push("all_messages".to_string());
}
if !parts.is_empty() {
println!(" Triggers: {}", parts.join(" + "));
}
println!(" Thread replies: {}", persona.thread_replies);
println!(" Broadcast replies: {}", persona.broadcast_replies);
if !persona.mcp_servers.is_empty() {
println!(" MCP servers: {}", persona.mcp_servers.len());
}
if !persona.skills.is_empty() {
println!(" Skills: {}", persona.skills.join(", "));
}
if let Some(ref avatar) = persona.avatar {
println!(" Avatar: {avatar}");
}
let prompt_preview = if persona.system_prompt.chars().count() > 80 {
let truncated: String = persona.system_prompt.chars().take(77).collect();
format!("{truncated}...")
} else {
persona.system_prompt.clone()
};
println!(
" System prompt: {} chars ({})",
persona.system_prompt.len(),
prompt_preview.replace('\n', " ")
);
if !persona.runtime_env_vars.is_empty() {
let env_str: Vec<String> = persona
.runtime_env_vars
.iter()
.map(|(k, v)| format!("{k}={v}"))
.collect();
println!(" Env vars: {}", env_str.join(", "));
}
println!();
}
Ok(())
}