#![deny(unsafe_code)]
mod application;
mod generation;
mod infrastructure;
mod integration;
mod protocols;
use anyhow::Context;
use clap::Parser;
use infrastructure::{EmbeddedTemplateExporter, EmbeddedTemplateRepository};
use integration::{ClientParams, McpClientIntegration, McpServerIntegration, ServerParams};
use reqwest::Url;
use std::path::PathBuf;
use tracing::{Level, info};
use tracing_subscriber::EnvFilter;
#[derive(Parser)]
#[command(name = "agenterra")]
#[command(author, version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
pub command: Commands,
}
#[derive(clap::Subcommand, Debug)]
pub enum Commands {
Scaffold {
#[command(subcommand)]
target: TargetCommands,
},
Templates {
#[command(subcommand)]
action: TemplateCommands,
},
}
#[derive(clap::Subcommand, Debug)]
pub enum TargetCommands {
A2a {
#[command(subcommand)]
role: A2aCommands,
},
Acp {
#[command(subcommand)]
role: AcpCommands,
},
Anp {
#[command(subcommand)]
role: AnpCommands,
},
Mcp {
#[command(subcommand)]
role: McpCommands,
},
}
#[derive(clap::Subcommand, Debug)]
pub enum McpCommands {
Server {
#[arg(long, default_value = "agenterra_mcp_server")]
project_name: String,
#[arg(long)]
schema_path: String,
#[arg(long, default_value = "rust")]
template: String,
#[arg(long)]
template_dir: Option<PathBuf>,
#[arg(long)]
output_dir: Option<PathBuf>,
#[arg(long)]
log_file: Option<String>,
#[arg(long)]
port: Option<u16>,
#[arg(long)]
base_url: Option<Url>,
},
Client {
#[arg(long, default_value = "agenterra_mcp_client")]
project_name: String,
#[arg(long, default_value = "rust")]
template: String,
#[arg(long)]
template_dir: Option<PathBuf>,
#[arg(long)]
output_dir: Option<PathBuf>,
},
}
#[derive(clap::Subcommand, Debug)]
pub enum A2aCommands {
Agent {
#[arg(long, default_value = "agenterra_a2a_agent")]
project_name: String,
#[arg(long, default_value = "rust")]
template: String,
#[arg(long)]
template_dir: Option<PathBuf>,
#[arg(long)]
output_dir: Option<PathBuf>,
},
}
#[derive(clap::Subcommand, Debug)]
pub enum AcpCommands {
Server {
#[arg(long, default_value = "agenterra_acp_server")]
project_name: String,
},
}
#[derive(clap::Subcommand, Debug)]
pub enum AnpCommands {
Broker {
#[arg(long, default_value = "agenterra_anp_broker")]
project_name: String,
},
}
#[derive(clap::Subcommand, Debug)]
pub enum TemplateCommands {
List,
Export {
path: PathBuf,
#[arg(long)]
template: Option<String>,
},
Info {
template: String,
},
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env().add_directive(Level::INFO.into()))
.init();
info!("Starting Agenterra CLI");
let cli = Cli::parse();
match &cli.command {
Commands::Scaffold { target } => match target {
TargetCommands::A2a { .. } => {
anyhow::bail!(
"A2A protocol is not yet implemented. Currently only MCP is supported."
);
}
TargetCommands::Acp { .. } => {
anyhow::bail!(
"ACP protocol is not yet implemented. Currently only MCP is supported."
);
}
TargetCommands::Anp { .. } => {
anyhow::bail!(
"ANP protocol is not yet implemented. Currently only MCP is supported."
);
}
TargetCommands::Mcp { role } => handle_mcp_command(role).await?,
},
Commands::Templates { action } => handle_template_command(action).await?,
}
Ok(())
}
async fn handle_mcp_command(role: &McpCommands) -> anyhow::Result<()> {
match role {
McpCommands::Server {
project_name,
schema_path,
template,
template_dir,
output_dir,
log_file,
port,
base_url,
} => {
let params = ServerParams {
project_name: project_name.clone(),
schema_path: schema_path.clone(),
template: template.clone(),
template_dir: template_dir.clone(),
output_dir: output_dir.clone(),
port: *port,
log_file: log_file.clone(),
base_url: base_url.clone(),
};
McpServerIntegration::generate(params)
.await
.context("Failed to generate MCP server")?;
info!("Successfully generated MCP server");
}
McpCommands::Client {
project_name,
template,
template_dir,
output_dir,
} => {
let params = ClientParams {
project_name: project_name.clone(),
template: template.clone(),
template_dir: template_dir.clone(),
output_dir: output_dir.clone(),
};
McpClientIntegration::generate(params)
.await
.context("Failed to generate MCP client")?;
info!("Successfully generated MCP client");
}
}
Ok(())
}
async fn handle_template_command(action: &TemplateCommands) -> anyhow::Result<()> {
match action {
TemplateCommands::List => {
let repository = EmbeddedTemplateRepository::new();
let use_case = application::ListTemplatesUseCase::new(repository);
println!("{}", use_case.execute());
}
TemplateCommands::Export { path, template } => {
let exporter = EmbeddedTemplateExporter::new();
let repository = EmbeddedTemplateRepository::new();
let use_case = application::ExportTemplatesUseCase::new(exporter, repository);
match template {
Some(template_path) => match use_case.execute_single(template_path, path) {
Ok(()) => println!("Exported template {} to {}", template_path, path.display()),
Err(application::ApplicationError::TemplateNotFound(_)) => {
eprintln!("Template not found: {template_path}");
std::process::exit(1);
}
Err(e) => return Err(e.into()),
},
None => {
let count = use_case.execute_all(path)?;
println!("Exported {} templates to {}", count, path.display());
}
}
}
TemplateCommands::Info { template } => {
let repository = EmbeddedTemplateRepository::new();
let discovery = EmbeddedTemplateRepository::new();
let use_case = application::TemplateInfoUseCase::new(repository, discovery);
match use_case.execute(template).await {
Ok(output) => println!("{output}"),
Err(application::ApplicationError::TemplateNotFound(_)) => {
eprintln!("Template not found: {template}");
eprintln!("\nRun 'agenterra templates list' to see available templates.");
std::process::exit(1);
}
Err(e) => return Err(e.into()),
}
}
}
Ok(())
}