#[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())
);
}