use std::io::Write;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, clap::ValueEnum)]
pub enum HumanJsonFormat {
#[default]
Human,
Json,
}
impl HumanJsonFormat {
pub fn resolve(format: Option<Self>, json_flag: bool) -> (Self, bool) {
let resolved = if json_flag {
Self::Json
} else {
format.unwrap_or(Self::Human)
};
(resolved, resolved == Self::Json)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, clap::ValueEnum)]
pub enum HumanJsonSarifFormat {
#[default]
Human,
Json,
Sarif,
}
impl HumanJsonSarifFormat {
pub fn resolve(format: Option<Self>, json_flag: bool, sarif_flag: bool) -> (Self, bool, bool) {
let resolved = if json_flag {
Self::Json
} else if sarif_flag {
Self::Sarif
} else {
format.unwrap_or(Self::Human)
};
(resolved, resolved == Self::Json, resolved == Self::Sarif)
}
}
pub fn suggest_closest<'a>(
query: &str,
candidates: &[&'a str],
max_distance: usize,
) -> Option<&'a str> {
candidates
.iter()
.map(|c| (*c, tirith_core::util::levenshtein(query, c)))
.filter(|(_, d)| *d <= max_distance)
.min_by_key(|(_, d)| *d)
.map(|(c, _)| c)
}
pub fn confirm(prompt: &str, yes: bool) -> bool {
if yes {
return true;
}
if !is_terminal::is_terminal(std::io::stderr()) {
eprintln!("tirith: skipping prompt (not a TTY — use --yes to auto-approve)");
return false;
}
eprint!("{prompt} [y/N] ");
let _ = std::io::stderr().flush();
let mut input = String::new();
match std::io::stdin().read_line(&mut input) {
Ok(_) => matches!(input.trim(), "y" | "Y" | "yes" | "Yes"),
Err(e) => {
eprintln!("tirith: could not read confirmation input: {e}");
false
}
}
}
pub mod audit;
pub mod check;
pub mod checkpoint;
pub mod completions;
pub mod daemon;
pub mod diff;
pub mod doctor;
pub mod explain;
pub mod gateway;
pub mod hook_event;
pub mod init;
pub mod last_trigger;
pub mod license_cmd;
pub mod manpage;
pub mod mcp_server;
pub mod paste;
pub mod policy;
pub mod receipt;
pub mod scan;
pub mod score;
pub mod threatdb_cmd;
pub mod trust;
pub mod warnings;
pub mod why;
#[cfg(unix)]
pub mod fetch;
#[cfg(unix)]
pub mod run;
pub mod setup;
#[cfg(test)]
pub(crate) mod test_harness;
pub fn resolve_tirith_on_path() -> Vec<std::path::PathBuf> {
let output = {
#[cfg(unix)]
{
std::process::Command::new("sh")
.args(["-c", "which -a tirith 2>/dev/null"])
.output()
}
#[cfg(not(unix))]
{
std::process::Command::new("where.exe")
.arg("tirith")
.output()
}
};
let output = match output {
Ok(o) if o.status.success() => o,
_ => return Vec::new(),
};
String::from_utf8_lossy(&output.stdout)
.lines()
.filter(|l| !l.is_empty())
.map(std::path::PathBuf::from)
.collect()
}
pub fn find_shadow_binaries() -> Vec<String> {
let our_canonical = std::env::current_exe()
.ok()
.and_then(|p| p.canonicalize().ok());
let mut seen = std::collections::HashSet::new();
let mut shadows = Vec::new();
for path in resolve_tirith_on_path() {
let canonical = path.canonicalize().ok();
if let (Some(ours), Some(ref theirs)) = (&our_canonical, &canonical) {
if ours == theirs {
continue;
}
}
let key = canonical
.map(|c| c.display().to_string())
.unwrap_or_else(|| path.display().to_string());
if seen.insert(key) {
shadows.push(path.display().to_string());
}
}
shadows
}