tsk/commands/
list.rs

1use super::Command;
2use crate::context::AppContext;
3use crate::repo_utils::find_repository_root;
4use crate::task::TaskStatus;
5use crate::task_storage::get_task_storage;
6use async_trait::async_trait;
7use std::error::Error;
8use std::path::Path;
9use tabled::settings::Style;
10use tabled::{Table, Tabled};
11
12pub struct ListCommand;
13
14#[derive(Tabled)]
15struct TaskRow {
16    #[tabled(rename = "ID")]
17    id: String,
18    #[tabled(rename = "Name")]
19    name: String,
20    #[tabled(rename = "Type")]
21    task_type: String,
22    #[tabled(rename = "Status")]
23    status: String,
24    #[tabled(rename = "Agent")]
25    agent: String,
26    #[tabled(rename = "Created")]
27    created: String,
28}
29
30#[async_trait]
31impl Command for ListCommand {
32    async fn execute(&self, ctx: &AppContext) -> Result<(), Box<dyn Error>> {
33        let _repo_root = find_repository_root(Path::new("."))?;
34
35        // Try to get tasks from server first
36        let client = ctx.tsk_client();
37        let tasks = if client.is_server_available().await {
38            match client.list_tasks().await {
39                Ok(tasks) => tasks,
40                Err(_) => {
41                    eprintln!("Failed to list tasks via server");
42                    eprintln!("Falling back to direct file read...");
43
44                    // Fall back to direct storage
45                    let storage = get_task_storage(ctx.xdg_directories(), ctx.file_system());
46                    storage
47                        .list_tasks()
48                        .await
49                        .map_err(|e| e as Box<dyn Error>)?
50                }
51            }
52        } else {
53            // Server not available, read directly
54            let storage = get_task_storage(ctx.xdg_directories(), ctx.file_system());
55            storage
56                .list_tasks()
57                .await
58                .map_err(|e| e as Box<dyn Error>)?
59        };
60
61        if tasks.is_empty() {
62            println!("No tasks in queue");
63        } else {
64            let rows: Vec<TaskRow> = tasks
65                .iter()
66                .map(|task| TaskRow {
67                    id: task.id.clone(),
68                    name: task.name.clone(),
69                    task_type: task.task_type.clone(),
70                    status: match &task.status {
71                        TaskStatus::Queued => "QUEUED".to_string(),
72                        TaskStatus::Running => "RUNNING".to_string(),
73                        TaskStatus::Failed => "FAILED".to_string(),
74                        TaskStatus::Complete => "COMPLETE".to_string(),
75                    },
76                    agent: task.agent.clone(),
77                    created: task.created_at.format("%Y-%m-%d %H:%M").to_string(),
78                })
79                .collect();
80
81            let table = Table::new(rows).with(Style::modern()).to_string();
82            println!("{table}");
83
84            // Print summary
85            let queued = tasks
86                .iter()
87                .filter(|t| t.status == TaskStatus::Queued)
88                .count();
89            let running = tasks
90                .iter()
91                .filter(|t| t.status == TaskStatus::Running)
92                .count();
93            let complete = tasks
94                .iter()
95                .filter(|t| t.status == TaskStatus::Complete)
96                .count();
97            let failed = tasks
98                .iter()
99                .filter(|t| t.status == TaskStatus::Failed)
100                .count();
101
102            println!(
103                "\nSummary: {queued} queued, {running} running, {complete} complete, {failed} failed"
104            );
105        }
106
107        Ok(())
108    }
109}