cargo-ai 0.2.0

Build lightweight AI agents with Cargo. Powered by Rust. Declared in JSON.
#[allow(dead_code)]
const GENERATED_BY_CARGO_AI_VERSION: &str = __GENERATED_BY_CARGO_AI_VERSION__;
#[allow(dead_code)]
const GENERATED_WITH_TEMPLATE_SCHEMA_VERSION: &str = __GENERATED_WITH_TEMPLATE_SCHEMA_VERSION__;

include!(concat!(env!("OUT_DIR"), "/agent_build_provenance.rs"));

#[allow(dead_code)]
pub fn generated_agent_provenance() -> [(&'static str, &'static str); 2] {
    [
        ("generated_by_cargo_ai_version", GENERATED_BY_CARGO_AI_VERSION),
        (
            "generated_with_template_schema_version",
            GENERATED_WITH_TEMPLATE_SCHEMA_VERSION,
        ),
    ]
}

#[allow(dead_code)]
fn cargo_ai_config_path_for_agent() -> std::path::PathBuf {
    if let Some(cargo_home) = std::env::var_os("CARGO_HOME") {
        return std::path::PathBuf::from(cargo_home).join(".cargo-ai/config.toml");
    }

    if let Some(home_dir) = dirs::home_dir() {
        return home_dir.join(".cargo/.cargo-ai/config.toml");
    }

    std::path::PathBuf::from(".cargo/.cargo-ai/config.toml")
}

#[allow(dead_code)]
fn read_local_cargo_ai_metadata() -> (Option<String>, Option<String>) {
    let config_path = cargo_ai_config_path_for_agent();
    let contents = match std::fs::read_to_string(config_path) {
        Ok(value) => value,
        Err(_) => return (None, None),
    };

    let parsed: toml::Value = match toml::from_str(&contents) {
        Ok(value) => value,
        Err(_) => return (None, None),
    };

    let cargo_ai_metadata = parsed
        .get("cargo_ai_metadata")
        .and_then(|value| value.as_table());

    let cargo_ai_version = cargo_ai_metadata
        .and_then(|table| table.get("cargo_ai_version"))
        .and_then(|value| value.as_str())
        .map(|value| value.trim().to_string())
        .filter(|value| !value.is_empty());

    let template_schema_version = cargo_ai_metadata
        .and_then(|table| table.get("template_schema_version"))
        .and_then(|value| value.as_str())
        .map(|value| value.trim().to_string())
        .filter(|value| !value.is_empty());

    (cargo_ai_version, template_schema_version)
}

#[allow(dead_code)]
fn agent_sync_status(
    generated_by_version: &str,
    generated_template_version: &str,
    local_cargo_ai_version: Option<&str>,
    local_template_version: Option<&str>,
) -> &'static str {
    match (local_cargo_ai_version, local_template_version) {
        (Some(local_cargo_ai), Some(local_template)) => {
            if local_cargo_ai == generated_by_version
                && local_template == generated_template_version
            {
                __SYNC_STATUS_IN_SYNC__
            } else {
                __SYNC_STATUS_OUT_OF_SYNC__
            }
        }
        _ => __SYNC_STATUS_UNKNOWN__,
    }
}

#[allow(dead_code)]
fn generated_agent_definition_json_value() -> serde_json::Value {
    serde_json::from_str(AGENT_EMBEDDED_DEFINITION_JSON).unwrap_or(serde_json::Value::Null)
}

#[allow(dead_code)]
fn generated_agent_inspect_value() -> serde_json::Value {
    serde_json::json!({
        "generated_by_cargo_ai_version": GENERATED_BY_CARGO_AI_VERSION,
        "generated_with_template_schema_version": GENERATED_WITH_TEMPLATE_SCHEMA_VERSION,
        "agent_build_id": AGENT_BUILD_ID,
        "target_triple": AGENT_TARGET_TRIPLE,
        "definition_sha256": AGENT_DEFINITION_SHA256,
        "embedded_definition_json": generated_agent_definition_json_value(),
        "build_timestamp_utc": AGENT_BUILD_TIMESTAMP_UTC
    })
}

