mod agent_builder;
mod args;
mod cargo_ai_metadata;
mod commands;
mod config;
mod credentials;
mod infra_api;
mod providers;
mod runtime_definition;
mod schema_version;
mod ui;
mod update_check;
mod web_resources;
use serde::{Deserialize, Serialize};
use std::path::Path;
use std::process;
include!(concat!(env!("OUT_DIR"), "/agent_model.rs"));
#[tokio::main]
async fn main() {
let cargo_ai_home = config::paths::cargo_ai_root();
let cargo_ai_home_preexisting = cargo_ai_home.exists();
let cmd_args = args::build_cli();
let skip_update_check_for_invocation = cmd_args.get_flag("no_update_check");
match credentials::migration::run_phase1_migration() {
Ok(outcome) if outcome.changed() => {
println!(
"✅ Migrated legacy credentials: {} profile token(s), account tokens migrated: {}.",
outcome.migrated_profile_tokens,
if outcome.migrated_account_tokens {
"yes"
} else {
"no"
}
);
}
Ok(_) => {}
Err(error) => {
eprintln!("⚠️ Failed to migrate legacy credentials: {error}");
}
}
let _ = cargo_ai_metadata::persist_current_metadata();
if let Some(notice) = cargo_ai_home_initialization_notice(
cargo_ai_home_preexisting,
cargo_ai_home.exists(),
&cargo_ai_home,
) {
eprintln!("{notice}");
}
let command_succeeded = if let Some(sub_m) = cmd_args.subcommand_matches("version") {
commands::version::run(sub_m).await
} else {
if cmd_args.subcommand_name().is_some() {
update_check::maybe_run_background_check(skip_update_check_for_invocation).await;
}
if let Some(sub_m) = cmd_args.subcommand_matches("run") {
commands::run::run(sub_m).await
} else if let Some(command_succeeded) = try_run_build(&cmd_args) {
command_succeeded
} else if let Some(command_succeeded) = try_run_package(&cmd_args) {
command_succeeded
} else if let Some(command_succeeded) = try_run_hatch(&cmd_args) {
command_succeeded
} else if let Some(sub_m) = cmd_args.subcommand_matches("add") {
commands::add::run(sub_m)
} else if let Some(sub_m) = cmd_args.subcommand_matches("tools") {
commands::tools::run(sub_m)
} else if let Some(sub_m) = cmd_args.subcommand_matches("init") {
commands::init::run(sub_m)
} else if let Some(sub_m) = cmd_args.subcommand_matches("new") {
commands::new::run(sub_m)
} else if let Some(sub_m) = cmd_args.subcommand_matches("account") {
commands::account::run(sub_m).await
} else if let Some(sub_m) = cmd_args.subcommand_matches("auth") {
commands::auth::run(sub_m).await
} else if let Some(sub_m) = cmd_args.subcommand_matches("profile") {
commands::profile::run(sub_m)
} else if let Some(sub_m) = cmd_args.subcommand_matches("credentials") {
commands::credentials::run(sub_m)
} else {
eprintln!("❌ Provide subcommand.");
false
}
};
if !command_succeeded {
process::exit(1);
}
}
#[cfg(feature = "developer-tools")]
fn try_run_build(cmd_args: &clap::ArgMatches) -> Option<bool> {
cmd_args
.subcommand_matches("build")
.map(crate::commands::build::run)
}
#[cfg(not(feature = "developer-tools"))]
fn try_run_build(_cmd_args: &clap::ArgMatches) -> Option<bool> {
None
}
#[cfg(feature = "developer-tools")]
fn try_run_package(cmd_args: &clap::ArgMatches) -> Option<bool> {
cmd_args
.subcommand_matches("package")
.map(crate::commands::package::run)
}
#[cfg(not(feature = "developer-tools"))]
fn try_run_package(_cmd_args: &clap::ArgMatches) -> Option<bool> {
None
}
#[cfg(feature = "developer-tools")]
fn try_run_hatch(cmd_args: &clap::ArgMatches) -> Option<bool> {
cmd_args
.subcommand_matches("hatch")
.map(crate::commands::hatch::run)
}
#[cfg(not(feature = "developer-tools"))]
fn try_run_hatch(_cmd_args: &clap::ArgMatches) -> Option<bool> {
None
}
fn cargo_ai_home_initialization_notice(
home_was_preexisting: bool,
home_exists_now: bool,
home: &Path,
) -> Option<String> {
if home_was_preexisting || !home_exists_now {
return None;
}
Some(format!(
"ℹ️ Initialized Cargo AI Home at {}. Set CARGO_AI_HOME to override this location.",
home.display()
))
}
#[cfg(test)]
mod tests {
use super::cargo_ai_home_initialization_notice;
use std::path::Path;
#[test]
fn prints_notice_when_home_is_created_during_startup() {
let notice =
cargo_ai_home_initialization_notice(false, true, Path::new("/tmp/cargo-ai-home"));
assert_eq!(
notice,
Some(
"ℹ️ Initialized Cargo AI Home at /tmp/cargo-ai-home. Set CARGO_AI_HOME to override this location."
.to_string()
)
);
}
#[test]
fn skips_notice_when_home_already_existed() {
let notice =
cargo_ai_home_initialization_notice(true, true, Path::new("/tmp/cargo-ai-home"));
assert_eq!(notice, None);
}
#[test]
fn skips_notice_when_home_still_does_not_exist() {
let notice =
cargo_ai_home_initialization_notice(false, false, Path::new("/tmp/cargo-ai-home"));
assert_eq!(notice, None);
}
}