use anyhow::{Context, Result};
use clap::Args as ClapArgs;
use std::path::PathBuf;
use crate::commands::common;
use crate::config::Config;
use crate::session::{TmuxManager, WorktreeManager};
use crate::tower::TowerApp;
use crate::utils::path_to_str;
#[derive(ClapArgs)]
pub struct Args {
#[arg(default_value = ".")]
pub project_path: PathBuf,
#[arg(short = 'n', long)]
pub num_experts: Option<u32>,
#[arg(short, long)]
pub config: Option<PathBuf>,
}
pub async fn execute(args: Args) -> Result<()> {
let project_path = args
.project_path
.canonicalize()
.context("Failed to resolve project path")?;
println!("Launching macot session for: {}", project_path.display());
let mut config = Config::load(args.config)?.with_project_path(project_path.clone());
if let Some(n) = args.num_experts {
config = config.with_num_experts(n);
}
let tmux = TmuxManager::from_config(&config);
if tmux.session_exists().await {
println!("Attaching to existing session: {}", config.session_name());
let metadata = tmux.load_session_metadata().await?;
if args.num_experts.is_none() {
match metadata.num_experts {
Some(n) => config = config.with_num_experts(n),
None => {
tracing::warn!("Session metadata missing num_experts; using config default")
}
}
}
println!("Number of experts: {}", config.num_experts());
let worktree_manager = WorktreeManager::resolve(project_path).await?;
let mut app = TowerApp::new(config, worktree_manager);
app.run().await?;
return Ok(());
}
println!("Creating session: {}", config.session_name());
println!("Number of experts: {}", config.num_experts());
let managers = common::init_session(&config, &project_path).await?;
let config_clone = config.clone();
let tmux_clone = managers.tmux.clone();
let claude_clone = managers.claude.clone();
let working_dir = path_to_str(&project_path)?.to_string();
tokio::spawn(async move {
let config = config_clone;
let tmux = tmux_clone;
let claude = claude_clone;
for (i, expert) in config.experts.iter().enumerate() {
let expert_id = i as u32;
let expert_name = expert.name.clone();
let working_dir = working_dir.clone();
let timeout = config.timeouts.agent_ready;
let (instruction_file, agents_file, settings_file) =
match common::prepare_expert_files(&config, expert_id) {
Ok(files) => files,
Err(e) => {
eprintln!("Failed to prepare files for expert {expert_id}: {e}");
continue;
}
};
if let Err(e) = tmux.set_pane_title(expert_id, &expert_name).await {
eprintln!("Failed to set pane title for expert {expert_id}: {e}");
}
if let Err(e) = claude
.launch_claude(
expert_id,
&working_dir,
instruction_file.as_deref(),
agents_file.as_deref(),
settings_file.as_deref(),
)
.await
{
eprintln!("Failed to launch Claude for expert {expert_id}: {e}");
continue;
}
match claude.wait_for_ready(expert_id, timeout).await {
Ok(true) => {
tracing::info!("Expert {} ({}) ready", expert_id, expert_name);
}
Ok(false) => {
tracing::warn!("Expert {} ({}) timeout", expert_id, expert_name);
}
Err(e) => {
tracing::error!("Expert {} ({}) failed: {}", expert_id, expert_name, e);
}
}
}
});
println!("Session infrastructure ready. Launching experts in background...");
let worktree_manager = WorktreeManager::resolve(project_path).await?;
let mut app = TowerApp::new(config, worktree_manager);
app.run().await?;
Ok(())
}