Skip to main content

intent_engine/cli_handlers/
status_command.rs

1use crate::backend::{TaskBackend, WorkspaceBackend};
2use crate::db::models::{NoFocusResponse, StatusResponse, TaskBrief};
3use crate::error::Result;
4
5/// Handle `ie status` command.
6///
7/// Returns `Ok(true)` if a focused task was displayed, `Ok(false)` if no focus.
8/// The caller can add backend-specific logic (e.g. LLM suggestions) after this.
9pub async fn handle_status(
10    task_mgr: &impl TaskBackend,
11    ws_mgr: &impl WorkspaceBackend,
12    task_id: Option<i64>,
13    with_events: bool,
14    format: &str,
15) -> Result<bool> {
16    // Determine which task to show status for
17    let target_task_id = if let Some(id) = task_id {
18        Some(id)
19    } else {
20        let current = ws_mgr.get_current_task(None).await?;
21        current.current_task_id
22    };
23
24    match target_task_id {
25        Some(id) => {
26            let status = task_mgr.get_status(id, with_events).await?;
27
28            if format == "json" {
29                println!("{}", serde_json::to_string_pretty(&status)?);
30            } else {
31                print_status_text(&status);
32            }
33            Ok(true)
34        },
35        None => {
36            let root_tasks = task_mgr.get_root_tasks().await?;
37            let root_briefs: Vec<TaskBrief> = root_tasks.iter().map(TaskBrief::from).collect();
38
39            let response = NoFocusResponse {
40                message: "No focused task. Use 'ie plan' with status:'doing' to start a task."
41                    .to_string(),
42                root_tasks: root_briefs,
43            };
44
45            if format == "json" {
46                println!("{}", serde_json::to_string_pretty(&response)?);
47            } else {
48                println!("  {}", response.message);
49                if !response.root_tasks.is_empty() {
50                    println!("\n  Root tasks:");
51                    for task in &response.root_tasks {
52                        let icon = super::utils::status_icon(&task.status);
53                        println!("   {} #{}: {} [{}]", icon, task.id, task.name, task.status);
54                    }
55                }
56            }
57            Ok(false)
58        },
59    }
60}
61
62/// Print status in text format (shared between SQLite and Neo4j).
63pub fn print_status_text(status: &StatusResponse) {
64    let ft = &status.focused_task;
65    let icon = super::utils::status_icon(&ft.status);
66    println!("{} Task #{}: {}", icon, ft.id, ft.name);
67    println!("   Status: {}", ft.status);
68    if let Some(parent_id) = ft.parent_id {
69        println!("   Parent: #{}", parent_id);
70    }
71    if let Some(priority) = ft.priority {
72        println!("   Priority: {}", priority);
73    }
74    if let Some(spec) = &ft.spec {
75        println!("   Spec: {}", spec);
76    }
77    println!("   Owner: {}", ft.owner);
78    if let Some(ts) = ft.first_todo_at {
79        println!("   First todo: {}", ts.format("%Y-%m-%d %H:%M:%S UTC"));
80    }
81    if let Some(ts) = ft.first_doing_at {
82        println!("   First doing: {}", ts.format("%Y-%m-%d %H:%M:%S UTC"));
83    }
84    if let Some(ts) = ft.first_done_at {
85        println!("   First done: {}", ts.format("%Y-%m-%d %H:%M:%S UTC"));
86    }
87
88    if !status.ancestors.is_empty() {
89        println!("\n  Ancestors ({}):", status.ancestors.len());
90        for ancestor in &status.ancestors {
91            let parent_info = ancestor
92                .parent_id
93                .map(|p| format!(" (parent: #{})", p))
94                .unwrap_or_default();
95            let priority_info = ancestor
96                .priority
97                .map(|p| format!(" [P{}]", p))
98                .unwrap_or_default();
99            println!(
100                "   #{}: {} [{}]{}{}",
101                ancestor.id, ancestor.name, ancestor.status, parent_info, priority_info
102            );
103            if let Some(spec) = &ancestor.spec {
104                println!("      Spec: {}", spec);
105            }
106            println!("      Owner: {}", ancestor.owner);
107            if let Some(ts) = ancestor.first_todo_at {
108                print!("      todo: {} ", ts.format("%m-%d %H:%M:%S"));
109            }
110            if let Some(ts) = ancestor.first_doing_at {
111                print!("doing: {} ", ts.format("%m-%d %H:%M:%S"));
112            }
113            if let Some(ts) = ancestor.first_done_at {
114                print!("done: {}", ts.format("%m-%d %H:%M:%S"));
115            }
116            if ancestor.first_todo_at.is_some()
117                || ancestor.first_doing_at.is_some()
118                || ancestor.first_done_at.is_some()
119            {
120                println!();
121            }
122        }
123    }
124
125    if !status.siblings.is_empty() {
126        println!("\n  Siblings ({}):", status.siblings.len());
127        for sibling in &status.siblings {
128            let parent_info = sibling
129                .parent_id
130                .map(|p| format!(" (parent: #{})", p))
131                .unwrap_or_default();
132            let spec_indicator = if sibling.has_spec { "" } else { " ?" };
133            println!(
134                "   #{}: {} [{}]{}{}",
135                sibling.id, sibling.name, sibling.status, parent_info, spec_indicator
136            );
137        }
138    }
139
140    if !status.descendants.is_empty() {
141        println!("\n  Descendants ({}):", status.descendants.len());
142        for desc in &status.descendants {
143            let parent_info = desc
144                .parent_id
145                .map(|p| format!(" (parent: #{})", p))
146                .unwrap_or_default();
147            let spec_indicator = if desc.has_spec { "" } else { " ?" };
148            println!(
149                "   #{}: {} [{}]{}{}",
150                desc.id, desc.name, desc.status, parent_info, spec_indicator
151            );
152        }
153    }
154
155    if let Some(events) = &status.events {
156        println!("\n  Events ({}):", events.len());
157        for event in events.iter().take(10) {
158            println!(
159                "   #{} [{}] {}: {}",
160                event.id,
161                event.log_type,
162                event.timestamp.format("%Y-%m-%d %H:%M:%S"),
163                event.discussion_data.chars().take(60).collect::<String>()
164            );
165        }
166    }
167}