agent-tui 1.1.0

CLI tool for AI agents to interact with TUI applications
Documentation
use std::env;
use std::error::Error;
use std::fs;
use std::path::Path;
use std::process::Command;

type BuildResult<T> = Result<T, Box<dyn Error>>;

fn strict_build_metadata(manifest_dir: &Path) -> bool {
    env::var("AGENT_TUI_STRICT_BUILD_METADATA")
        .ok()
        .map(|value| {
            matches!(
                value.trim().to_ascii_lowercase().as_str(),
                "1" | "true" | "yes"
            )
        })
        .unwrap_or(false)
        || (matches!(env::var("PROFILE").ok().as_deref(), Some("release"))
            && has_git_metadata(manifest_dir))
}

fn has_git_metadata(manifest_dir: &Path) -> bool {
    manifest_dir
        .ancestors()
        .any(|ancestor| ancestor.join(".git").exists())
}

fn unknown_metadata(message: &str, manifest_dir: &Path) -> BuildResult<String> {
    if strict_build_metadata(manifest_dir) {
        return Err(format!(
            "{message}; release builds from git checkouts must embed verifiable build metadata"
        )
        .into());
    }

    println!("cargo:warning={message}; embedding 'packaged-source' build metadata");
    Ok("packaged-source".to_string())
}

fn non_empty_env(name: &str) -> Option<String> {
    env::var(name).ok().filter(|value| !value.trim().is_empty())
}

fn git_rev_parse(manifest_dir: &Path) -> Option<String> {
    Command::new("git")
        .args(["rev-parse", "--short=12", "HEAD"])
        .current_dir(manifest_dir)
        .output()
        .ok()
        .and_then(|output| {
            if output.status.success() {
                Some(String::from_utf8_lossy(&output.stdout).trim().to_string())
            } else {
                None
            }
        })
        .filter(|value| !value.is_empty())
}

fn packaged_vcs_sha(manifest_dir: &Path) -> Option<String> {
    let contents = fs::read_to_string(manifest_dir.join(".cargo_vcs_info.json")).ok()?;
    contents.lines().find_map(|line| {
        let trimmed = line.trim();
        let rest = trimmed.strip_prefix("\"sha1\":")?;
        let value = rest.trim().trim_end_matches(',').trim();
        if value.starts_with('"') && value.ends_with('"') && value.len() >= 2 {
            Some(value[1..value.len() - 1].to_string())
        } else {
            None
        }
    })
}

fn main() -> BuildResult<()> {
    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:rerun-if-changed=Cargo.toml");
    println!("cargo:rerun-if-changed=.cargo_vcs_info.json");
    println!("cargo:rerun-if-env-changed=AGENT_TUI_VERSION");
    println!("cargo:rerun-if-env-changed=AGENT_TUI_GIT_SHA");
    println!("cargo:rerun-if-env-changed=AGENT_TUI_STRICT_BUILD_METADATA");
    println!("cargo:rerun-if-env-changed=PROFILE");

    let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| String::new());
    let manifest_dir = Path::new(&manifest_dir);

    let version = non_empty_env("AGENT_TUI_VERSION")
        .or_else(|| non_empty_env("CARGO_PKG_VERSION"))
        .map(Ok)
        .unwrap_or_else(|| {
            unknown_metadata(
                "AGENT_TUI_VERSION and CARGO_PKG_VERSION unavailable",
                manifest_dir,
            )
        })?;

    let git_sha = non_empty_env("AGENT_TUI_GIT_SHA")
        .or_else(|| git_rev_parse(manifest_dir))
        .or_else(|| packaged_vcs_sha(manifest_dir))
        .map(Ok)
        .unwrap_or_else(|| {
            unknown_metadata(
                "AGENT_TUI_GIT_SHA unavailable and git/package metadata lookup failed",
                manifest_dir,
            )
        })?;

    println!("cargo:rustc-env=AGENT_TUI_VERSION={version}");
    println!("cargo:rustc-env=AGENT_TUI_GIT_SHA={git_sha}");
    Ok(())
}