use crate::client::Client;
use crate::config;
use crate::output;
use clap::{Args, Subcommand};
#[derive(Args)]
pub struct AgentArgs {
#[command(subcommand)]
pub command: AgentCommand,
}
#[derive(Subcommand)]
pub enum AgentCommand {
List {
repo: Option<String>,
},
View {
name: String,
#[arg(long)]
repo: Option<String>,
},
Trigger {
name: String,
#[arg(long)]
repo: Option<String>,
#[arg(long)]
prompt: Option<String>,
},
Sessions {
name: String,
#[arg(long)]
repo: Option<String>,
},
Session {
id: String,
#[arg(long)]
repo: Option<String>,
},
Approve {
id: String,
#[arg(long)]
repo: Option<String>,
},
Reject {
id: String,
#[arg(long)]
repo: Option<String>,
},
}
pub async fn run(args: AgentArgs) -> Result<(), Box<dyn std::error::Error>> {
match args.command {
AgentCommand::List { repo } => list(repo.as_deref()).await,
AgentCommand::View { name, repo } => view(repo.as_deref(), &name).await,
AgentCommand::Trigger { name, repo, prompt } => {
trigger(repo.as_deref(), &name, prompt.as_deref()).await
}
AgentCommand::Sessions { name, repo } => sessions(repo.as_deref(), &name).await,
AgentCommand::Session { id, repo } => session(repo.as_deref(), &id).await,
AgentCommand::Approve { id, repo } => approve(repo.as_deref(), &id).await,
AgentCommand::Reject { id, repo } => reject(repo.as_deref(), &id).await,
}
}
async fn list(repo: Option<&str>) -> Result<(), Box<dyn std::error::Error>> {
let client = Client::from_config()?;
let (org, name) = config::resolve_repo(repo)?;
let resp: serde_json::Value = client.get(&format!("/{org}/{name}/agents")).await?;
let agents: Vec<serde_json::Value> = if let Some(arr) = resp.get("agents") {
serde_json::from_value(arr.clone()).unwrap_or_default()
} else if let Some(arr) = resp.get("data") {
serde_json::from_value(arr.clone()).unwrap_or_default()
} else {
serde_json::from_value(resp).unwrap_or_default()
};
output::header(&format!("Agents ({org}/{name})"));
let rows: Vec<Vec<String>> = agents
.iter()
.map(|a| {
vec![
a.get("name")
.and_then(|v| v.as_str())
.unwrap_or("?")
.to_string(),
a.get("description")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
a.get("trigger")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
]
})
.collect();
output::print_table(&["NAME", "DESCRIPTION", "TRIGGER"], &rows);
Ok(())
}
async fn view(repo: Option<&str>, agent_name: &str) -> Result<(), Box<dyn std::error::Error>> {
let client = Client::from_config()?;
let (org, name) = config::resolve_repo(repo)?;
let resp: serde_json::Value = client
.get(&format!("/{org}/{name}/agents/{agent_name}"))
.await?;
let agent = resp
.get("agent")
.or_else(|| resp.get("data"))
.unwrap_or(&resp);
output::header(&format!(
"Agent: {}",
agent
.get("name")
.and_then(|v| v.as_str())
.unwrap_or(agent_name)
));
if let Some(desc) = agent.get("description").and_then(|v| v.as_str()) {
output::detail("Description", desc);
}
if let Some(trigger) = agent.get("trigger").and_then(|v| v.as_str()) {
output::detail("Trigger", trigger);
}
if let Some(model) = agent.get("model").and_then(|v| v.as_str()) {
output::detail("Model", model);
}
Ok(())
}
async fn trigger(
repo: Option<&str>,
agent_name: &str,
prompt: Option<&str>,
) -> Result<(), Box<dyn std::error::Error>> {
let client = Client::from_config()?;
let (org, name) = config::resolve_repo(repo)?;
let body = match prompt {
Some(p) => serde_json::json!({ "prompt": p }),
None => serde_json::json!({}),
};
let resp: serde_json::Value = client
.post(&format!("/{org}/{name}/agents/{agent_name}/trigger"), &body)
.await?;
let session_id = resp
.pointer("/session/id")
.or_else(|| resp.pointer("/data/session_id"))
.or_else(|| resp.get("session_id"))
.and_then(|v| v.as_str())
.unwrap_or("?");
output::success(&format!("Triggered agent {agent_name}"));
output::detail("Session", session_id);
Ok(())
}
async fn sessions(repo: Option<&str>, agent_name: &str) -> Result<(), Box<dyn std::error::Error>> {
let client = Client::from_config()?;
let (org, name) = config::resolve_repo(repo)?;
let resp: serde_json::Value = client
.get(&format!("/{org}/{name}/agents/{agent_name}/sessions"))
.await?;
let sessions: Vec<serde_json::Value> = if let Some(arr) = resp.get("sessions") {
serde_json::from_value(arr.clone()).unwrap_or_default()
} else if let Some(arr) = resp.get("data") {
serde_json::from_value(arr.clone()).unwrap_or_default()
} else {
serde_json::from_value(resp).unwrap_or_default()
};
output::header(&format!("Sessions for agent {agent_name}"));
let rows: Vec<Vec<String>> = sessions
.iter()
.map(|s| {
vec![
s.get("id")
.and_then(|v| v.as_str())
.unwrap_or("?")
.chars()
.take(8)
.collect(),
output::colorize_status(s.get("status").and_then(|v| v.as_str()).unwrap_or("?")),
s.get("inserted_at")
.and_then(|v| v.as_str())
.map(output::format_time)
.unwrap_or_default(),
]
})
.collect();
output::print_table(&["ID", "STATUS", "STARTED"], &rows);
Ok(())
}
async fn session(repo: Option<&str>, id: &str) -> Result<(), Box<dyn std::error::Error>> {
let client = Client::from_config()?;
let (org, name) = config::resolve_repo(repo)?;
let resp: serde_json::Value = client
.get(&format!("/{org}/{name}/agents/sessions/{id}"))
.await?;
let session = resp
.get("session")
.or_else(|| resp.get("data"))
.unwrap_or(&resp);
output::header(&format!("Session {}", &id[..8.min(id.len())]));
if let Some(status) = session.get("status").and_then(|v| v.as_str()) {
output::detail("Status", &output::colorize_status(status));
}
if let Some(agent) = session.get("agent_name").and_then(|v| v.as_str()) {
output::detail("Agent", agent);
}
if let Some(messages) = session.get("messages").and_then(|v| v.as_array()) {
output::header("Messages");
for msg in messages {
let role = msg.get("role").and_then(|v| v.as_str()).unwrap_or("?");
let content = msg.get("content").and_then(|v| v.as_str()).unwrap_or("");
println!(" [{role}] {content}");
}
}
Ok(())
}
async fn approve(repo: Option<&str>, id: &str) -> Result<(), Box<dyn std::error::Error>> {
let client = Client::from_config()?;
let (org, name) = config::resolve_repo(repo)?;
client
.post_empty(&format!("/{org}/{name}/agents/sessions/{id}/approve"))
.await?;
output::success(&format!(
"Approved action for session {}",
&id[..8.min(id.len())]
));
Ok(())
}
async fn reject(repo: Option<&str>, id: &str) -> Result<(), Box<dyn std::error::Error>> {
let client = Client::from_config()?;
let (org, name) = config::resolve_repo(repo)?;
client
.post_empty(&format!("/{org}/{name}/agents/sessions/{id}/reject"))
.await?;
output::success(&format!(
"Rejected action for session {}",
&id[..8.min(id.len())]
));
Ok(())
}