use std::path::Path;
use console::style;
use crate::config::{self, get_ai_tool_command, get_ai_tool_resume_command, is_claude_tool};
use crate::constants::{LaunchMethod, MAX_SESSION_NAME_LENGTH};
use crate::error::Result;
use crate::git;
use crate::messages;
use crate::session;
use super::helpers::{resolve_target_strict, resolve_worktree_target};
use super::launchers;
use super::spawn_spec::{self, SpawnSpec};
fn dispatch_launch(
path: &Path,
method: LaunchMethod,
session_name: Option<String>,
cmd: &str,
ai_tool_name: &str,
) -> Result<()> {
match method {
LaunchMethod::Foreground => {
println!(
"{}\n",
style(messages::starting_ai_tool_foreground(ai_tool_name)).cyan()
);
let _session_lock = match crate::operations::lockfile::acquire(path, ai_tool_name) {
Ok(lock) => Some(lock),
Err(err @ crate::operations::lockfile::AcquireError::ForeignLock(_)) => {
return Err(crate::error::CwError::Other(format!(
"{}; exit that session first",
err
)));
}
Err(e) => {
eprintln!(
"{} could not write session lock: {}",
style("warning:").yellow(),
e
);
None
}
};
launchers::foreground::run(path, cmd);
}
LaunchMethod::Detach => {
launchers::detached::run(path, cmd);
println!(
"{} {} detached (survives terminal close)\n",
style("*").green().bold(),
ai_tool_name
);
}
LaunchMethod::ItermWindow => launchers::iterm::launch_window(path, cmd, ai_tool_name)?,
LaunchMethod::ItermTab => launchers::iterm::launch_tab(path, cmd, ai_tool_name)?,
LaunchMethod::ItermPaneH => launchers::iterm::launch_pane(path, cmd, ai_tool_name, true)?,
LaunchMethod::ItermPaneV => launchers::iterm::launch_pane(path, cmd, ai_tool_name, false)?,
LaunchMethod::Tmux => {
let sn = session_name.unwrap_or_else(|| generate_session_name(path));
launchers::tmux::launch_session(path, cmd, ai_tool_name, &sn)?;
}
LaunchMethod::TmuxWindow => launchers::tmux::launch_window(path, cmd, ai_tool_name)?,
LaunchMethod::TmuxPaneH => launchers::tmux::launch_pane(path, cmd, ai_tool_name, true)?,
LaunchMethod::TmuxPaneV => launchers::tmux::launch_pane(path, cmd, ai_tool_name, false)?,
LaunchMethod::Zellij => {
let sn = session_name.unwrap_or_else(|| generate_session_name(path));
launchers::zellij::launch_session(path, cmd, ai_tool_name, &sn)?;
}
LaunchMethod::ZellijTab => launchers::zellij::launch_tab(path, cmd, ai_tool_name)?,
LaunchMethod::ZellijPaneH => launchers::zellij::launch_pane(path, cmd, ai_tool_name, true)?,
LaunchMethod::ZellijPaneV => {
launchers::zellij::launch_pane(path, cmd, ai_tool_name, false)?
}
LaunchMethod::WeztermWindow => launchers::wezterm::launch_window(path, cmd, ai_tool_name)?,
LaunchMethod::WeztermTab => launchers::wezterm::launch_tab(path, cmd, ai_tool_name)?,
LaunchMethod::WeztermTabBg => launchers::wezterm::launch_tab_bg(path, cmd, ai_tool_name)?,
LaunchMethod::WeztermPaneH => {
launchers::wezterm::launch_pane(path, cmd, ai_tool_name, true)?
}
LaunchMethod::WeztermPaneV => {
launchers::wezterm::launch_pane(path, cmd, ai_tool_name, false)?
}
}
Ok(())
}
pub fn launch_ai_tool(path: &Path, resume: bool, term_override: Option<&str>) -> Result<()> {
let (method, session_name) = config::resolve_term_option(term_override, path)?;
let ai_cmd_parts = if resume {
get_ai_tool_resume_command()?
} else if is_claude_tool().unwrap_or(false) && session::claude_native_session_exists(path) {
eprintln!("Found existing Claude session, using --continue");
get_ai_tool_resume_command()?
} else {
get_ai_tool_command()?
};
if ai_cmd_parts.is_empty() {
return Ok(());
}
let ai_tool_name = ai_cmd_parts[0].clone();
if !git::has_command(&ai_tool_name) {
println!(
"{} {} not detected. Install it or update config with 'cw config set ai-tool <tool>'.\n",
style("!").yellow(),
ai_tool_name,
);
return Ok(());
}
let spec = SpawnSpec::new(ai_cmd_parts, path.to_path_buf());
let (cmd, _) = spawn_spec::materialize(&spec)?;
dispatch_launch(path, method, session_name, &cmd, ai_tool_name.as_str())
}
pub fn resume_worktree(worktree: Option<&str>, term_override: Option<&str>) -> Result<()> {
let (worktree_path, branch_name) = if let Some(target) = worktree {
let main_repo = git::get_main_repo_root(None)?;
let strict = resolve_target_strict(&main_repo, target)?;
let branch_name = strict.branch.unwrap_or_else(|| {
strict
.path
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| "(detached)".into())
});
(strict.path, branch_name)
} else {
let resolved = resolve_worktree_target(None, None)?;
(resolved.path, resolved.branch)
};
if worktree.is_some() {
let _ = std::env::set_current_dir(&worktree_path);
println!(
"{}\n",
style(messages::switched_to_worktree(&worktree_path)).dim()
);
}
let has_session =
is_claude_tool().unwrap_or(false) && session::claude_native_session_exists(&worktree_path);
if has_session {
println!(
"{} Found session for branch: {}",
style("*").green(),
style(&branch_name).bold()
);
if let Some(metadata) = session::load_session_metadata(&branch_name) {
println!(" AI tool: {}", style(&metadata.ai_tool).dim());
println!(" Last updated: {}", style(&metadata.updated_at).dim());
}
if let Some(context) = session::load_context(&branch_name) {
println!("\n{}", style("Previous context:").cyan());
println!("{}", style(&context).dim());
}
println!();
} else {
println!(
"{} No previous session found for branch: {}",
style("i").yellow(),
style(&branch_name).bold()
);
println!("{}\n", style("Starting fresh session...").dim());
}
let ai_cmd = if has_session {
get_ai_tool_resume_command()?
} else {
get_ai_tool_command()?
};
if !ai_cmd.is_empty() {
let ai_tool_name = &ai_cmd[0];
let _ = session::save_session_metadata(
&branch_name,
ai_tool_name,
&worktree_path.to_string_lossy(),
);
if has_session {
println!(
"{} {}\n",
style(messages::resuming_ai_tool_in(ai_tool_name)).cyan(),
worktree_path.display()
);
} else {
println!(
"{} {}\n",
style(messages::starting_ai_tool_in(ai_tool_name)).cyan(),
worktree_path.display()
);
}
launch_ai_tool(&worktree_path, has_session, term_override)?;
}
Ok(())
}
pub fn spawn_in_worktree(
worktree_path: &Path,
prompt: Option<&str>,
term_override: Option<&str>,
) -> Result<()> {
let (method, session_name) = config::resolve_term_option(term_override, worktree_path)?;
let ai_cmd_parts = if let Some(p) = prompt {
config::get_ai_tool_merge_command(p)?
} else {
get_ai_tool_command()?
};
if ai_cmd_parts.is_empty() {
return Ok(());
}
let ai_tool_name = ai_cmd_parts[0].clone();
if !git::has_command(&ai_tool_name) {
println!(
"{} {} not detected. Install it or update config with 'cw config set ai-tool <tool>'.\n",
style("!").yellow(),
ai_tool_name,
);
return Ok(());
}
let spec = SpawnSpec::new(ai_cmd_parts, worktree_path.to_path_buf());
let (cmd, _) = spawn_spec::materialize(&spec)?;
dispatch_launch(
worktree_path,
method,
session_name,
&cmd,
ai_tool_name.as_str(),
)
}
fn generate_session_name(path: &Path) -> String {
let config = config::load_config().unwrap_or_default();
let prefix = &config.launch.tmux_session_prefix;
let dir_name = path
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| "worktree".to_string());
let name = format!("{}-{}", prefix, dir_name);
if name.len() > MAX_SESSION_NAME_LENGTH {
name[..MAX_SESSION_NAME_LENGTH].to_string()
} else {
name
}
}