tsk/commands/
quick.rs

1use super::Command;
2use crate::context::AppContext;
3use crate::repo_utils::find_repository_root;
4use crate::task::TaskBuilder;
5use crate::task_manager::TaskManager;
6use async_trait::async_trait;
7use std::error::Error;
8use std::path::{Path, PathBuf};
9
10pub struct QuickCommand {
11    pub name: String,
12    pub r#type: String,
13    pub description: Option<String>,
14    pub instructions: Option<String>,
15    pub edit: bool,
16    pub agent: Option<String>,
17    pub timeout: u32,
18    pub tech_stack: Option<String>,
19    pub project: Option<String>,
20}
21
22#[async_trait]
23impl Command for QuickCommand {
24    async fn execute(&self, ctx: &AppContext) -> Result<(), Box<dyn Error>> {
25        println!("Executing quick task: {}", self.name);
26        println!("Type: {}", self.r#type);
27
28        // Find repository root
29        let repo_root = find_repository_root(Path::new("."))?;
30
31        // Create task using TaskBuilder
32        let task = TaskBuilder::new()
33            .repo_root(repo_root.clone())
34            .name(self.name.clone())
35            .task_type(self.r#type.clone())
36            .description(self.description.clone())
37            .instructions_file(self.instructions.as_ref().map(PathBuf::from))
38            .edit(self.edit)
39            .agent(self.agent.clone())
40            .timeout(self.timeout)
41            .tech_stack(self.tech_stack.clone())
42            .project(self.project.clone())
43            .build(ctx)
44            .await?;
45
46        if let Some(ref agent) = self.agent {
47            println!("Agent: {agent}");
48        }
49        println!("Timeout: {} minutes", self.timeout);
50
51        // Update terminal title for the task
52        ctx.terminal_operations()
53            .set_title(&format!("TSK: {}", self.name));
54
55        // Execute the task
56        let task_manager = TaskManager::new(ctx)?;
57        let result = task_manager
58            .execute_queued_task(&task)
59            .await
60            .map_err(|e| e.message);
61
62        // Restore terminal title
63        ctx.terminal_operations().restore_title();
64
65        result?;
66
67        Ok(())
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use crate::test_utils::{NoOpDockerClient, NoOpTskClient};
75    use std::sync::Arc;
76
77    fn create_test_context() -> AppContext {
78        AppContext::builder()
79            .with_docker_client(Arc::new(NoOpDockerClient))
80            .with_tsk_client(Arc::new(NoOpTskClient))
81            .build()
82    }
83
84    #[tokio::test]
85    async fn test_quick_command_validation_no_input() {
86        let cmd = QuickCommand {
87            name: "test".to_string(),
88            r#type: "generic".to_string(),
89            description: None,
90            instructions: None,
91            edit: false,
92            agent: None,
93            timeout: 30,
94            tech_stack: None,
95            project: None,
96        };
97
98        let ctx = create_test_context();
99        let result = cmd.execute(&ctx).await;
100        assert!(result.is_err());
101        assert!(result.unwrap_err().to_string().contains(
102            "Either description or instructions file must be provided, or use edit mode"
103        ));
104    }
105}