use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use std::path::PathBuf;
use appam::agent::{Agent, TomlAgent};
#[derive(Parser)]
#[command(name = "appam")]
#[command(version, about = "AI Agent Framework", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Run {
#[arg(value_name = "AGENT")]
agent: PathBuf,
#[arg(value_name = "PROMPT")]
prompt: Option<String>,
#[arg(short, long)]
model: Option<String>,
},
Serve {
#[arg(short, long, default_value = "agents")]
agents_dir: PathBuf,
#[arg(long, default_value = "0.0.0.0")]
host: String,
#[arg(short, long, default_value_t = 3000)]
port: u16,
},
Validate {
#[arg(value_name = "AGENT")]
agent: PathBuf,
},
List {
#[arg(short, long, default_value = "agents")]
agents_dir: PathBuf,
},
New {
#[arg(value_name = "NAME")]
name: String,
#[arg(short, long, default_value = "agents")]
output: PathBuf,
},
Tracing {
#[arg(short, long, default_value = "logs")]
traces_dir: PathBuf,
#[arg(long, default_value = "127.0.0.1")]
host: String,
#[arg(short, long, default_value_t = 8080)]
port: u16,
},
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
match cli.command {
Commands::Run {
agent,
prompt,
model,
} => run_agent(agent, prompt, model).await?,
Commands::Serve {
agents_dir,
host,
port,
} => serve_api(agents_dir, host, port).await?,
Commands::Validate { agent } => validate_agent(agent)?,
Commands::List { agents_dir } => list_agents(agents_dir)?,
Commands::New { name, output } => create_agent(name, output)?,
Commands::Tracing {
traces_dir,
host,
port,
} => serve_tracing(traces_dir, host, port).await?,
}
Ok(())
}
async fn run_agent(
agent_path: PathBuf,
prompt: Option<String>,
model_override: Option<String>,
) -> Result<()> {
println!("Loading agent from: {}", agent_path.display());
let mut agent = TomlAgent::from_file(&agent_path)
.with_context(|| format!("Failed to load agent: {}", agent_path.display()))?;
if let Some(model) = model_override {
agent = agent.with_model(model);
}
if let Some(user_prompt) = prompt {
println!("Running agent with prompt: {}", user_prompt);
agent.run(&user_prompt).await?;
} else {
println!("Interactive mode not yet implemented. Please provide a prompt.");
println!(
"Usage: appam run {} \"Your prompt here\"",
agent_path.display()
);
}
Ok(())
}
async fn serve_api(agents_dir: PathBuf, host: String, port: u16) -> Result<()> {
use appam::web;
web::serve(host, port, agents_dir).await?;
Ok(())
}
fn validate_agent(agent_path: PathBuf) -> Result<()> {
println!("Validating agent: {}", agent_path.display());
use appam::agent::config::AgentConfig;
use appam::tools::loader::validate_tool_configs;
let config = AgentConfig::from_file(&agent_path)
.with_context(|| format!("Failed to load agent config: {}", agent_path.display()))?;
let base_dir = agent_path
.parent()
.ok_or_else(|| anyhow::anyhow!("Invalid agent path"))?;
config
.validate(base_dir)
.context("Agent validation failed")?;
validate_tool_configs(&config.tools, base_dir).context("Tool validation failed")?;
println!("✓ Agent configuration is valid");
println!(" Name: {}", config.agent.name);
println!(
" Model: {}",
config.agent.model.as_deref().unwrap_or("default")
);
println!(" Tools: {}", config.tools.len());
if let Some(desc) = &config.agent.description {
println!(" Description: {}", desc);
}
Ok(())
}
fn list_agents(agents_dir: PathBuf) -> Result<()> {
if !agents_dir.exists() {
println!("Agents directory not found: {}", agents_dir.display());
println!("Create agents with: appam new <name>");
return Ok(());
}
use appam::agent::config::AgentConfig;
let mut agents = Vec::new();
for entry in std::fs::read_dir(&agents_dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("toml") {
if let Ok(config) = AgentConfig::from_file(&path) {
agents.push((path.clone(), config));
}
}
}
if agents.is_empty() {
println!("No agents found in: {}", agents_dir.display());
println!("Create agents with: appam new <name>");
return Ok(());
}
println!("Available agents in {}:", agents_dir.display());
println!();
for (path, config) in agents {
println!(" • {} ({})", config.agent.name, path.display());
if let Some(desc) = &config.agent.description {
println!(" {}", desc);
}
println!(
" Model: {}",
config.agent.model.as_deref().unwrap_or("default")
);
println!(" Tools: {}", config.tools.len());
println!();
}
Ok(())
}
fn create_agent(name: String, output_dir: PathBuf) -> Result<()> {
use std::fs;
println!("Creating new agent: {}", name);
let agent_dir = output_dir.join(&name);
if agent_dir.exists() {
anyhow::bail!("Agent directory already exists: {}", agent_dir.display());
}
fs::create_dir_all(&agent_dir)?;
fs::create_dir_all(agent_dir.join("tools"))?;
let config_path = agent_dir.join("agent.toml");
fs::write(
&config_path,
format!(
r#"[agent]
name = "{}"
model = "openai/gpt-5"
system_prompt = "prompt.txt"
description = "A new AI agent"
# Add tool configurations here
# [[tools]]
# name = "example"
# schema = "tools/example.json"
# implementation = {{ type = "python", script = "tools/example.py" }}
"#,
name
),
)?;
let prompt_path = agent_dir.join("prompt.txt");
fs::write(
&prompt_path,
format!(
r#"You are {}, an AI assistant with specialized capabilities.
Your role is to help users by leveraging your available tools effectively.
Always:
- Be helpful, accurate, and concise
- Use tools when appropriate to provide factual information
- Explain your reasoning clearly
- Ask clarifying questions if needed
Available tools will be provided in your tool specifications.
"#,
name
),
)?;
println!("✓ Agent created successfully!");
println!();
println!("Agent directory: {}", agent_dir.display());
println!("Next steps:");
println!(
" 1. Edit {} to customize your agent",
prompt_path.file_name().unwrap().to_string_lossy()
);
println!(" 2. Add tools manually in the tools/ directory if needed");
println!(" 3. Update agent.toml with your tool configurations");
println!(" 4. Validate: appam validate {}", config_path.display());
println!(
" 5. Run: appam run {} \"Your prompt\"",
config_path.display()
);
Ok(())
}
async fn serve_tracing(traces_dir: PathBuf, host: String, port: u16) -> Result<()> {
use appam::web;
println!("🔍 Starting Appam Trace Visualizer");
println!("═══════════════════════════════════════════════════════════");
println!();
println!("Traces directory: {}", traces_dir.display());
println!("Server address: http://{}:{}", host, port);
println!();
println!("Open in browser: http://{}:{}", host, port);
println!();
web::serve_tracing(host, port, traces_dir).await?;
Ok(())
}