#[allow(dead_code)]
fn push_aligned_lines(lines: &mut Vec<String>, title: &str, items: &[(&str, String)]) {
    if items.is_empty() {
        return;
    }

    lines.push(String::new());
    lines.push(title.to_string());

    let label_width = items.iter().map(|(label, _)| label.len()).max().unwrap_or(0);
    for (label, value) in items {
        lines.push(format!("  {label:<width$}  {value}", width = label_width));
    }
}

#[allow(dead_code)]
fn print_agent_version_status() {
    let provenance = generated_agent_provenance();
    let generated_by_version = provenance
        .iter()
        .find(|(key, _)| *key == "generated_by_cargo_ai_version")
        .map(|(_, value)| *value)
        .unwrap_or("unknown");
    let generated_template_version = provenance
        .iter()
        .find(|(key, _)| *key == "generated_with_template_schema_version")
        .map(|(_, value)| *value)
        .unwrap_or("unknown");

    let (local_cargo_ai_version, local_template_version) = read_local_cargo_ai_metadata();
    let status = agent_sync_status(
        generated_by_version,
        generated_template_version,
        local_cargo_ai_version.as_deref(),
        local_template_version.as_deref(),
    );
    let (icon, sync_label, summary) = if status == __SYNC_STATUS_IN_SYNC__ {
        (
            "✓",
            "In sync",
            "Agent provenance matches local Cargo AI metadata.".to_string(),
        )
    } else if status == __SYNC_STATUS_OUT_OF_SYNC__ {
        (
            "!",
            "Out of sync",
            "Agent is out of sync with local Cargo AI metadata. Re-hatch this agent with the latest cargo-ai (use --force to overwrite).".to_string(),
        )
    } else {
        (
            "!",
            "Unknown",
            "Local Cargo AI metadata is unavailable. Run `cargo ai version` on this machine, then re-run this command.".to_string(),
        )
    };

    println!("{icon} Agent version status");
    println!("{summary}");

    let mut lines = Vec::new();
    push_aligned_lines(
        &mut lines,
        "Versions",
        &[
            ("Agent cargo-ai", generated_by_version.to_string()),
            ("Agent template", generated_template_version.to_string()),
            (
                "Local cargo-ai",
                local_cargo_ai_version
                    .as_deref()
                    .unwrap_or("unknown")
                    .to_string(),
            ),
            (
                "Local template",
                local_template_version
                    .as_deref()
                    .unwrap_or("unknown")
                    .to_string(),
            ),
        ],
    );
    push_aligned_lines(
        &mut lines,
        "Status",
        &[("Sync", sync_label.to_string())],
    );

    for line in lines {
        println!("{line}");
    }
}

#[allow(dead_code)]
fn print_agent_inspect(as_json: bool) {
    let inspect_value = generated_agent_inspect_value();

    if as_json {
        println!(
            "{}",
            serde_json::to_string_pretty(&inspect_value).unwrap_or_else(|_| "{}".to_string())
        );
        return;
    }

    println!("✓ Agent inspect");
    println!("Embedded agent provenance and definition.");

    let mut lines = Vec::new();
    push_aligned_lines(
        &mut lines,
        "Build",
        &[
            ("Cargo AI version", GENERATED_BY_CARGO_AI_VERSION.to_string()),
            (
                "Template schema",
                GENERATED_WITH_TEMPLATE_SCHEMA_VERSION.to_string(),
            ),
            ("Build id", AGENT_BUILD_ID.to_string()),
            ("Target", AGENT_TARGET_TRIPLE.to_string()),
            ("Definition SHA", AGENT_DEFINITION_SHA256.to_string()),
            ("Built at", AGENT_BUILD_TIMESTAMP_UTC.to_string()),
        ],
    );

    for line in lines {
        println!("{line}");
    }
    println!();
    println!("Embedded definition");

    let embedded_definition = inspect_value
        .get("embedded_definition_json")
        .cloned()
        .unwrap_or(serde_json::Value::Null);
    println!(
        "{}",
        serde_json::to_string_pretty(&embedded_definition).unwrap_or_else(|_| "null".to_string())
    );
}