use agentd::config::ConfigError;
use agentd::exit;
use serde_json::json;
#[cfg(unix)]
mod interface;
fn main() {
std::process::exit(run());
}
fn run() -> i32 {
let argv: Vec<String> = std::env::args().collect();
if let Some(sub @ ("tui" | "ui")) = argv.get(1).map(String::as_str) {
#[cfg(unix)]
{
let env: Vec<(String, String)> = std::env::vars().collect();
return interface::run(sub, &argv[2..], &env);
}
#[cfg(not(unix))]
{
eprintln!(
"agentd {sub}: the tui/ui passthrough is unix-only; run `agentd-{sub} --endpoint <url>` against a separately started daemon"
);
return exit::USAGE;
}
}
#[cfg(any(feature = "internal-mocks", debug_assertions))]
if argv.get(1).map(String::as_str) == Some("--internal-mock-mcp-http") {
let addr_file = argv
.get(2)
.map(String::as_str)
.unwrap_or("/tmp/agentd-mock-mcp.addr");
let uri = argv.get(3).map(String::as_str).unwrap_or("mock://resource");
let emit = !argv.iter().any(|a| a == "--no-emit");
return agentd::mcp::mock_http::run(addr_file, uri, emit);
}
#[cfg(any(feature = "internal-mocks", debug_assertions))]
if argv.get(1).map(String::as_str) == Some("--internal-mock-llm") {
let addr_file = argv
.get(2)
.map(String::as_str)
.unwrap_or("/tmp/agentd-mock-llm.addr");
let script = argv.get(3).map(String::as_str).unwrap_or("final");
return agentd::intel::mock::run(addr_file, script);
}
if std::env::var_os(agentd::subagent::protocol::SUBAGENT_ENV).is_some() {
return agentd::subagent::control::run();
}
if std::env::var_os(agentd::supervisor::reap::INSTANCE_CHILD_ENV).is_some() {
agentd::supervisor::reap::install_instance_pdeathsig();
}
let env: Vec<(String, String)> = std::env::vars().collect();
run_v2(&argv[1..], &env)
}
fn run_v2(args: &[String], env: &[(String, String)]) -> i32 {
use agentd::config::v2::{self, Ask, Detected};
let fresh = args.iter().any(|a| a == "--fresh");
let prompt_missing = args.iter().any(|a| a == "--prompt-missing");
let mut env_files: Vec<String> = Vec::new();
{
let mut it = args.iter();
while let Some(a) = it.next() {
if let Some(v) = a.strip_prefix("--env=") {
env_files.push(v.to_string());
} else if a == "--env" {
match it.next() {
Some(v) => env_files.push(v.clone()),
None => {
eprintln!("agentd: --env needs a file path");
return exit::USAGE;
}
}
}
}
}
let mut args2: Vec<String> = Vec::new();
{
let mut skip = false;
for a in args {
if skip {
skip = false;
continue;
}
if a == "--env" {
skip = true;
continue;
}
if a == "--fresh" || a == "--prompt-missing" || a.starts_with("--env=") {
continue;
}
args2.push(a.clone());
}
}
let args = args2.as_slice();
let env: Vec<(String, String)> = if env_files.is_empty() {
env.to_vec()
} else {
match agentd::config::envfile::load_files(&env_files) {
Ok(pairs) => {
for (k, v) in pairs {
if std::env::var_os(&k).is_none() {
unsafe { std::env::set_var(&k, &v) };
}
}
std::env::vars().collect()
}
Err(e) => {
eprintln!("agentd: {e}");
return exit::USAGE;
}
}
};
let env = env.as_slice();
if fresh {
agentd::state::request_fresh();
}
if prompt_missing {
agentd::config::prompt::request_prompt_missing();
}
let detected = match v2::probe(args, env) {
Ok(d) => d,
Err(e) => {
eprintln!("{e}");
return exit::USAGE;
}
};
if detected == Detected::V1 {
eprintln!(
"agentd: this configuration uses the flat schema, which agentd does not accept. \
Migrate to `config_version: \"1\"` with sections (agent / intelligence / a2a / workflows); \
see docs/configuration.md."
);
return exit::USAGE;
}
let (loaded, ask) = match v2::load(args, env) {
Ok(x) => x,
Err(ConfigError::Validate(Ok(line))) => {
eprintln!("{line}");
return exit::SUCCESS;
}
Err(ConfigError::Validate(Err(lines))) => {
eprintln!("{lines}");
return exit::USAGE;
}
Err(ConfigError::Usage(s)) => {
eprintln!("{s}");
return exit::USAGE;
}
Err(other) => {
eprintln!("{other:?}");
return exit::USAGE;
}
};
match ask {
Ask::Help => {
print!(
"{}",
v2::help_text().replace(
" -h, --help",
" --fresh start a NEW generation: do not resume prior durable state\n (the previous generation is kept on the store, not deleted)\n -h, --help",
)
);
exit::SUCCESS
}
Ask::Version => {
println!("agentd {}", agentd::VERSION);
exit::SUCCESS
}
Ask::Schema => {
println!(
"{}",
serde_json::to_string_pretty(&v2::schema::schema())
.unwrap_or_else(|_| "{}".to_string())
);
exit::SUCCESS
}
Ask::ContextTemplate => {
println!("{}", agentd::runtime::env::DEFAULT_TEMPLATE);
exit::SUCCESS
}
Ask::WorkflowSchema => {
println!(
"{}",
serde_json::to_string_pretty(&agentd::engine::workflow_schema())
.unwrap_or_else(|_| "{}".to_string())
);
exit::SUCCESS
}
Ask::Validate => {
for w in &loaded.warnings {
eprintln!("{}", json!({"event": "config.warning", "msg": w}));
}
for s in &loaded.settings.mcp.servers {
if s.service.is_some() || !loaded.settings.services.is_empty() {
eprintln!(
"{}",
json!({"event": "config.effective_server", "server": s.name,
"service": s.service, "endpoint": s.endpoint,
"allow": s.allow, "exclude": s.exclude, "tags": s.tags})
);
}
}
eprintln!(
"{}",
json!({"event": "config.valid",
"schema": agentd::config::v2::schema::CONFIG_VERSION,
"files": loaded.files.iter().map(|(p, _)| p.clone()).collect::<Vec<_>>()})
);
exit::SUCCESS
}
Ask::Capabilities => {
println!(
"{}",
serde_json::to_string_pretty(&agentd::runtime::capabilities(&loaded))
.unwrap_or_else(|_| "{}".to_string())
);
exit::SUCCESS
}
Ask::Login(target) => {
#[cfg(feature = "oauth")]
{
match agentd::auth::login::run_cli(
&loaded.settings,
&target,
std::time::Duration::from_secs(30),
) {
Ok(msg) => {
println!("{msg}");
exit::SUCCESS
}
Err(e) => {
eprintln!("agentd: login failed: {e}");
exit::USAGE
}
}
}
#[cfg(not(feature = "oauth"))]
{
let _ = ⌖
eprintln!("agentd: --login requires building with --features oauth");
exit::USAGE
}
}
Ask::Logout(target) => {
let target = agentd::auth::canonical_target(&loaded.settings, &target);
let dir = agentd::auth::cache::default_dir();
match agentd::auth::cache::evict_file(&dir, &target) {
Ok(()) => {
println!("logged out of {target}");
exit::SUCCESS
}
Err(e) => {
eprintln!("agentd: logout failed: {e}");
exit::USAGE
}
}
}
Ask::Run => {
agentd::state::record_config_digest(&loaded.settings);
agentd::runtime::run(&loaded, args, env)
}
}
}