claudectl 0.21.0

Mission control for Claude Code — supervise, budget, orchestrate, and auto-pilot sessions with a local LLM brain
Documentation
use std::path::Path;

fn launch_title(cwd: &str, resume: Option<&str>) -> String {
    if let Some(session_id) = resume.filter(|value| !value.trim().is_empty()) {
        return format!("claude resume {session_id}");
    }

    let project = Path::new(cwd)
        .file_name()
        .and_then(|value| value.to_str())
        .filter(|value| !value.is_empty())
        .unwrap_or("claude");

    format!("claude: {project}")
}

fn build_wsl_command(cwd: &str, prompt: Option<&str>, resume: Option<&str>) -> String {
    let mut parts = vec![
        format!("cd {}", super::shell_escape(cwd)),
        "&&".to_string(),
        "exec".to_string(),
        "claude".to_string(),
    ];
    parts.extend(
        super::build_claude_args(prompt, resume)
            .into_iter()
            .map(|arg| super::shell_escape(&arg)),
    );
    parts.join(" ")
}

fn build_cmd_args(
    cwd: &str,
    prompt: Option<&str>,
    resume: Option<&str>,
    distro: Option<&str>,
) -> Vec<String> {
    let mut args = vec![
        "/c".to_string(),
        "wt.exe".to_string(),
        "-w".to_string(),
        "0".to_string(),
        "new-tab".to_string(),
        "--title".to_string(),
        launch_title(cwd, resume),
        "wsl.exe".to_string(),
    ];

    if let Some(distro_name) = distro.filter(|value| !value.trim().is_empty()) {
        args.push("-d".to_string());
        args.push(distro_name.to_string());
    }

    args.push("bash".to_string());
    args.push("-lc".to_string());
    args.push(build_wsl_command(cwd, prompt, resume));
    args
}

pub fn launch(cwd: &str, prompt: Option<&str>, resume: Option<&str>) -> Result<String, String> {
    let distro = std::env::var("WSL_DISTRO_NAME").ok();
    let output = std::process::Command::new("cmd.exe")
        .args(build_cmd_args(cwd, prompt, resume, distro.as_deref()))
        .output()
        .map_err(|e| format!("cmd.exe /c wt.exe new-tab failed: {e}"))?;

    if output.status.success() {
        Ok("Windows Terminal tab".into())
    } else {
        let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
        if stderr.is_empty() {
            Err(String::from_utf8_lossy(&output.stdout).trim().to_string())
        } else {
            Err(stderr)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn build_wsl_command_shell_escapes_cwd_and_args() {
        let command = build_wsl_command("/tmp/ship it", Some("say 'hi'"), Some("session-7"));

        assert_eq!(
            command,
            "cd '/tmp/ship it' && exec claude '--resume' 'session-7' '-p' 'say '\"'\"'hi'\"'\"''"
        );
    }

    #[test]
    fn build_cmd_args_targets_current_window_and_distro() {
        let args = build_cmd_args("/work/repo", None, None, Some("Ubuntu"));

        assert_eq!(
            args,
            vec![
                "/c",
                "wt.exe",
                "-w",
                "0",
                "new-tab",
                "--title",
                "claude: repo",
                "wsl.exe",
                "-d",
                "Ubuntu",
                "bash",
                "-lc",
                "cd '/work/repo' && exec claude",
            ]
        );
    }

    #[test]
    fn launch_title_prefers_resume_id_when_present() {
        assert_eq!(
            launch_title("/tmp/project", Some("session-123")),
            "claude resume session-123"
        );
    }
}