use std::io::{IsTerminal, Write};
use std::time::Duration;
use crate::config::registry::open_registry;
use crate::domain::launch_blueprint::LaunchTarget;
use crate::launcher::args;
pub struct SessionOptions {
pub suppress_banner: bool,
}
pub fn run_session(
paths: &crate::config::layout::AppPaths,
target: &LaunchTarget,
args: &[String],
env: &[String],
policy: SessionOptions,
mode: Option<&str>,
) -> anyhow::Result<i32> {
let args = args::normalize_claude_args(args);
let config = open_registry(&paths.config_file)?;
let (mut env, cleanup) = super::overlay::prepare_provider_env(target, &args, env, &config)?;
if let Some(mode_name) = mode {
let mode_dir = std::path::Path::new(&paths.modes_dir).join(mode_name);
env.push(format!("CLAUDE_CONFIG_DIR={}", mode_dir.display()));
}
if let Some(home) = dirs::home_dir() {
let global_skills = home.join(".claude").join("skills");
crate::adapters::skill::seeder::install_skills(&global_skills);
if let Some(mode_name) = mode {
let mode_skills = std::path::Path::new(&paths.modes_dir)
.join(mode_name)
.join("skills");
crate::adapters::skill::seeder::install_skills(&mode_skills);
}
}
if let Some(msg) =
crate::adapters::update::check::maybe_message(paths, crate::adapters::version::VALUE)
.ok()
.flatten()
.filter(|_| is_tty_stderr())
{
let _ = writeln!(std::io::stderr(), "{}", msg);
}
if !policy.suppress_banner && is_tty_stdout() {
print!("{}", crate::adapters::ui::output::banner(target, mode));
}
let claude_path = find_claude_cli()?;
let code = exec_claude_session(&claude_path, &args, &env)?;
cleanup();
Ok(code)
}
pub fn launch_session(
paths: &crate::config::layout::AppPaths,
target: &LaunchTarget,
args: &[String],
env: &[String],
options: SessionOptions,
mode: Option<&str>,
) -> anyhow::Result<i32> {
run_session(paths, target, args, env, options, mode)
}
pub fn find_claude_cli() -> anyhow::Result<std::path::PathBuf> {
let current_exe = std::env::current_exe()
.ok()
.and_then(|p| std::fs::canonicalize(&p).ok());
for candidate in which::which_all("claude").into_iter().flatten() {
let resolved = std::fs::canonicalize(&candidate).unwrap_or_else(|_| candidate.clone());
if current_exe.as_ref().is_some_and(|exe| resolved == *exe) {
continue;
}
return Ok(candidate);
}
if let Some(home) = dirs::home_dir() {
let fallbacks = [
home.join(".local").join("bin").join("claude"),
home.join("bin").join("claude"),
];
for candidate in &fallbacks {
if candidate.exists() {
let resolved =
std::fs::canonicalize(candidate).unwrap_or_else(|_| candidate.clone());
if current_exe.as_ref().is_none_or(|exe| resolved != *exe) {
return Ok(candidate.clone());
}
}
}
}
Err(anyhow::anyhow!(
"The Claude CLI binary could not be found. Please ensure it is installed and available in your PATH."
))
}
pub fn exec_claude_session(
claude_path: &std::path::Path,
args: &[String],
env: &[String],
) -> anyhow::Result<i32> {
let start = std::time::Instant::now();
let status = spawn_claude(claude_path, args, env)?;
let elapsed = start.elapsed();
if has_resume_flag(args) && elapsed < Duration::from_secs(2) {
let fallback = strip_resume_flags(args);
if fallback.len() != args.len() {
let _ = writeln!(
std::io::stderr(),
"\n WARNING: Session restore failed (known Claude Code bug). Starting fresh...\n"
);
let retry = spawn_claude(claude_path, &fallback, env)?;
return Ok(retry.code().unwrap_or(1));
}
}
Ok(status.code().unwrap_or(1))
}
fn spawn_claude(
claude_path: &std::path::Path,
args: &[String],
env: &[String],
) -> anyhow::Result<std::process::ExitStatus> {
let mut cmd = std::process::Command::new(claude_path);
cmd.args(args);
cmd.stdin(std::process::Stdio::inherit())
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit());
for env_str in env {
if let Some((key, value)) = env_str.split_once('=') {
cmd.env(key, value);
}
}
Ok(cmd.status()?)
}
fn has_resume_flag(args: &[String]) -> bool {
args.iter()
.any(|a| matches!(a.as_str(), "--continue" | "--resume"))
}
fn strip_resume_flags(args: &[String]) -> Vec<String> {
let mut out = Vec::with_capacity(args.len());
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"--continue" => i += 1,
"--resume" => {
i += 1;
if i < args.len() && !args[i].starts_with('-') {
i += 1;
}
}
other => {
out.push(other.to_owned());
i += 1;
}
}
}
out
}
fn is_tty_stderr() -> bool {
std::io::stderr().is_terminal()
}
fn is_tty_stdout() -> bool {
std::io::stdout().is_terminal()
}