Skip to main content

claudectl_core/
launch.rs

1use std::path::{Path, PathBuf};
2
3use crate::terminals;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct LaunchRequest {
7    pub cwd_path: PathBuf,
8    pub prompt: Option<String>,
9    pub resume: Option<String>,
10}
11
12impl LaunchRequest {
13    pub fn option_summary(&self) -> String {
14        let mut parts = Vec::new();
15        if let Some(resume) = &self.resume {
16            parts.push(format!("resume {resume}"));
17        }
18        if self.prompt.is_some() {
19            parts.push("prompt set".to_string());
20        }
21
22        if parts.is_empty() {
23            String::new()
24        } else {
25            format!(" ({})", parts.join(", "))
26        }
27    }
28}
29
30pub fn prepare(
31    cwd: &str,
32    prompt: Option<&str>,
33    resume: Option<&str>,
34) -> Result<LaunchRequest, String> {
35    let raw_cwd = if cwd.trim().is_empty() {
36        "."
37    } else {
38        cwd.trim()
39    };
40    let cwd_path = Path::new(raw_cwd)
41        .canonicalize()
42        .map_err(|_| format!("Directory not found: {raw_cwd}"))?;
43
44    if !cwd_path.is_dir() {
45        return Err(format!(
46            "Launch path is not a directory: {}",
47            cwd_path.display()
48        ));
49    }
50
51    let prompt = prompt
52        .filter(|value| !value.trim().is_empty())
53        .map(ToOwned::to_owned);
54    let resume = resume
55        .map(str::trim)
56        .filter(|value| !value.is_empty())
57        .map(ToOwned::to_owned);
58
59    Ok(LaunchRequest {
60        cwd_path,
61        prompt,
62        resume,
63    })
64}
65
66pub fn launch(request: &LaunchRequest) -> Result<String, String> {
67    terminals::launch_session(
68        request.cwd_path.to_string_lossy().as_ref(),
69        request.prompt.as_deref(),
70        request.resume.as_deref(),
71    )
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn prepare_normalizes_optional_fields() {
80        let temp = tempfile::tempdir().unwrap();
81        let request = prepare(
82            temp.path().to_string_lossy().as_ref(),
83            Some("ship it"),
84            Some("  session-123  "),
85        )
86        .unwrap();
87
88        assert_eq!(request.cwd_path, temp.path().canonicalize().unwrap());
89        assert_eq!(request.prompt.as_deref(), Some("ship it"));
90        assert_eq!(request.resume.as_deref(), Some("session-123"));
91    }
92
93    #[test]
94    fn prepare_rejects_missing_directory() {
95        let missing = "/tmp/claudectl-this-path-should-not-exist";
96        let err = prepare(missing, None, None).unwrap_err();
97        assert_eq!(err, format!("Directory not found: {missing}"));
98    }
99
100    #[test]
101    fn prepare_treats_blank_prompt_and_resume_as_none() {
102        let temp = tempfile::tempdir().unwrap();
103        let request = prepare(
104            temp.path().to_string_lossy().as_ref(),
105            Some("   "),
106            Some(""),
107        )
108        .unwrap();
109
110        assert_eq!(request.prompt, None);
111        assert_eq!(request.resume, None);
112    }
113}