tempo_cli/cli/
commands.rs

1use super::{Cli, Commands, ProjectAction, SessionAction, TagAction, ConfigAction, GoalAction, EstimateAction, BranchAction, TemplateAction, WorkspaceAction, CalendarAction, IssueAction, ClientAction};
2use crate::utils::ipc::{IpcClient, IpcMessage, IpcResponse, get_socket_path, is_daemon_running};
3use crate::db::queries::{ProjectQueries, SessionQueries, TagQueries, SessionEditQueries};
4use crate::db::advanced_queries::{GoalQueries, GitBranchQueries, TimeEstimateQueries, InsightQueries, TemplateQueries, WorkspaceQueries};
5use crate::db::{Database, get_database_path, get_connection, get_pool_stats};
6use crate::models::{Project, Tag, Goal, TimeEstimate, ProjectTemplate, Workspace};
7use crate::utils::paths::{canonicalize_path, detect_project_name, get_git_hash, is_git_repository, validate_project_path};
8use crate::utils::validation::{validate_project_name, validate_project_description};
9use crate::utils::config::{load_config, save_config};
10use crate::cli::reports::ReportGenerator;
11use anyhow::{Result, Context};
12use std::env;
13use std::path::PathBuf;
14use std::process::{Command, Stdio};
15use chrono::{Utc, TimeZone};
16
17use crate::ui::dashboard::Dashboard;
18use crate::ui::timer::InteractiveTimer;
19use crate::ui::history::SessionHistoryBrowser;
20use crossterm::{execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}};
21use ratatui::{backend::CrosstermBackend, Terminal};
22use std::io;
23use tokio::runtime::Handle;
24
25
26pub async fn handle_command(cli: Cli) -> Result<()> {
27    match cli.command {
28        Commands::Start => {
29            start_daemon().await
30        }
31        
32        Commands::Stop => {
33            stop_daemon().await
34        }
35        
36        Commands::Restart => {
37            restart_daemon().await
38        }
39        
40        Commands::Status => {
41            status_daemon().await
42        }
43        
44        Commands::Init { name, path, description } => {
45            init_project(name, path, description).await
46        }
47        
48        Commands::List { all, tag } => {
49            list_projects(all, tag).await
50        }
51        
52        Commands::Report { project, from, to, format, group } => {
53            generate_report(project, from, to, format, group).await
54        }
55        
56        Commands::Project { action } => {
57            handle_project_action(action).await
58        }
59        
60        Commands::Session { action } => {
61            handle_session_action(action).await
62        }
63        
64        Commands::Tag { action } => {
65            handle_tag_action(action).await
66        }
67        
68        Commands::Config { action } => {
69            handle_config_action(action).await
70        }
71        
72        Commands::Dashboard => {
73            launch_dashboard().await
74        }
75        
76        Commands::Tui => {
77            launch_dashboard().await
78        }
79        
80        Commands::Timer => {
81            launch_timer().await
82        }
83
84        Commands::History => {
85            launch_history().await
86        }
87
88        Commands::Goal { action } => {
89            handle_goal_action(action).await
90        }
91
92        Commands::Insights { period, project } => {
93            show_insights(period, project).await
94        }
95
96        Commands::Summary { period, from } => {
97            show_summary(period, from).await
98        }
99
100        Commands::Compare { projects, from, to } => {
101            compare_projects(projects, from, to).await
102        }
103
104        Commands::PoolStats => {
105            show_pool_stats().await
106        }
107
108        Commands::Estimate { action } => {
109            handle_estimate_action(action).await
110        }
111
112        Commands::Branch { action } => {
113            handle_branch_action(action).await
114        }
115
116        Commands::Template { action } => {
117            handle_template_action(action).await
118        }
119
120        Commands::Workspace { action } => {
121            handle_workspace_action(action).await
122        }
123
124        Commands::Calendar { action } => {
125            handle_calendar_action(action).await
126        }
127
128        Commands::Issue { action } => {
129            handle_issue_action(action).await
130        }
131
132        Commands::Client { action } => {
133            handle_client_action(action).await
134        }
135
136        Commands::Completions { shell } => {
137            Cli::generate_completions(shell);
138            Ok(())
139        }
140    }
141}
142
143async fn handle_project_action(action: ProjectAction) -> Result<()> {
144    match action {
145        ProjectAction::Archive { project } => {
146            archive_project(project).await
147        }
148        
149        ProjectAction::Unarchive { project } => {
150            unarchive_project(project).await
151        }
152        
153        ProjectAction::UpdatePath { project, path } => {
154            update_project_path(project, path).await
155        }
156        
157        ProjectAction::AddTag { project, tag } => {
158            add_tag_to_project(project, tag).await
159        }
160        
161        ProjectAction::RemoveTag { project, tag } => {
162            remove_tag_from_project(project, tag).await
163        }
164    }
165}
166
167async fn handle_session_action(action: SessionAction) -> Result<()> {
168    match action {
169        SessionAction::Start { project, context } => {
170            start_session(project, context).await
171        }
172        
173        SessionAction::Stop => {
174            stop_session().await
175        }
176        
177        SessionAction::Pause => {
178            pause_session().await
179        }
180        
181        SessionAction::Resume => {
182            resume_session().await
183        }
184        
185        SessionAction::Current => {
186            current_session().await
187        }
188        
189        SessionAction::List { limit, project } => {
190            list_sessions(limit, project).await
191        }
192        
193        SessionAction::Edit { id, start, end, project, reason } => {
194            edit_session(id, start, end, project, reason).await
195        }
196        
197        SessionAction::Delete { id, force } => {
198            delete_session(id, force).await
199        }
200        
201        SessionAction::Merge { session_ids, project, notes } => {
202            merge_sessions(session_ids, project, notes).await
203        }
204        
205        SessionAction::Split { session_id, split_times, notes } => {
206            split_session(session_id, split_times, notes).await
207        }
208    }
209}
210
211async fn handle_tag_action(action: TagAction) -> Result<()> {
212    match action {
213        TagAction::Create { name, color, description } => {
214            create_tag(name, color, description).await
215        }
216        
217        TagAction::List => {
218            list_tags().await
219        }
220        
221        TagAction::Delete { name } => {
222            delete_tag(name).await
223        }
224    }
225}
226
227async fn handle_config_action(action: ConfigAction) -> Result<()> {
228    match action {
229        ConfigAction::Show => {
230            show_config().await
231        }
232        
233        ConfigAction::Set { key, value } => {
234            set_config(key, value).await
235        }
236        
237        ConfigAction::Get { key } => {
238            get_config(key).await
239        }
240        
241        ConfigAction::Reset => {
242            reset_config().await
243        }
244    }
245}
246
247// Daemon control functions
248async fn start_daemon() -> Result<()> {
249    if is_daemon_running() {
250        println!("Daemon is already running");
251        return Ok(());
252    }
253
254    println!("Starting tempo daemon...");
255    
256    let current_exe = std::env::current_exe()?;
257    let daemon_exe = current_exe.with_file_name("tempo-daemon");
258    
259    if !daemon_exe.exists() {
260        return Err(anyhow::anyhow!("tempo-daemon executable not found at {:?}", daemon_exe));
261    }
262
263    let mut cmd = Command::new(&daemon_exe);
264    cmd.stdout(Stdio::null())
265       .stderr(Stdio::null())
266       .stdin(Stdio::null());
267
268    #[cfg(unix)]
269    {
270        use std::os::unix::process::CommandExt;
271        cmd.process_group(0);
272    }
273
274    let child = cmd.spawn()?;
275    
276    // Wait a moment for daemon to start
277    tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
278    
279    if is_daemon_running() {
280        println!("Daemon started successfully (PID: {})", child.id());
281        Ok(())
282    } else {
283        Err(anyhow::anyhow!("Failed to start daemon"))
284    }
285}
286
287async fn stop_daemon() -> Result<()> {
288    if !is_daemon_running() {
289        println!("Daemon is not running");
290        return Ok(());
291    }
292
293    println!("Stopping tempo daemon...");
294    
295    // Try to connect and send shutdown message
296    if let Ok(socket_path) = get_socket_path() {
297        if let Ok(mut client) = IpcClient::connect(&socket_path).await {
298            match client.send_message(&IpcMessage::Shutdown).await {
299                Ok(_) => {
300                    println!("Daemon stopped successfully");
301                    return Ok(());
302                }
303                Err(e) => {
304                    eprintln!("Failed to send shutdown message: {}", e);
305                }
306            }
307        }
308    }
309
310    // Fallback: kill via PID file
311    if let Ok(Some(pid)) = crate::utils::ipc::read_pid_file() {
312        #[cfg(unix)]
313        {
314            let result = Command::new("kill")
315                .arg(pid.to_string())
316                .output();
317            
318            match result {
319                Ok(_) => println!("Daemon stopped via kill signal"),
320                Err(e) => eprintln!("Failed to kill daemon: {}", e),
321            }
322        }
323        
324        #[cfg(windows)]
325        {
326            let result = Command::new("taskkill")
327                .args(&["/PID", &pid.to_string(), "/F"])
328                .output();
329            
330            match result {
331                Ok(_) => println!("Daemon stopped via taskkill"),
332                Err(e) => eprintln!("Failed to kill daemon: {}", e),
333            }
334        }
335    }
336
337    Ok(())
338}
339
340async fn restart_daemon() -> Result<()> {
341    println!("Restarting tempo daemon...");
342    stop_daemon().await?;
343    tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
344    start_daemon().await
345}
346
347async fn status_daemon() -> Result<()> {
348    if !is_daemon_running() {
349        print_daemon_not_running();
350        return Ok(());
351    }
352
353    if let Ok(socket_path) = get_socket_path() {
354        match IpcClient::connect(&socket_path).await {
355            Ok(mut client) => {
356                match client.send_message(&IpcMessage::GetStatus).await {
357                    Ok(IpcResponse::Status { daemon_running: _, active_session, uptime }) => {
358                        print_daemon_status(uptime, active_session.as_ref());
359                    }
360                    Ok(IpcResponse::Pong) => {
361                        print_daemon_status(0, None); // Minimal response
362                    }
363                    Ok(other) => {
364                        println!("Daemon is running (unexpected response: {:?})", other);
365                    }
366                    Err(e) => {
367                        println!("Daemon is running but not responding: {}", e);
368                    }
369                }
370            }
371            Err(e) => {
372                println!("Daemon appears to be running but cannot connect: {}", e);
373            }
374        }
375    } else {
376        println!("Cannot determine socket path");
377    }
378
379    Ok(())
380}
381
382// Session control functions
383async fn start_session(project: Option<String>, context: Option<String>) -> Result<()> {
384    if !is_daemon_running() {
385        println!("Daemon is not running. Start it with 'tempo start'");
386        return Ok(());
387    }
388
389    let project_path = if let Some(proj) = project {
390        PathBuf::from(proj)
391    } else {
392        env::current_dir()?
393    };
394
395    let context = context.unwrap_or_else(|| "manual".to_string());
396
397    let socket_path = get_socket_path()?;
398    let mut client = IpcClient::connect(&socket_path).await?;
399    
400    let message = IpcMessage::StartSession { 
401        project_path: Some(project_path.clone()), 
402        context 
403    };
404    
405    match client.send_message(&message).await {
406        Ok(IpcResponse::Ok) => {
407            println!("Session started for project at {:?}", project_path);
408        }
409        Ok(IpcResponse::Error(message)) => {
410            println!("Failed to start session: {}", message);
411        }
412        Ok(other) => {
413            println!("Unexpected response: {:?}", other);
414        }
415        Err(e) => {
416            println!("Failed to communicate with daemon: {}", e);
417        }
418    }
419
420    Ok(())
421}
422
423async fn stop_session() -> Result<()> {
424    if !is_daemon_running() {
425        println!("Daemon is not running");
426        return Ok(());
427    }
428
429    let socket_path = get_socket_path()?;
430    let mut client = IpcClient::connect(&socket_path).await?;
431    
432    match client.send_message(&IpcMessage::StopSession).await {
433        Ok(IpcResponse::Ok) => {
434            println!("Session stopped");
435        }
436        Ok(IpcResponse::Error(message)) => {
437            println!("Failed to stop session: {}", message);
438        }
439        Ok(other) => {
440            println!("Unexpected response: {:?}", other);
441        }
442        Err(e) => {
443            println!("Failed to communicate with daemon: {}", e);
444        }
445    }
446
447    Ok(())
448}
449
450async fn pause_session() -> Result<()> {
451    if !is_daemon_running() {
452        println!("Daemon is not running");
453        return Ok(());
454    }
455
456    let socket_path = get_socket_path()?;
457    let mut client = IpcClient::connect(&socket_path).await?;
458    
459    match client.send_message(&IpcMessage::PauseSession).await {
460        Ok(IpcResponse::Ok) => {
461            println!("Session paused");
462        }
463        Ok(IpcResponse::Error(message)) => {
464            println!("Failed to pause session: {}", message);
465        }
466        Ok(other) => {
467            println!("Unexpected response: {:?}", other);
468        }
469        Err(e) => {
470            println!("Failed to communicate with daemon: {}", e);
471        }
472    }
473
474    Ok(())
475}
476
477async fn resume_session() -> Result<()> {
478    if !is_daemon_running() {
479        println!("Daemon is not running");
480        return Ok(());
481    }
482
483    let socket_path = get_socket_path()?;
484    let mut client = IpcClient::connect(&socket_path).await?;
485    
486    match client.send_message(&IpcMessage::ResumeSession).await {
487        Ok(IpcResponse::Ok) => {
488            println!("Session resumed");
489        }
490        Ok(IpcResponse::Error(message)) => {
491            println!("Failed to resume session: {}", message);
492        }
493        Ok(other) => {
494            println!("Unexpected response: {:?}", other);
495        }
496        Err(e) => {
497            println!("Failed to communicate with daemon: {}", e);
498        }
499    }
500
501    Ok(())
502}
503
504async fn current_session() -> Result<()> {
505    if !is_daemon_running() {
506        print_daemon_not_running();
507        return Ok(());
508    }
509
510    let socket_path = get_socket_path()?;
511    let mut client = IpcClient::connect(&socket_path).await?;
512    
513    match client.send_message(&IpcMessage::GetActiveSession).await {
514        Ok(IpcResponse::SessionInfo(session)) => {
515            print_formatted_session(&session)?;
516        }
517        Ok(IpcResponse::Error(message)) => {
518            print_no_active_session(&message);
519        }
520        Ok(other) => {
521            println!("Unexpected response: {:?}", other);
522        }
523        Err(e) => {
524            println!("Failed to communicate with daemon: {}", e);
525        }
526    }
527
528    Ok(())
529}
530
531// Report generation function
532async fn generate_report(
533    project: Option<String>,
534    from: Option<String>,
535    to: Option<String>,
536    format: Option<String>,
537    group: Option<String>,
538) -> Result<()> {
539    println!("Generating time report...");
540    
541    let generator = ReportGenerator::new()?;
542    let report = generator.generate_report(project, from, to, group)?;
543    
544    match format.as_deref() {
545        Some("csv") => {
546            let output_path = PathBuf::from("tempo-report.csv");
547            generator.export_csv(&report, &output_path)?;
548            println!("Report exported to: {:?}", output_path);
549        }
550        Some("json") => {
551            let output_path = PathBuf::from("tempo-report.json");
552            generator.export_json(&report, &output_path)?;
553            println!("Report exported to: {:?}", output_path);
554        }
555        _ => {
556            // Print to console with formatted output
557            print_formatted_report(&report)?;
558        }
559    }
560    
561    Ok(())
562}
563
564// Formatted output functions
565fn print_formatted_session(session: &crate::utils::ipc::SessionInfo) -> Result<()> {
566    // Color scheme definitions
567    let context_color = match session.context.as_str() {
568        "terminal" => "\x1b[96m",    // Bright cyan
569        "ide" => "\x1b[95m",        // Bright magenta
570        "linked" => "\x1b[93m",     // Bright yellow
571        "manual" => "\x1b[94m",     // Bright blue
572        _ => "\x1b[97m",            // Bright white (default)
573    };
574    
575    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
576    println!("\x1b[36m│\x1b[0m           \x1b[1;37mCurrent Session\x1b[0m               \x1b[36m│\x1b[0m");
577    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
578    println!("\x1b[36m│\x1b[0m Status:   \x1b[1;32m●\x1b[0m \x1b[32mActive\x1b[0m                     \x1b[36m│\x1b[0m");
579    println!("\x1b[36m│\x1b[0m Project:  \x1b[1;33m{:<25}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&session.project_name, 25));
580    println!("\x1b[36m│\x1b[0m Duration: \x1b[1;32m{:<25}\x1b[0m \x1b[36m│\x1b[0m", format_duration_fancy(session.duration));
581    println!("\x1b[36m│\x1b[0m Started:  \x1b[37m{:<25}\x1b[0m \x1b[36m│\x1b[0m", session.start_time.format("%H:%M:%S").to_string());
582    println!("\x1b[36m│\x1b[0m Context:  {}{:<25}\x1b[0m \x1b[36m│\x1b[0m", context_color, truncate_string(&session.context, 25));
583    println!("\x1b[36m│\x1b[0m Path:     \x1b[2;37m{:<25}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&session.project_path.to_string_lossy(), 25));
584    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
585    Ok(())
586}
587
588fn print_formatted_report(report: &crate::cli::reports::TimeReport) -> Result<()> {
589    // Helper function to get context color
590    let get_context_color = |context: &str| -> &str {
591        match context {
592            "terminal" => "\x1b[96m",    // Bright cyan
593            "ide" => "\x1b[95m",        // Bright magenta
594            "linked" => "\x1b[93m",     // Bright yellow
595            "manual" => "\x1b[94m",     // Bright blue
596            _ => "\x1b[97m",            // Bright white (default)
597        }
598    };
599
600    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
601    println!("\x1b[36m│\x1b[0m            \x1b[1;37mTime Report\x1b[0m                  \x1b[36m│\x1b[0m");
602    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
603    
604    for (project_name, project_summary) in &report.projects {
605        println!("\x1b[36m│\x1b[0m \x1b[1;33m{:<20}\x1b[0m \x1b[1;32m{:>15}\x1b[0m \x1b[36m│\x1b[0m", 
606            truncate_string(project_name, 20),
607            format_duration_fancy(project_summary.total_duration)
608        );
609        
610        for (context, duration) in &project_summary.contexts {
611            let context_color = get_context_color(context);
612            println!("\x1b[36m│\x1b[0m   {}{:<15}\x1b[0m \x1b[32m{:>20}\x1b[0m \x1b[36m│\x1b[0m", 
613                context_color,
614                truncate_string(context, 15),
615                format_duration_fancy(*duration)
616            );
617        }
618        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
619    }
620    
621    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
622    println!("\x1b[36m│\x1b[0m \x1b[1;37mTotal Time:\x1b[0m \x1b[1;32m{:>26}\x1b[0m \x1b[36m│\x1b[0m", format_duration_fancy(report.total_duration));
623    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
624    Ok(())
625}
626
627fn format_duration_fancy(seconds: i64) -> String {
628    let hours = seconds / 3600;
629    let minutes = (seconds % 3600) / 60;
630    let secs = seconds % 60;
631
632    if hours > 0 {
633        format!("{}h {}m {}s", hours, minutes, secs)
634    } else if minutes > 0 {
635        format!("{}m {}s", minutes, secs)
636    } else {
637        format!("{}s", secs)
638    }
639}
640
641fn truncate_string(s: &str, max_len: usize) -> String {
642    if s.len() <= max_len {
643        format!("{:<width$}", s, width = max_len)
644    } else {
645        format!("{:.width$}...", s, width = max_len.saturating_sub(3))
646    }
647}
648
649// Helper functions for consistent messaging
650fn print_daemon_not_running() {
651    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
652    println!("\x1b[36m│\x1b[0m               \x1b[1;37mDaemon Status\x1b[0m               \x1b[36m│\x1b[0m");
653    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
654    println!("\x1b[36m│\x1b[0m Status:   \x1b[1;31m●\x1b[0m \x1b[31mOffline\x1b[0m                   \x1b[36m│\x1b[0m");
655    println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
656    println!("\x1b[36m│\x1b[0m \x1b[33mDaemon is not running.\x1b[0m                 \x1b[36m│\x1b[0m");
657    println!("\x1b[36m│\x1b[0m \x1b[37mStart it with:\x1b[0m \x1b[96mtempo start\x1b[0m         \x1b[36m│\x1b[0m");
658    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
659}
660
661fn print_no_active_session(message: &str) {
662    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
663    println!("\x1b[36m│\x1b[0m           \x1b[1;37mCurrent Session\x1b[0m               \x1b[36m│\x1b[0m");
664    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
665    println!("\x1b[36m│\x1b[0m Status:   \x1b[1;33m○\x1b[0m \x1b[33mIdle\x1b[0m                      \x1b[36m│\x1b[0m");
666    println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
667    println!("\x1b[36m│\x1b[0m \x1b[90m{:<37}\x1b[0m \x1b[36m│\x1b[0m", message);
668    println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
669    println!("\x1b[36m│\x1b[0m \x1b[37mStart tracking:\x1b[0m                       \x1b[36m│\x1b[0m");
670    println!("\x1b[36m│\x1b[0m   \x1b[96mtempo session start\x1b[0m                \x1b[36m│\x1b[0m");
671    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
672}
673
674fn print_daemon_status(uptime: u64, active_session: Option<&crate::utils::ipc::SessionInfo>) {
675    let uptime_formatted = format_duration_fancy(uptime as i64);
676    
677    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
678    println!("\x1b[36m│\x1b[0m               \x1b[1;37mDaemon Status\x1b[0m               \x1b[36m│\x1b[0m");
679    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
680    println!("\x1b[36m│\x1b[0m Status:   \x1b[1;32m●\x1b[0m \x1b[32mOnline\x1b[0m                    \x1b[36m│\x1b[0m");
681    println!("\x1b[36m│\x1b[0m Uptime:   \x1b[37m{:<25}\x1b[0m \x1b[36m│\x1b[0m", uptime_formatted);
682    
683    if let Some(session) = active_session {
684        let context_color = match session.context.as_str() {
685            "terminal" => "\x1b[96m", "ide" => "\x1b[95m", "linked" => "\x1b[93m", 
686            "manual" => "\x1b[94m", _ => "\x1b[97m",
687        };
688        
689        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
690        println!("\x1b[36m│\x1b[0m \x1b[1;37mActive Session:\x1b[0m                      \x1b[36m│\x1b[0m");
691        println!("\x1b[36m│\x1b[0m   Project: \x1b[1;33m{:<23}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&session.project_name, 23));
692        println!("\x1b[36m│\x1b[0m   Duration: \x1b[1;32m{:<22}\x1b[0m \x1b[36m│\x1b[0m", format_duration_fancy(session.duration));
693        println!("\x1b[36m│\x1b[0m   Context: {}{:<23}\x1b[0m \x1b[36m│\x1b[0m", context_color, session.context);
694    } else {
695        println!("\x1b[36m│\x1b[0m Session:  \x1b[33mNo active session\x1b[0m             \x1b[36m│\x1b[0m");
696    }
697    
698    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
699}
700
701// Project management functions
702async fn init_project(name: Option<String>, path: Option<PathBuf>, description: Option<String>) -> Result<()> {
703    // Validate inputs early
704    let validated_name = if let Some(n) = name.as_ref() {
705        Some(validate_project_name(n)
706            .with_context(|| format!("Invalid project name '{}'", n))?)
707    } else {
708        None
709    };
710    
711    let validated_description = if let Some(d) = description.as_ref() {
712        Some(validate_project_description(d)
713            .with_context(|| "Invalid project description")?)
714    } else {
715        None
716    };
717    
718    let project_path = path.unwrap_or_else(|| {
719        env::current_dir().expect("Failed to get current directory")
720    });
721    
722    // Use secure path validation
723    let canonical_path = validate_project_path(&project_path)
724        .with_context(|| format!("Invalid project path: {}", project_path.display()))?;
725    
726    let project_name = validated_name.clone().unwrap_or_else(|| {
727        let detected = detect_project_name(&canonical_path);
728        validate_project_name(&detected).unwrap_or_else(|_| "project".to_string())
729    });
730    
731    // Get database connection from pool
732    let conn = match get_connection().await {
733        Ok(conn) => conn,
734        Err(_) => {
735            // Fallback to direct connection
736            let db_path = get_database_path()?;
737            let db = Database::new(&db_path)?;
738            return init_project_with_db(validated_name, Some(canonical_path), validated_description, &db.connection).await;
739        }
740    };
741    
742    // Check if project already exists
743    if let Some(existing) = ProjectQueries::find_by_path(conn.connection(), &canonical_path)? {
744        eprintln!("\x1b[33m⚠ Warning:\x1b[0m A project named '{}' already exists at this path.", existing.name);
745        eprintln!("Use 'tempo list' to see all projects or choose a different location.");
746        return Ok(());
747    }
748    
749    // Use the pooled connection to complete initialization
750    init_project_with_db(Some(project_name.clone()), Some(canonical_path.clone()), validated_description, conn.connection()).await?;
751    
752    println!("\x1b[32m✓ Success:\x1b[0m Project '{}' initialized at {}", project_name, canonical_path.display());
753    println!("Start tracking time with: \x1b[36mtempo start\x1b[0m");
754    
755    Ok(())
756}
757
758async fn list_projects(include_archived: bool, tag_filter: Option<String>) -> Result<()> {
759    // Initialize database
760    let db_path = get_database_path()?;
761    let db = Database::new(&db_path)?;
762    
763    // Get projects
764    let projects = ProjectQueries::list_all(&db.connection, include_archived)?;
765    
766    if projects.is_empty() {
767        println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
768        println!("\x1b[36m│\x1b[0m              \x1b[1;37mNo Projects\x1b[0m                 \x1b[36m│\x1b[0m");
769        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
770        println!("\x1b[36m│\x1b[0m No projects found.                      \x1b[36m│\x1b[0m");
771        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
772        println!("\x1b[36m│\x1b[0m \x1b[37mCreate a project:\x1b[0m                      \x1b[36m│\x1b[0m");
773        println!("\x1b[36m│\x1b[0m   \x1b[96mtempo init [project-name]\x1b[0m           \x1b[36m│\x1b[0m");
774        println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
775        return Ok(());
776    }
777    
778    // Filter by tag if specified
779    let filtered_projects = if let Some(_tag) = tag_filter {
780        // TODO: Implement tag filtering when project-tag associations are implemented
781        projects
782    } else {
783        projects
784    };
785    
786    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
787    println!("\x1b[36m│\x1b[0m              \x1b[1;37mProjects\x1b[0m                    \x1b[36m│\x1b[0m");
788    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
789    
790    for project in &filtered_projects {
791        let status_icon = if project.is_archived { "📦" } else { "📁" };
792        let status_color = if project.is_archived { "\x1b[90m" } else { "\x1b[37m" };
793        let git_indicator = if project.git_hash.is_some() { " (git)" } else { "" };
794        
795        println!("\x1b[36m│\x1b[0m {} {}{:<25}\x1b[0m \x1b[36m│\x1b[0m", 
796            status_icon,
797            status_color,
798            format!("{}{}", truncate_string(&project.name, 20), git_indicator)
799        );
800        
801        if let Some(description) = &project.description {
802            println!("\x1b[36m│\x1b[0m   \x1b[2;37m{:<35}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(description, 35));
803        }
804        
805        let path_display = project.path.to_string_lossy();
806        if path_display.len() > 35 {
807            let home_dir = dirs::home_dir();
808            let display_path = if let Some(home) = home_dir {
809                if let Ok(stripped) = project.path.strip_prefix(&home) {
810                    format!("~/{}", stripped.display())
811                } else {
812                    path_display.to_string()
813                }
814            } else {
815                path_display.to_string()
816            };
817            println!("\x1b[36m│\x1b[0m   \x1b[90m{:<35}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&display_path, 35));
818        } else {
819            println!("\x1b[36m│\x1b[0m   \x1b[90m{:<35}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&path_display, 35));
820        }
821        
822        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
823    }
824    
825    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
826    println!("\x1b[36m│\x1b[0m \x1b[1;37mTotal:\x1b[0m {:<30} \x1b[36m│\x1b[0m", 
827        format!("{} projects", filtered_projects.len())
828    );
829    if include_archived {
830        let active_count = filtered_projects.iter().filter(|p| !p.is_archived).count();
831        let archived_count = filtered_projects.iter().filter(|p| p.is_archived).count();
832        println!("\x1b[36m│\x1b[0m \x1b[37mActive:\x1b[0m {:<15} \x1b[90mArchived:\x1b[0m {:<8} \x1b[36m│\x1b[0m", 
833            active_count, archived_count
834        );
835    }
836    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
837    
838    Ok(())
839}
840
841// Tag management functions
842async fn create_tag(name: String, color: Option<String>, description: Option<String>) -> Result<()> {
843    // Initialize database
844    let db_path = get_database_path()?;
845    let db = Database::new(&db_path)?;
846    
847    // Create tag - use builder pattern to avoid cloning
848    let mut tag = Tag::new(name);
849    if let Some(c) = color {
850        tag = tag.with_color(c);
851    }
852    if let Some(d) = description {
853        tag = tag.with_description(d);
854    }
855    
856    // Validate tag
857    tag.validate()?;
858    
859    // Check if tag already exists
860    let existing_tags = TagQueries::list_all(&db.connection)?;
861    if existing_tags.iter().any(|t| t.name == tag.name) {
862        println!("\x1b[33m⚠  Tag already exists:\x1b[0m {}", tag.name);
863        return Ok(());
864    }
865    
866    // Save to database
867    let tag_id = TagQueries::create(&db.connection, &tag)?;
868    
869    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
870    println!("\x1b[36m│\x1b[0m           \x1b[1;37mTag Created\x1b[0m                   \x1b[36m│\x1b[0m");
871    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
872    println!("\x1b[36m│\x1b[0m Name:     \x1b[1;33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&tag.name, 27));
873    if let Some(color_val) = &tag.color {
874        println!("\x1b[36m│\x1b[0m Color:    \x1b[37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(color_val, 27));
875    }
876    if let Some(desc) = &tag.description {
877        println!("\x1b[36m│\x1b[0m Desc:     \x1b[2;37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(desc, 27));
878    }
879    println!("\x1b[36m│\x1b[0m ID:       \x1b[90m{:<27}\x1b[0m \x1b[36m│\x1b[0m", tag_id);
880    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
881    println!("\x1b[36m│\x1b[0m \x1b[32m✓ Tag created successfully\x1b[0m             \x1b[36m│\x1b[0m");
882    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
883    
884    Ok(())
885}
886
887async fn list_tags() -> Result<()> {
888    // Initialize database
889    let db_path = get_database_path()?;
890    let db = Database::new(&db_path)?;
891    
892    // Get tags
893    let tags = TagQueries::list_all(&db.connection)?;
894    
895    if tags.is_empty() {
896        println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
897        println!("\x1b[36m│\x1b[0m               \x1b[1;37mNo Tags\x1b[0m                    \x1b[36m│\x1b[0m");
898        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
899        println!("\x1b[36m│\x1b[0m No tags found.                          \x1b[36m│\x1b[0m");
900        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
901        println!("\x1b[36m│\x1b[0m \x1b[37mCreate a tag:\x1b[0m                          \x1b[36m│\x1b[0m");
902        println!("\x1b[36m│\x1b[0m   \x1b[96mtempo tag create <name>\x1b[0m             \x1b[36m│\x1b[0m");
903        println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
904        return Ok(());
905    }
906    
907    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
908    println!("\x1b[36m│\x1b[0m                \x1b[1;37mTags\x1b[0m                      \x1b[36m│\x1b[0m");
909    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
910    
911    for tag in &tags {
912        let color_indicator = if let Some(color) = &tag.color {
913            format!(" ({})", color)
914        } else {
915            String::new()
916        };
917        
918        println!("\x1b[36m│\x1b[0m 🏷️  \x1b[1;33m{:<30}\x1b[0m \x1b[36m│\x1b[0m", 
919            format!("{}{}", truncate_string(&tag.name, 25), color_indicator)
920        );
921        
922        if let Some(description) = &tag.description {
923            println!("\x1b[36m│\x1b[0m     \x1b[2;37m{:<33}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(description, 33));
924        }
925        
926        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
927    }
928    
929    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
930    println!("\x1b[36m│\x1b[0m \x1b[1;37mTotal:\x1b[0m {:<30} \x1b[36m│\x1b[0m", 
931        format!("{} tags", tags.len())
932    );
933    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
934    
935    Ok(())
936}
937
938async fn delete_tag(name: String) -> Result<()> {
939    // Initialize database
940    let db_path = get_database_path()?;
941    let db = Database::new(&db_path)?;
942    
943    // Check if tag exists
944    if TagQueries::find_by_name(&db.connection, &name)?.is_none() {
945        println!("\x1b[31m✗ Tag '{}' not found\x1b[0m", name);
946        return Ok(());
947    }
948    
949    // Delete the tag
950    let deleted = TagQueries::delete_by_name(&db.connection, &name)?;
951    
952    if deleted {
953        println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
954        println!("\x1b[36m│\x1b[0m           \x1b[1;37mTag Deleted\x1b[0m                   \x1b[36m│\x1b[0m");
955        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
956        println!("\x1b[36m│\x1b[0m Name:     \x1b[1;33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&name, 27));
957        println!("\x1b[36m│\x1b[0m Status:   \x1b[32mDeleted\x1b[0m                   \x1b[36m│\x1b[0m");
958        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
959        println!("\x1b[36m│\x1b[0m \x1b[32m✓ Tag deleted successfully\x1b[0m             \x1b[36m│\x1b[0m");
960        println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
961    } else {
962        println!("\x1b[31m✗ Failed to delete tag '{}'\x1b[0m", name);
963    }
964    
965    Ok(())
966}
967
968// Configuration management functions
969async fn show_config() -> Result<()> {
970    let config = load_config()?;
971    
972    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
973    println!("\x1b[36m│\x1b[0m           \x1b[1;37mConfiguration\x1b[0m                  \x1b[36m│\x1b[0m");
974    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
975    println!("\x1b[36m│\x1b[0m idle_timeout_minutes:  \x1b[33m{:<16}\x1b[0m \x1b[36m│\x1b[0m", config.idle_timeout_minutes);
976    println!("\x1b[36m│\x1b[0m auto_pause_enabled:    \x1b[33m{:<16}\x1b[0m \x1b[36m│\x1b[0m", config.auto_pause_enabled);
977    println!("\x1b[36m│\x1b[0m default_context:       \x1b[33m{:<16}\x1b[0m \x1b[36m│\x1b[0m", config.default_context);
978    println!("\x1b[36m│\x1b[0m max_session_hours:     \x1b[33m{:<16}\x1b[0m \x1b[36m│\x1b[0m", config.max_session_hours);
979    println!("\x1b[36m│\x1b[0m backup_enabled:        \x1b[33m{:<16}\x1b[0m \x1b[36m│\x1b[0m", config.backup_enabled);
980    println!("\x1b[36m│\x1b[0m log_level:             \x1b[33m{:<16}\x1b[0m \x1b[36m│\x1b[0m", config.log_level);
981    
982    if !config.custom_settings.is_empty() {
983        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
984        println!("\x1b[36m│\x1b[0m \x1b[1;37mCustom Settings:\x1b[0m                      \x1b[36m│\x1b[0m");
985        for (key, value) in &config.custom_settings {
986            println!("\x1b[36m│\x1b[0m {:<20} \x1b[33m{:<16}\x1b[0m \x1b[36m│\x1b[0m", 
987                truncate_string(key, 20), 
988                truncate_string(value, 16)
989            );
990        }
991    }
992    
993    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
994    
995    Ok(())
996}
997
998async fn get_config(key: String) -> Result<()> {
999    let config = load_config()?;
1000    
1001    let value = match key.as_str() {
1002        "idle_timeout_minutes" => Some(config.idle_timeout_minutes.to_string()),
1003        "auto_pause_enabled" => Some(config.auto_pause_enabled.to_string()),
1004        "default_context" => Some(config.default_context),
1005        "max_session_hours" => Some(config.max_session_hours.to_string()),
1006        "backup_enabled" => Some(config.backup_enabled.to_string()),
1007        "log_level" => Some(config.log_level),
1008        _ => config.custom_settings.get(&key).cloned(),
1009    };
1010    
1011    match value {
1012        Some(val) => {
1013            println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1014            println!("\x1b[36m│\x1b[0m          \x1b[1;37mConfiguration Value\x1b[0m             \x1b[36m│\x1b[0m");
1015            println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1016            println!("\x1b[36m│\x1b[0m {:<20} \x1b[33m{:<16}\x1b[0m \x1b[36m│\x1b[0m", 
1017                truncate_string(&key, 20), 
1018                truncate_string(&val, 16)
1019            );
1020            println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1021        }
1022        None => {
1023            println!("\x1b[31m✗ Configuration key not found:\x1b[0m {}", key);
1024        }
1025    }
1026    
1027    Ok(())
1028}
1029
1030async fn set_config(key: String, value: String) -> Result<()> {
1031    let mut config = load_config()?;
1032    
1033    let display_value = value.clone(); // Clone for display purposes
1034    
1035    match key.as_str() {
1036        "idle_timeout_minutes" => {
1037            config.idle_timeout_minutes = value.parse()?;
1038        }
1039        "auto_pause_enabled" => {
1040            config.auto_pause_enabled = value.parse()?;
1041        }
1042        "default_context" => {
1043            config.default_context = value;
1044        }
1045        "max_session_hours" => {
1046            config.max_session_hours = value.parse()?;
1047        }
1048        "backup_enabled" => {
1049            config.backup_enabled = value.parse()?;
1050        }
1051        "log_level" => {
1052            config.log_level = value;
1053        }
1054        _ => {
1055            config.set_custom(key.clone(), value);
1056        }
1057    }
1058    
1059    config.validate()?;
1060    save_config(&config)?;
1061    
1062    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1063    println!("\x1b[36m│\x1b[0m        \x1b[1;37mConfiguration Updated\x1b[0m             \x1b[36m│\x1b[0m");
1064    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1065    println!("\x1b[36m│\x1b[0m {:<20} \x1b[32m{:<16}\x1b[0m \x1b[36m│\x1b[0m", 
1066        truncate_string(&key, 20), 
1067        truncate_string(&display_value, 16)
1068    );
1069    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1070    println!("\x1b[36m│\x1b[0m \x1b[32m✓ Configuration saved successfully\x1b[0m      \x1b[36m│\x1b[0m");
1071    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1072    
1073    Ok(())
1074}
1075
1076async fn reset_config() -> Result<()> {
1077    let default_config = crate::models::Config::default();
1078    save_config(&default_config)?;
1079    
1080    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1081    println!("\x1b[36m│\x1b[0m         \x1b[1;37mConfiguration Reset\x1b[0m              \x1b[36m│\x1b[0m");
1082    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1083    println!("\x1b[36m│\x1b[0m \x1b[32m✓ Configuration reset to defaults\x1b[0m       \x1b[36m│\x1b[0m");
1084    println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
1085    println!("\x1b[36m│\x1b[0m \x1b[37mView current config:\x1b[0m                   \x1b[36m│\x1b[0m");
1086    println!("\x1b[36m│\x1b[0m   \x1b[96mtempo config show\x1b[0m                   \x1b[36m│\x1b[0m");
1087    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1088    
1089    Ok(())
1090}
1091
1092// Session management functions
1093async fn list_sessions(limit: Option<usize>, project_filter: Option<String>) -> Result<()> {
1094    // Initialize database
1095    let db_path = get_database_path()?;
1096    let db = Database::new(&db_path)?;
1097    
1098    let session_limit = limit.unwrap_or(10);
1099    
1100    // Handle project filtering
1101    let project_id = if let Some(project_name) = &project_filter {
1102        match ProjectQueries::find_by_name(&db.connection, project_name)? {
1103            Some(project) => Some(project.id.unwrap()),
1104            None => {
1105                println!("\x1b[31m✗ Project '{}' not found\x1b[0m", project_name);
1106                return Ok(());
1107            }
1108        }
1109    } else {
1110        None
1111    };
1112    
1113    let sessions = SessionQueries::list_with_filter(&db.connection, project_id, None, None, Some(session_limit))?;
1114    
1115    if sessions.is_empty() {
1116        println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1117        println!("\x1b[36m│\x1b[0m             \x1b[1;37mNo Sessions\x1b[0m                  \x1b[36m│\x1b[0m");
1118        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1119        println!("\x1b[36m│\x1b[0m No sessions found.                      \x1b[36m│\x1b[0m");
1120        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
1121        println!("\x1b[36m│\x1b[0m \x1b[37mStart a session:\x1b[0m                      \x1b[36m│\x1b[0m");
1122        println!("\x1b[36m│\x1b[0m   \x1b[96mtempo session start\x1b[0m                 \x1b[36m│\x1b[0m");
1123        println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1124        return Ok(());
1125    }
1126    
1127    // Filter by project if specified
1128    let filtered_sessions = if let Some(_project) = project_filter {
1129        // TODO: Implement project filtering when we have project relationships
1130        sessions
1131    } else {
1132        sessions
1133    };
1134    
1135    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1136    println!("\x1b[36m│\x1b[0m         \x1b[1;37mRecent Sessions\x1b[0m                 \x1b[36m│\x1b[0m");
1137    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1138    
1139    for session in &filtered_sessions {
1140        let status_icon = if session.end_time.is_some() { "✅" } else { "🔄" };
1141        let duration = if let Some(end) = session.end_time {
1142            (end - session.start_time).num_seconds() - session.paused_duration.num_seconds()
1143        } else {
1144            (Utc::now() - session.start_time).num_seconds() - session.paused_duration.num_seconds()
1145        };
1146        
1147        let context_color = match session.context {
1148            crate::models::SessionContext::Terminal => "\x1b[96m",
1149            crate::models::SessionContext::IDE => "\x1b[95m", 
1150            crate::models::SessionContext::Linked => "\x1b[93m",
1151            crate::models::SessionContext::Manual => "\x1b[94m",
1152        };
1153        
1154        println!("\x1b[36m│\x1b[0m {} \x1b[1;37m{:<32}\x1b[0m \x1b[36m│\x1b[0m", 
1155            status_icon,
1156            format!("Session {}", session.id.unwrap_or(0))
1157        );
1158        println!("\x1b[36m│\x1b[0m    Duration: \x1b[32m{:<24}\x1b[0m \x1b[36m│\x1b[0m", format_duration_fancy(duration));
1159        println!("\x1b[36m│\x1b[0m    Context:  {}{:<24}\x1b[0m \x1b[36m│\x1b[0m", 
1160            context_color, 
1161            session.context
1162        );
1163        println!("\x1b[36m│\x1b[0m    Started:  \x1b[37m{:<24}\x1b[0m \x1b[36m│\x1b[0m", 
1164            session.start_time.format("%Y-%m-%d %H:%M:%S")
1165        );
1166        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
1167    }
1168    
1169    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1170    println!("\x1b[36m│\x1b[0m \x1b[1;37mShowing:\x1b[0m {:<28} \x1b[36m│\x1b[0m", 
1171        format!("{} recent sessions", filtered_sessions.len())
1172    );
1173    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1174    
1175    Ok(())
1176}
1177
1178async fn edit_session(id: i64, start: Option<String>, end: Option<String>, project: Option<String>, reason: Option<String>) -> Result<()> {
1179    // Initialize database
1180    let db_path = get_database_path()?;
1181    let db = Database::new(&db_path)?;
1182    
1183    // Find the session
1184    let session = SessionQueries::find_by_id(&db.connection, id)?;
1185    let session = match session {
1186        Some(s) => s,
1187        None => {
1188            println!("\x1b[31m✗ Session {} not found\x1b[0m", id);
1189            return Ok(());
1190        }
1191    };
1192    
1193    let original_start = session.start_time;
1194    let original_end = session.end_time;
1195    
1196    // Parse new values
1197    let mut new_start = original_start;
1198    let mut new_end = original_end;
1199    let mut new_project_id = session.project_id;
1200    
1201    // Parse start time if provided
1202    if let Some(start_str) = &start {
1203        new_start = match chrono::DateTime::parse_from_rfc3339(start_str) {
1204            Ok(dt) => dt.with_timezone(&chrono::Utc),
1205            Err(_) => {
1206                match chrono::NaiveDateTime::parse_from_str(start_str, "%Y-%m-%d %H:%M:%S") {
1207                    Ok(dt) => chrono::Utc.from_utc_datetime(&dt),
1208                    Err(_) => return Err(anyhow::anyhow!("Invalid start time format. Use RFC3339 or 'YYYY-MM-DD HH:MM:SS'"))
1209                }
1210            }
1211        };
1212    }
1213    
1214    // Parse end time if provided
1215    if let Some(end_str) = &end {
1216        if end_str.to_lowercase() == "null" || end_str.to_lowercase() == "none" {
1217            new_end = None;
1218        } else {
1219            new_end = Some(match chrono::DateTime::parse_from_rfc3339(end_str) {
1220                Ok(dt) => dt.with_timezone(&chrono::Utc),
1221                Err(_) => {
1222                    match chrono::NaiveDateTime::parse_from_str(end_str, "%Y-%m-%d %H:%M:%S") {
1223                        Ok(dt) => chrono::Utc.from_utc_datetime(&dt),
1224                        Err(_) => return Err(anyhow::anyhow!("Invalid end time format. Use RFC3339 or 'YYYY-MM-DD HH:MM:SS'"))
1225                    }
1226                }
1227            });
1228        }
1229    }
1230    
1231    // Find project by name if provided
1232    if let Some(project_name) = &project {
1233        if let Some(proj) = ProjectQueries::find_by_name(&db.connection, project_name)? {
1234            new_project_id = proj.id.unwrap();
1235        } else {
1236            println!("\x1b[31m✗ Project '{}' not found\x1b[0m", project_name);
1237            return Ok(());
1238        }
1239    }
1240    
1241    // Validate the edit
1242    if new_start >= new_end.unwrap_or(chrono::Utc::now()) {
1243        println!("\x1b[31m✗ Start time must be before end time\x1b[0m");
1244        return Ok(());
1245    }
1246    
1247    // Create audit trail record
1248    SessionEditQueries::create_edit_record(
1249        &db.connection,
1250        id,
1251        original_start,
1252        original_end,
1253        new_start,
1254        new_end,
1255        reason.clone()
1256    )?;
1257    
1258    // Update the session
1259    SessionQueries::update_session(
1260        &db.connection,
1261        id,
1262        if start.is_some() { Some(new_start) } else { None },
1263        if end.is_some() { Some(new_end) } else { None },
1264        if project.is_some() { Some(new_project_id) } else { None },
1265        None
1266    )?;
1267    
1268    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1269    println!("\x1b[36m│\x1b[0m         \x1b[1;37mSession Updated\x1b[0m                 \x1b[36m│\x1b[0m");
1270    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1271    println!("\x1b[36m│\x1b[0m Session:  \x1b[1;37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", id);
1272    
1273    if start.is_some() {
1274        println!("\x1b[36m│\x1b[0m Start:    \x1b[32m{:<27}\x1b[0m \x1b[36m│\x1b[0m", 
1275            truncate_string(&new_start.format("%Y-%m-%d %H:%M:%S").to_string(), 27)
1276        );
1277    }
1278    
1279    if end.is_some() {
1280        let end_str = if let Some(e) = new_end {
1281            e.format("%Y-%m-%d %H:%M:%S").to_string()
1282        } else {
1283            "Ongoing".to_string()
1284        };
1285        println!("\x1b[36m│\x1b[0m End:      \x1b[32m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&end_str, 27));
1286    }
1287    
1288    if let Some(r) = &reason {
1289        println!("\x1b[36m│\x1b[0m Reason:   \x1b[2;37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(r, 27));
1290    }
1291    
1292    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1293    println!("\x1b[36m│\x1b[0m \x1b[32m✓ Session updated with audit trail\x1b[0m     \x1b[36m│\x1b[0m");
1294    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1295    
1296    Ok(())
1297}
1298
1299async fn delete_session(id: i64, force: bool) -> Result<()> {
1300    // Initialize database
1301    let db_path = get_database_path()?;
1302    let db = Database::new(&db_path)?;
1303    
1304    // Check if session exists
1305    let session = SessionQueries::find_by_id(&db.connection, id)?;
1306    let session = match session {
1307        Some(s) => s,
1308        None => {
1309            println!("\x1b[31m✗ Session {} not found\x1b[0m", id);
1310            return Ok(());
1311        }
1312    };
1313    
1314    // Check if it's an active session and require force flag
1315    if session.end_time.is_none() && !force {
1316        println!("\x1b[33m⚠  Cannot delete active session without --force flag\x1b[0m");
1317        println!("  Use: tempo session delete {} --force", id);
1318        return Ok(());
1319    }
1320    
1321    // Delete the session
1322    SessionQueries::delete_session(&db.connection, id)?;
1323    
1324    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1325    println!("\x1b[36m│\x1b[0m         \x1b[1;37mSession Deleted\x1b[0m                 \x1b[36m│\x1b[0m");
1326    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1327    println!("\x1b[36m│\x1b[0m Session:  \x1b[1;37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", id);
1328    println!("\x1b[36m│\x1b[0m Status:   \x1b[32mDeleted\x1b[0m                   \x1b[36m│\x1b[0m");
1329    
1330    if session.end_time.is_none() {
1331        println!("\x1b[36m│\x1b[0m Type:     \x1b[33mActive session (forced)\x1b[0m      \x1b[36m│\x1b[0m");
1332    } else {
1333        println!("\x1b[36m│\x1b[0m Type:     \x1b[37mCompleted session\x1b[0m           \x1b[36m│\x1b[0m");
1334    }
1335    
1336    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1337    println!("\x1b[36m│\x1b[0m \x1b[32m✓ Session and audit trail removed\x1b[0m      \x1b[36m│\x1b[0m");
1338    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1339    
1340    Ok(())
1341}
1342
1343// Project management functions
1344async fn archive_project(project_name: String) -> Result<()> {
1345    let db_path = get_database_path()?;
1346    let db = Database::new(&db_path)?;
1347    
1348    let project = match ProjectQueries::find_by_name(&db.connection, &project_name)? {
1349        Some(p) => p,
1350        None => {
1351            println!("\x1b[31m✗ Project '{}' not found\x1b[0m", project_name);
1352            return Ok(());
1353        }
1354    };
1355    
1356    if project.is_archived {
1357        println!("\x1b[33m⚠  Project '{}' is already archived\x1b[0m", project_name);
1358        return Ok(());
1359    }
1360    
1361    let success = ProjectQueries::archive_project(&db.connection, project.id.unwrap())?;
1362    
1363    if success {
1364        println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1365        println!("\x1b[36m│\x1b[0m        \x1b[1;37mProject Archived\x1b[0m                \x1b[36m│\x1b[0m");
1366        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1367        println!("\x1b[36m│\x1b[0m Name:     \x1b[1;33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&project_name, 27));
1368        println!("\x1b[36m│\x1b[0m Status:   \x1b[90mArchived\x1b[0m                  \x1b[36m│\x1b[0m");
1369        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1370        println!("\x1b[36m│\x1b[0m \x1b[32m✓ Project archived successfully\x1b[0m        \x1b[36m│\x1b[0m");
1371        println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1372    } else {
1373        println!("\x1b[31m✗ Failed to archive project '{}'\x1b[0m", project_name);
1374    }
1375    
1376    Ok(())
1377}
1378
1379async fn unarchive_project(project_name: String) -> Result<()> {
1380    let db_path = get_database_path()?;
1381    let db = Database::new(&db_path)?;
1382    
1383    let project = match ProjectQueries::find_by_name(&db.connection, &project_name)? {
1384        Some(p) => p,
1385        None => {
1386            println!("\x1b[31m✗ Project '{}' not found\x1b[0m", project_name);
1387            return Ok(());
1388        }
1389    };
1390    
1391    if !project.is_archived {
1392        println!("\x1b[33m⚠  Project '{}' is not archived\x1b[0m", project_name);
1393        return Ok(());
1394    }
1395    
1396    let success = ProjectQueries::unarchive_project(&db.connection, project.id.unwrap())?;
1397    
1398    if success {
1399        println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1400        println!("\x1b[36m│\x1b[0m       \x1b[1;37mProject Unarchived\x1b[0m               \x1b[36m│\x1b[0m");
1401        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1402        println!("\x1b[36m│\x1b[0m Name:     \x1b[1;33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&project_name, 27));
1403        println!("\x1b[36m│\x1b[0m Status:   \x1b[32mActive\x1b[0m                    \x1b[36m│\x1b[0m");
1404        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1405        println!("\x1b[36m│\x1b[0m \x1b[32m✓ Project unarchived successfully\x1b[0m      \x1b[36m│\x1b[0m");
1406        println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1407    } else {
1408        println!("\x1b[31m✗ Failed to unarchive project '{}'\x1b[0m", project_name);
1409    }
1410    
1411    Ok(())
1412}
1413
1414async fn update_project_path(project_name: String, new_path: PathBuf) -> Result<()> {
1415    let db_path = get_database_path()?;
1416    let db = Database::new(&db_path)?;
1417    
1418    let project = match ProjectQueries::find_by_name(&db.connection, &project_name)? {
1419        Some(p) => p,
1420        None => {
1421            println!("\x1b[31m✗ Project '{}' not found\x1b[0m", project_name);
1422            return Ok(());
1423        }
1424    };
1425    
1426    let canonical_path = canonicalize_path(&new_path)?;
1427    let success = ProjectQueries::update_project_path(&db.connection, project.id.unwrap(), &canonical_path)?;
1428    
1429    if success {
1430        println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1431        println!("\x1b[36m│\x1b[0m       \x1b[1;37mProject Path Updated\x1b[0m              \x1b[36m│\x1b[0m");
1432        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1433        println!("\x1b[36m│\x1b[0m Name:     \x1b[1;33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&project_name, 27));
1434        println!("\x1b[36m│\x1b[0m Old Path: \x1b[2;37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&project.path.to_string_lossy(), 27));
1435        println!("\x1b[36m│\x1b[0m New Path: \x1b[32m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&canonical_path.to_string_lossy(), 27));
1436        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1437        println!("\x1b[36m│\x1b[0m \x1b[32m✓ Path updated successfully\x1b[0m            \x1b[36m│\x1b[0m");
1438        println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1439    } else {
1440        println!("\x1b[31m✗ Failed to update path for project '{}'\x1b[0m", project_name);
1441    }
1442    
1443    Ok(())
1444}
1445
1446async fn add_tag_to_project(project_name: String, tag_name: String) -> Result<()> {
1447    println!("\x1b[33m⚠  Project-tag associations not yet implemented\x1b[0m");
1448    println!("Would add tag '{}' to project '{}'", tag_name, project_name);
1449    println!("This requires implementing project_tags table operations.");
1450    Ok(())
1451}
1452
1453async fn remove_tag_from_project(project_name: String, tag_name: String) -> Result<()> {
1454    println!("\x1b[33m⚠  Project-tag associations not yet implemented\x1b[0m");
1455    println!("Would remove tag '{}' from project '{}'", tag_name, project_name);
1456    println!("This requires implementing project_tags table operations.");
1457    Ok(())
1458}
1459
1460// Bulk session operations
1461async fn bulk_update_sessions_project(session_ids: Vec<i64>, new_project_name: String) -> Result<()> {
1462    let db_path = get_database_path()?;
1463    let db = Database::new(&db_path)?;
1464    
1465    // Find the target project
1466    let project = match ProjectQueries::find_by_name(&db.connection, &new_project_name)? {
1467        Some(p) => p,
1468        None => {
1469            println!("\x1b[31m✗ Project '{}' not found\x1b[0m", new_project_name);
1470            return Ok(());
1471        }
1472    };
1473    
1474    let updated = SessionQueries::bulk_update_project(&db.connection, &session_ids, project.id.unwrap())?;
1475    
1476    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1477    println!("\x1b[36m│\x1b[0m      \x1b[1;37mBulk Session Update\x1b[0m               \x1b[36m│\x1b[0m");
1478    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1479    println!("\x1b[36m│\x1b[0m Sessions: \x1b[1;37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", updated);
1480    println!("\x1b[36m│\x1b[0m Project:  \x1b[1;33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&new_project_name, 27));
1481    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1482    println!("\x1b[36m│\x1b[0m \x1b[32m✓ {} sessions updated\x1b[0m {:<12} \x1b[36m│\x1b[0m", updated, "");
1483    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1484    
1485    Ok(())
1486}
1487
1488async fn bulk_delete_sessions(session_ids: Vec<i64>) -> Result<()> {
1489    let db_path = get_database_path()?;
1490    let db = Database::new(&db_path)?;
1491    
1492    let deleted = SessionQueries::bulk_delete(&db.connection, &session_ids)?;
1493    
1494    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1495    println!("\x1b[36m│\x1b[0m      \x1b[1;37mBulk Session Delete\x1b[0m               \x1b[36m│\x1b[0m");
1496    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1497    println!("\x1b[36m│\x1b[0m Requested: \x1b[1;37m{:<25}\x1b[0m \x1b[36m│\x1b[0m", session_ids.len());
1498    println!("\x1b[36m│\x1b[0m Deleted:   \x1b[32m{:<25}\x1b[0m \x1b[36m│\x1b[0m", deleted);
1499    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1500    println!("\x1b[36m│\x1b[0m \x1b[32m✓ {} sessions deleted\x1b[0m {:<10} \x1b[36m│\x1b[0m", deleted, "");
1501    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1502    
1503    Ok(())
1504}
1505
1506async fn launch_dashboard() -> Result<()> {
1507    // Setup terminal
1508    enable_raw_mode()?;
1509    let mut stdout = io::stdout();
1510    execute!(stdout, EnterAlternateScreen)?;
1511    let backend = CrosstermBackend::new(stdout);
1512    let mut terminal = Terminal::new(backend)?;
1513
1514    // Create dashboard instance and run it
1515    let result = async {
1516        let mut dashboard = Dashboard::new().await?;
1517        dashboard.run(&mut terminal).await
1518    };
1519    
1520    let result = tokio::task::block_in_place(|| {
1521        Handle::current().block_on(result)
1522    });
1523
1524    // Restore terminal
1525    disable_raw_mode()?;
1526    execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
1527    terminal.show_cursor()?;
1528
1529    result
1530}
1531
1532async fn launch_timer() -> Result<()> {
1533    // Setup terminal
1534    enable_raw_mode()?;
1535    let mut stdout = io::stdout();
1536    execute!(stdout, EnterAlternateScreen)?;
1537    let backend = CrosstermBackend::new(stdout);
1538    let mut terminal = Terminal::new(backend)?;
1539
1540    // Create timer instance and run it
1541    let result = async {
1542        let mut timer = InteractiveTimer::new().await?;
1543        timer.run(&mut terminal).await
1544    };
1545    
1546    let result = tokio::task::block_in_place(|| {
1547        Handle::current().block_on(result)
1548    });
1549
1550    // Restore terminal
1551    disable_raw_mode()?;
1552    execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
1553    terminal.show_cursor()?;
1554
1555    result
1556}
1557
1558async fn merge_sessions(session_ids_str: String, project_name: Option<String>, notes: Option<String>) -> Result<()> {
1559    // Parse session IDs
1560    let session_ids: Result<Vec<i64>, _> = session_ids_str
1561        .split(',')
1562        .map(|s| s.trim().parse::<i64>())
1563        .collect();
1564    
1565    let session_ids = session_ids.map_err(|_| anyhow::anyhow!("Invalid session IDs format. Use comma-separated numbers like '1,2,3'"))?;
1566    
1567    if session_ids.len() < 2 {
1568        return Err(anyhow::anyhow!("At least 2 sessions are required for merging"));
1569    }
1570    
1571    // Get target project ID if specified
1572    let mut target_project_id = None;
1573    if let Some(project) = project_name {
1574        let db_path = get_database_path()?;
1575        let db = Database::new(&db_path)?;
1576        
1577        // Try to find project by name first, then by ID
1578        if let Ok(project_id) = project.parse::<i64>() {
1579            if ProjectQueries::find_by_id(&db.connection, project_id)?.is_some() {
1580                target_project_id = Some(project_id);
1581            }
1582        } else if let Some(proj) = ProjectQueries::find_by_name(&db.connection, &project)? {
1583            target_project_id = proj.id;
1584        }
1585        
1586        if target_project_id.is_none() {
1587            return Err(anyhow::anyhow!("Project '{}' not found", project));
1588        }
1589    }
1590    
1591    // Perform the merge
1592    let db_path = get_database_path()?;
1593    let db = Database::new(&db_path)?;
1594    
1595    let merged_id = SessionQueries::merge_sessions(&db.connection, &session_ids, target_project_id, notes)?;
1596    
1597    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1598    println!("\x1b[36m│\x1b[0m        \x1b[1;37mSession Merge Complete\x1b[0m            \x1b[36m│\x1b[0m");
1599    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1600    println!("\x1b[36m│\x1b[0m Merged sessions: \x1b[33m{:<22}\x1b[0m \x1b[36m│\x1b[0m", session_ids.iter().map(|id| id.to_string()).collect::<Vec<_>>().join(", "));
1601    println!("\x1b[36m│\x1b[0m New session ID:  \x1b[32m{:<22}\x1b[0m \x1b[36m│\x1b[0m", merged_id);
1602    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1603    println!("\x1b[36m│\x1b[0m \x1b[32m✓ Sessions successfully merged\x1b[0m        \x1b[36m│\x1b[0m");
1604    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1605    
1606    Ok(())
1607}
1608
1609async fn split_session(session_id: i64, split_times_str: String, notes: Option<String>) -> Result<()> {
1610    // Parse split times
1611    let split_time_strings: Vec<&str> = split_times_str.split(',').map(|s| s.trim()).collect();
1612    let mut split_times = Vec::new();
1613    
1614    for time_str in split_time_strings {
1615        // Try to parse as time (HH:MM or HH:MM:SS)
1616        let datetime = if time_str.contains(':') {
1617            // Parse as time and combine with today's date
1618            let today = chrono::Local::now().date_naive();
1619            let time = chrono::NaiveTime::parse_from_str(time_str, "%H:%M")
1620                .or_else(|_| chrono::NaiveTime::parse_from_str(time_str, "%H:%M:%S"))
1621                .map_err(|_| anyhow::anyhow!("Invalid time format '{}'. Use HH:MM or HH:MM:SS", time_str))?;
1622            today.and_time(time).and_utc()
1623        } else {
1624            // Try to parse as full datetime
1625            chrono::DateTime::parse_from_rfc3339(time_str)
1626                .map_err(|_| anyhow::anyhow!("Invalid datetime format '{}'. Use HH:MM or RFC3339 format", time_str))?
1627                .to_utc()
1628        };
1629        
1630        split_times.push(datetime);
1631    }
1632    
1633    if split_times.is_empty() {
1634        return Err(anyhow::anyhow!("No valid split times provided"));
1635    }
1636    
1637    // Parse notes if provided
1638    let notes_list = notes.map(|n| {
1639        n.split(',').map(|s| s.trim().to_string()).collect::<Vec<String>>()
1640    });
1641    
1642    // Perform the split
1643    let db_path = get_database_path()?;
1644    let db = Database::new(&db_path)?;
1645    
1646    let new_session_ids = SessionQueries::split_session(&db.connection, session_id, &split_times, notes_list)?;
1647    
1648    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1649    println!("\x1b[36m│\x1b[0m        \x1b[1;37mSession Split Complete\x1b[0m            \x1b[36m│\x1b[0m");
1650    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1651    println!("\x1b[36m│\x1b[0m Original session: \x1b[33m{:<20}\x1b[0m \x1b[36m│\x1b[0m", session_id);
1652    println!("\x1b[36m│\x1b[0m Split points:     \x1b[90m{:<20}\x1b[0m \x1b[36m│\x1b[0m", split_times.len());
1653    println!("\x1b[36m│\x1b[0m New sessions:     \x1b[32m{:<20}\x1b[0m \x1b[36m│\x1b[0m", new_session_ids.iter().map(|id| id.to_string()).collect::<Vec<_>>().join(", "));
1654    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1655    println!("\x1b[36m│\x1b[0m \x1b[32m✓ Session successfully split\x1b[0m          \x1b[36m│\x1b[0m");
1656    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1657    
1658    Ok(())
1659}
1660
1661async fn launch_history() -> Result<()> {
1662    enable_raw_mode()?;
1663    let mut stdout = io::stdout();
1664    execute!(stdout, EnterAlternateScreen)?;
1665    let backend = CrosstermBackend::new(stdout);
1666    let mut terminal = Terminal::new(backend)?;
1667
1668    let result = async {
1669        let mut browser = SessionHistoryBrowser::new().await?;
1670        browser.run(&mut terminal).await
1671    };
1672    
1673    let result = tokio::task::block_in_place(|| {
1674        Handle::current().block_on(result)
1675    });
1676
1677    disable_raw_mode()?;
1678    execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
1679    terminal.show_cursor()?;
1680
1681    result
1682}
1683
1684async fn handle_goal_action(action: GoalAction) -> Result<()> {
1685    match action {
1686        GoalAction::Create { name, target_hours, project, description, start_date, end_date } => {
1687            create_goal(name, target_hours, project, description, start_date, end_date).await
1688        }
1689        GoalAction::List { project } => {
1690            list_goals(project).await
1691        }
1692        GoalAction::Update { id, hours } => {
1693            update_goal_progress(id, hours).await
1694        }
1695    }
1696}
1697
1698async fn create_goal(name: String, target_hours: f64, project: Option<String>, description: Option<String>, start_date: Option<String>, end_date: Option<String>) -> Result<()> {
1699    let db_path = get_database_path()?;
1700    let db = Database::new(&db_path)?;
1701    
1702    let project_id = if let Some(proj_name) = project {
1703        match ProjectQueries::find_by_name(&db.connection, &proj_name)? {
1704            Some(p) => p.id,
1705            None => {
1706                println!("\x1b[31m✗ Project '{}' not found\x1b[0m", proj_name);
1707                return Ok(());
1708            }
1709        }
1710    } else {
1711        None
1712    };
1713    
1714    let start = start_date.and_then(|d| chrono::NaiveDate::parse_from_str(&d, "%Y-%m-%d").ok());
1715    let end = end_date.and_then(|d| chrono::NaiveDate::parse_from_str(&d, "%Y-%m-%d").ok());
1716    
1717    let mut goal = Goal::new(name.clone(), target_hours);
1718    if let Some(pid) = project_id {
1719        goal = goal.with_project(pid);
1720    }
1721    if let Some(desc) = description {
1722        goal = goal.with_description(desc);
1723    }
1724    goal = goal.with_dates(start, end);
1725    
1726    goal.validate()?;
1727    let goal_id = GoalQueries::create(&db.connection, &goal)?;
1728    
1729    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1730    println!("\x1b[36m│\x1b[0m           \x1b[1;37mGoal Created\x1b[0m                   \x1b[36m│\x1b[0m");
1731    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1732    println!("\x1b[36m│\x1b[0m Name:     \x1b[1;33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&name, 27));
1733    println!("\x1b[36m│\x1b[0m Target:   \x1b[32m{:<27}\x1b[0m \x1b[36m│\x1b[0m", format!("{} hours", target_hours));
1734    println!("\x1b[36m│\x1b[0m ID:       \x1b[90m{:<27}\x1b[0m \x1b[36m│\x1b[0m", goal_id);
1735    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1736    println!("\x1b[36m│\x1b[0m \x1b[32m✓ Goal created successfully\x1b[0m             \x1b[36m│\x1b[0m");
1737    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1738    
1739    Ok(())
1740}
1741
1742async fn list_goals(project: Option<String>) -> Result<()> {
1743    let db_path = get_database_path()?;
1744    let db = Database::new(&db_path)?;
1745    
1746    let project_id = if let Some(proj_name) = &project {
1747        match ProjectQueries::find_by_name(&db.connection, proj_name)? {
1748            Some(p) => p.id,
1749            None => {
1750                println!("\x1b[31m✗ Project '{}' not found\x1b[0m", proj_name);
1751                return Ok(());
1752            }
1753        }
1754    } else {
1755        None
1756    };
1757    
1758    let goals = GoalQueries::list_by_project(&db.connection, project_id)?;
1759    
1760    if goals.is_empty() {
1761        println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1762        println!("\x1b[36m│\x1b[0m              \x1b[1;37mNo Goals\x1b[0m                    \x1b[36m│\x1b[0m");
1763        println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1764        return Ok(());
1765    }
1766    
1767    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1768    println!("\x1b[36m│\x1b[0m                \x1b[1;37mGoals\x1b[0m                      \x1b[36m│\x1b[0m");
1769    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1770    
1771    for goal in &goals {
1772        let progress_pct = goal.progress_percentage();
1773        println!("\x1b[36m│\x1b[0m 🎯 \x1b[1;33m{:<25}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&goal.name, 25));
1774        println!("\x1b[36m│\x1b[0m    Progress: \x1b[32m{:.1}%\x1b[0m ({:.1}h / {:.1}h)     \x1b[36m│\x1b[0m", 
1775            progress_pct, goal.current_progress, goal.target_hours);
1776        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
1777    }
1778    
1779    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1780    Ok(())
1781}
1782
1783async fn update_goal_progress(id: i64, hours: f64) -> Result<()> {
1784    let db_path = get_database_path()?;
1785    let db = Database::new(&db_path)?;
1786    
1787    GoalQueries::update_progress(&db.connection, id, hours)?;
1788    println!("\x1b[32m✓ Updated goal {} progress by {} hours\x1b[0m", id, hours);
1789    Ok(())
1790}
1791
1792async fn show_insights(period: Option<String>, project: Option<String>) -> Result<()> {
1793    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1794    println!("\x1b[36m│\x1b[0m        \x1b[1;37mProductivity Insights\x1b[0m              \x1b[36m│\x1b[0m");
1795    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1796    println!("\x1b[36m│\x1b[0m Period:   \x1b[33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", period.as_deref().unwrap_or("all"));
1797    if let Some(proj) = project {
1798        println!("\x1b[36m│\x1b[0m Project:  \x1b[33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&proj, 27));
1799    }
1800    println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
1801    println!("\x1b[36m│\x1b[0m \x1b[33m⚠  Insights calculation in progress...\x1b[0m  \x1b[36m│\x1b[0m");
1802    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1803    Ok(())
1804}
1805
1806async fn show_summary(period: String, from: Option<String>) -> Result<()> {
1807    let db_path = get_database_path()?;
1808    let db = Database::new(&db_path)?;
1809    
1810    let start_date = if let Some(from_str) = from {
1811        chrono::NaiveDate::parse_from_str(&from_str, "%Y-%m-%d")?
1812    } else {
1813        match period.as_str() {
1814            "week" => chrono::Local::now().date_naive() - chrono::Duration::days(7),
1815            "month" => chrono::Local::now().date_naive() - chrono::Duration::days(30),
1816            _ => chrono::Local::now().date_naive(),
1817        }
1818    };
1819    
1820    let insight_data = match period.as_str() {
1821        "week" => InsightQueries::calculate_weekly_summary(&db.connection, start_date)?,
1822        "month" => InsightQueries::calculate_monthly_summary(&db.connection, start_date)?,
1823        _ => return Err(anyhow::anyhow!("Invalid period. Use 'week' or 'month'")),
1824    };
1825    
1826    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1827    println!("\x1b[36m│\x1b[0m         \x1b[1;37m{} Summary\x1b[0m                  \x1b[36m│\x1b[0m", period);
1828    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1829    println!("\x1b[36m│\x1b[0m Total Hours:  \x1b[32m{:<23}\x1b[0m \x1b[36m│\x1b[0m", format!("{:.1}h", insight_data.total_hours));
1830    println!("\x1b[36m│\x1b[0m Sessions:     \x1b[33m{:<23}\x1b[0m \x1b[36m│\x1b[0m", insight_data.sessions_count);
1831    println!("\x1b[36m│\x1b[0m Avg Session:  \x1b[33m{:<23}\x1b[0m \x1b[36m│\x1b[0m", format!("{:.1}h", insight_data.avg_session_duration));
1832    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1833    Ok(())
1834}
1835
1836async fn compare_projects(projects: String, _from: Option<String>, _to: Option<String>) -> Result<()> {
1837    let _project_names: Vec<&str> = projects.split(',').map(|s| s.trim()).collect();
1838    
1839    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1840    println!("\x1b[36m│\x1b[0m        \x1b[1;37mProject Comparison\x1b[0m                \x1b[36m│\x1b[0m");
1841    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1842    println!("\x1b[36m│\x1b[0m Projects: \x1b[33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&projects, 27));
1843    println!("\x1b[36m│\x1b[0m \x1b[33m⚠  Comparison feature in development\x1b[0m    \x1b[36m│\x1b[0m");
1844    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1845    Ok(())
1846}
1847
1848async fn handle_estimate_action(action: EstimateAction) -> Result<()> {
1849    match action {
1850        EstimateAction::Create { project, task, hours, due_date } => {
1851            create_estimate(project, task, hours, due_date).await
1852        }
1853        EstimateAction::Record { id, hours } => {
1854            record_actual_time(id, hours).await
1855        }
1856        EstimateAction::List { project } => {
1857            list_estimates(project).await
1858        }
1859    }
1860}
1861
1862async fn create_estimate(project: String, task: String, hours: f64, due_date: Option<String>) -> Result<()> {
1863    let db_path = get_database_path()?;
1864    let db = Database::new(&db_path)?;
1865    
1866    let project_obj = ProjectQueries::find_by_name(&db.connection, &project)?
1867        .ok_or_else(|| anyhow::anyhow!("Project '{}' not found", project))?;
1868    
1869    let due = due_date.and_then(|d| chrono::NaiveDate::parse_from_str(&d, "%Y-%m-%d").ok());
1870    
1871    let mut estimate = TimeEstimate::new(project_obj.id.unwrap(), task.clone(), hours);
1872    estimate.due_date = due;
1873    
1874    let estimate_id = TimeEstimateQueries::create(&db.connection, &estimate)?;
1875    
1876    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1877    println!("\x1b[36m│\x1b[0m      \x1b[1;37mTime Estimate Created\x1b[0m              \x1b[36m│\x1b[0m");
1878    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1879    println!("\x1b[36m│\x1b[0m Task:      \x1b[1;33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&task, 27));
1880    println!("\x1b[36m│\x1b[0m Estimate:  \x1b[32m{:<27}\x1b[0m \x1b[36m│\x1b[0m", format!("{} hours", hours));
1881    println!("\x1b[36m│\x1b[0m ID:        \x1b[90m{:<27}\x1b[0m \x1b[36m│\x1b[0m", estimate_id);
1882    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1883    Ok(())
1884}
1885
1886async fn record_actual_time(id: i64, hours: f64) -> Result<()> {
1887    let db_path = get_database_path()?;
1888    let db = Database::new(&db_path)?;
1889    
1890    TimeEstimateQueries::record_actual(&db.connection, id, hours)?;
1891    println!("\x1b[32m✓ Recorded {} hours for estimate {}\x1b[0m", hours, id);
1892    Ok(())
1893}
1894
1895async fn list_estimates(project: String) -> Result<()> {
1896    let db_path = get_database_path()?;
1897    let db = Database::new(&db_path)?;
1898    
1899    let project_obj = ProjectQueries::find_by_name(&db.connection, &project)?
1900        .ok_or_else(|| anyhow::anyhow!("Project '{}' not found", project))?;
1901    
1902    let estimates = TimeEstimateQueries::list_by_project(&db.connection, project_obj.id.unwrap())?;
1903    
1904    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1905    println!("\x1b[36m│\x1b[0m          \x1b[1;37mTime Estimates\x1b[0m                  \x1b[36m│\x1b[0m");
1906    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1907    
1908    for est in &estimates {
1909        let variance = est.variance();
1910        let variance_str = if let Some(v) = variance {
1911            if v > 0.0 {
1912                format!("\x1b[31m+{:.1}h over\x1b[0m", v)
1913            } else {
1914                format!("\x1b[32m{:.1}h under\x1b[0m", v.abs())
1915            }
1916        } else {
1917            "N/A".to_string()
1918        };
1919        
1920        println!("\x1b[36m│\x1b[0m 📋 \x1b[1;33m{:<25}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&est.task_name, 25));
1921        let actual_str = est.actual_hours.map(|h| format!("{:.1}h", h)).unwrap_or_else(|| "N/A".to_string());
1922        println!("\x1b[36m│\x1b[0m    Est: {}h | Actual: {} | {}  \x1b[36m│\x1b[0m", 
1923            est.estimated_hours,
1924            actual_str,
1925            variance_str
1926        );
1927        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
1928    }
1929    
1930    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1931    Ok(())
1932}
1933
1934async fn handle_branch_action(action: BranchAction) -> Result<()> {
1935    match action {
1936        BranchAction::List { project } => {
1937            list_branches(project).await
1938        }
1939        BranchAction::Stats { project, branch } => {
1940            show_branch_stats(project, branch).await
1941        }
1942    }
1943}
1944
1945async fn list_branches(project: String) -> Result<()> {
1946    let db_path = get_database_path()?;
1947    let db = Database::new(&db_path)?;
1948    
1949    let project_obj = ProjectQueries::find_by_name(&db.connection, &project)?
1950        .ok_or_else(|| anyhow::anyhow!("Project '{}' not found", project))?;
1951    
1952    let branches = GitBranchQueries::list_by_project(&db.connection, project_obj.id.unwrap())?;
1953    
1954    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1955    println!("\x1b[36m│\x1b[0m          \x1b[1;37mGit Branches\x1b[0m                   \x1b[36m│\x1b[0m");
1956    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1957    
1958    for branch in &branches {
1959        println!("\x1b[36m│\x1b[0m 🌿 \x1b[1;33m{:<25}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&branch.branch_name, 25));
1960        println!("\x1b[36m│\x1b[0m    Time: \x1b[32m{:<27}\x1b[0m \x1b[36m│\x1b[0m", format!("{:.1}h", branch.total_hours()));
1961        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
1962    }
1963    
1964    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1965    Ok(())
1966}
1967
1968async fn show_branch_stats(project: String, branch: Option<String>) -> Result<()> {
1969    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1970    println!("\x1b[36m│\x1b[0m        \x1b[1;37mBranch Statistics\x1b[0m                \x1b[36m│\x1b[0m");
1971    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1972    println!("\x1b[36m│\x1b[0m Project:  \x1b[33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&project, 27));
1973    if let Some(b) = branch {
1974        println!("\x1b[36m│\x1b[0m Branch:   \x1b[33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&b, 27));
1975    }
1976    println!("\x1b[36m│\x1b[0m \x1b[33m⚠  Branch stats in development\x1b[0m         \x1b[36m│\x1b[0m");
1977    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1978    Ok(())
1979}
1980
1981// Template management functions
1982async fn handle_template_action(action: TemplateAction) -> Result<()> {
1983    match action {
1984        TemplateAction::Create { name, description, tags, workspace_path } => {
1985            create_template(name, description, tags, workspace_path).await
1986        }
1987        TemplateAction::List => {
1988            list_templates().await
1989        }
1990        TemplateAction::Delete { template } => {
1991            delete_template(template).await
1992        }
1993        TemplateAction::Use { template, project_name, path } => {
1994            use_template(template, project_name, path).await
1995        }
1996    }
1997}
1998
1999async fn create_template(name: String, description: Option<String>, tags: Option<String>, workspace_path: Option<PathBuf>) -> Result<()> {
2000    let db_path = get_database_path()?;
2001    let db = Database::new(&db_path)?;
2002    
2003    let default_tags = tags
2004        .map(|t| t.split(',').map(|s| s.trim().to_string()).collect())
2005        .unwrap_or_default();
2006    
2007    let mut template = ProjectTemplate::new(name.clone())
2008        .with_tags(default_tags);
2009    
2010    let desc_clone = description.clone();
2011    if let Some(desc) = description {
2012        template = template.with_description(desc);
2013    }
2014    if let Some(path) = workspace_path {
2015        template = template.with_workspace_path(path);
2016    }
2017    
2018    let _template_id = TemplateQueries::create(&db.connection, &template)?;
2019    
2020    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
2021    println!("\x1b[36m│\x1b[0m         \x1b[1;37mTemplate Created\x1b[0m                  \x1b[36m│\x1b[0m");
2022    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
2023    println!("\x1b[36m│\x1b[0m Name:     \x1b[1;33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&name, 27));
2024    if let Some(desc) = &desc_clone {
2025        println!("\x1b[36m│\x1b[0m Desc:     \x1b[2;37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(desc, 27));
2026    }
2027    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
2028    Ok(())
2029}
2030
2031async fn list_templates() -> Result<()> {
2032    let db_path = get_database_path()?;
2033    let db = Database::new(&db_path)?;
2034    
2035    let templates = TemplateQueries::list_all(&db.connection)?;
2036    
2037    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
2038    println!("\x1b[36m│\x1b[0m          \x1b[1;37mTemplates\x1b[0m                      \x1b[36m│\x1b[0m");
2039    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
2040    
2041    if templates.is_empty() {
2042        println!("\x1b[36m│\x1b[0m No templates found.                      \x1b[36m│\x1b[0m");
2043    } else {
2044        for template in &templates {
2045            println!("\x1b[36m│\x1b[0m 📋 \x1b[1;33m{:<25}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&template.name, 25));
2046            if let Some(desc) = &template.description {
2047                println!("\x1b[36m│\x1b[0m    \x1b[2;37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(desc, 27));
2048            }
2049            println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
2050        }
2051    }
2052    
2053    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
2054    Ok(())
2055}
2056
2057async fn delete_template(_template: String) -> Result<()> {
2058    println!("\x1b[33m⚠  Template deletion not yet implemented\x1b[0m");
2059    Ok(())
2060}
2061
2062async fn use_template(template: String, project_name: String, path: Option<PathBuf>) -> Result<()> {
2063    let db_path = get_database_path()?;
2064    let db = Database::new(&db_path)?;
2065    
2066    let templates = TemplateQueries::list_all(&db.connection)?;
2067    let selected_template = templates.iter()
2068        .find(|t| t.name == template || t.id.map(|id| id.to_string()) == Some(template.clone()))
2069        .ok_or_else(|| anyhow::anyhow!("Template '{}' not found", template))?;
2070    
2071    // Initialize project with template
2072    let project_path = path.unwrap_or_else(|| env::current_dir().unwrap());
2073    let canonical_path = canonicalize_path(&project_path)?;
2074    
2075    // Check if project already exists
2076    if ProjectQueries::find_by_path(&db.connection, &canonical_path)?.is_some() {
2077        return Err(anyhow::anyhow!("Project already exists at this path"));
2078    }
2079    
2080    let git_hash = if is_git_repository(&canonical_path) {
2081        get_git_hash(&canonical_path)
2082    } else {
2083        None
2084    };
2085    
2086    let template_desc = selected_template.description.clone();
2087    let mut project = Project::new(project_name.clone(), canonical_path.clone())
2088        .with_git_hash(git_hash)
2089        .with_description(template_desc);
2090    
2091    let project_id = ProjectQueries::create(&db.connection, &project)?;
2092    project.id = Some(project_id);
2093    
2094    // Apply template tags (project-tag associations not yet implemented)
2095    // TODO: Implement project_tags table operations
2096    
2097    // Apply template goals
2098    for goal_def in &selected_template.default_goals {
2099        let mut goal = Goal::new(goal_def.name.clone(), goal_def.target_hours)
2100            .with_project(project_id);
2101        if let Some(desc) = &goal_def.description {
2102            goal = goal.with_description(desc.clone());
2103        }
2104        GoalQueries::create(&db.connection, &goal)?;
2105    }
2106    
2107    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
2108    println!("\x1b[36m│\x1b[0m    \x1b[1;37mProject Created from Template\x1b[0m          \x1b[36m│\x1b[0m");
2109    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
2110    println!("\x1b[36m│\x1b[0m Template: \x1b[33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&selected_template.name, 27));
2111    println!("\x1b[36m│\x1b[0m Project:   \x1b[33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&project_name, 27));
2112    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
2113    Ok(())
2114}
2115
2116// Workspace management functions
2117async fn handle_workspace_action(action: WorkspaceAction) -> Result<()> {
2118    match action {
2119        WorkspaceAction::Create { name, description, path } => {
2120            create_workspace(name, description, path).await
2121        }
2122        WorkspaceAction::List => {
2123            list_workspaces().await
2124        }
2125        WorkspaceAction::AddProject { workspace, project } => {
2126            add_project_to_workspace(workspace, project).await
2127        }
2128        WorkspaceAction::RemoveProject { workspace, project } => {
2129            remove_project_from_workspace(workspace, project).await
2130        }
2131        WorkspaceAction::Projects { workspace } => {
2132            list_workspace_projects(workspace).await
2133        }
2134        WorkspaceAction::Delete { workspace } => {
2135            delete_workspace(workspace).await
2136        }
2137    }
2138}
2139
2140async fn create_workspace(name: String, description: Option<String>, path: Option<PathBuf>) -> Result<()> {
2141    let db_path = get_database_path()?;
2142    let db = Database::new(&db_path)?;
2143    
2144    let mut workspace = Workspace::new(name.clone());
2145    let desc_clone = description.clone();
2146    if let Some(desc) = description {
2147        workspace = workspace.with_description(desc);
2148    }
2149    if let Some(p) = path {
2150        workspace = workspace.with_path(p);
2151    }
2152    
2153    let _workspace_id = WorkspaceQueries::create(&db.connection, &workspace)?;
2154    
2155    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
2156    println!("\x1b[36m│\x1b[0m        \x1b[1;37mWorkspace Created\x1b[0m                  \x1b[36m│\x1b[0m");
2157    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
2158    println!("\x1b[36m│\x1b[0m Name:     \x1b[1;33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&name, 27));
2159    if let Some(desc) = &desc_clone {
2160        println!("\x1b[36m│\x1b[0m Desc:     \x1b[2;37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(desc, 27));
2161    }
2162    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
2163    Ok(())
2164}
2165
2166async fn list_workspaces() -> Result<()> {
2167    let db_path = get_database_path()?;
2168    let db = Database::new(&db_path)?;
2169    
2170    let workspaces = WorkspaceQueries::list_all(&db.connection)?;
2171    
2172    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
2173    println!("\x1b[36m│\x1b[0m          \x1b[1;37mWorkspaces\x1b[0m                      \x1b[36m│\x1b[0m");
2174    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
2175    
2176    if workspaces.is_empty() {
2177        println!("\x1b[36m│\x1b[0m No workspaces found.                     \x1b[36m│\x1b[0m");
2178    } else {
2179        for workspace in &workspaces {
2180            println!("\x1b[36m│\x1b[0m 📁 \x1b[1;33m{:<25}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&workspace.name, 25));
2181            if let Some(desc) = &workspace.description {
2182                println!("\x1b[36m│\x1b[0m    \x1b[2;37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(desc, 27));
2183            }
2184            println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
2185        }
2186    }
2187    
2188    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
2189    Ok(())
2190}
2191
2192async fn add_project_to_workspace(workspace: String, project: String) -> Result<()> {
2193    let db_path = get_database_path()?;
2194    let db = Database::new(&db_path)?;
2195    
2196    // Find workspace by name
2197    let workspace_obj = WorkspaceQueries::find_by_name(&db.connection, &workspace)?
2198        .ok_or_else(|| anyhow::anyhow!("Workspace '{}' not found", workspace))?;
2199    
2200    // Find project by name
2201    let project_obj = ProjectQueries::find_by_name(&db.connection, &project)?
2202        .ok_or_else(|| anyhow::anyhow!("Project '{}' not found", project))?;
2203    
2204    let workspace_id = workspace_obj.id
2205        .ok_or_else(|| anyhow::anyhow!("Workspace ID is missing"))?;
2206    let project_id = project_obj.id
2207        .ok_or_else(|| anyhow::anyhow!("Project ID is missing"))?;
2208    
2209    if WorkspaceQueries::add_project(&db.connection, workspace_id, project_id)? {
2210        println!("\x1b[32m✓\x1b[0m Added project '\x1b[33m{}\x1b[0m' to workspace '\x1b[33m{}\x1b[0m'", project, workspace);
2211    } else {
2212        println!("\x1b[33m⚠\x1b[0m Project '\x1b[33m{}\x1b[0m' is already in workspace '\x1b[33m{}\x1b[0m'", project, workspace);
2213    }
2214    
2215    Ok(())
2216}
2217
2218async fn remove_project_from_workspace(workspace: String, project: String) -> Result<()> {
2219    let db_path = get_database_path()?;
2220    let db = Database::new(&db_path)?;
2221    
2222    // Find workspace by name
2223    let workspace_obj = WorkspaceQueries::find_by_name(&db.connection, &workspace)?
2224        .ok_or_else(|| anyhow::anyhow!("Workspace '{}' not found", workspace))?;
2225    
2226    // Find project by name
2227    let project_obj = ProjectQueries::find_by_name(&db.connection, &project)?
2228        .ok_or_else(|| anyhow::anyhow!("Project '{}' not found", project))?;
2229    
2230    let workspace_id = workspace_obj.id
2231        .ok_or_else(|| anyhow::anyhow!("Workspace ID is missing"))?;
2232    let project_id = project_obj.id
2233        .ok_or_else(|| anyhow::anyhow!("Project ID is missing"))?;
2234    
2235    if WorkspaceQueries::remove_project(&db.connection, workspace_id, project_id)? {
2236        println!("\x1b[32m✓\x1b[0m Removed project '\x1b[33m{}\x1b[0m' from workspace '\x1b[33m{}\x1b[0m'", project, workspace);
2237    } else {
2238        println!("\x1b[33m⚠\x1b[0m Project '\x1b[33m{}\x1b[0m' was not in workspace '\x1b[33m{}\x1b[0m'", project, workspace);
2239    }
2240    
2241    Ok(())
2242}
2243
2244async fn list_workspace_projects(workspace: String) -> Result<()> {
2245    let db_path = get_database_path()?;
2246    let db = Database::new(&db_path)?;
2247    
2248    // Find workspace by name
2249    let workspace_obj = WorkspaceQueries::find_by_name(&db.connection, &workspace)?
2250        .ok_or_else(|| anyhow::anyhow!("Workspace '{}' not found", workspace))?;
2251    
2252    let workspace_id = workspace_obj.id
2253        .ok_or_else(|| anyhow::anyhow!("Workspace ID is missing"))?;
2254    let projects = WorkspaceQueries::list_projects(&db.connection, workspace_id)?;
2255    
2256    if projects.is_empty() {
2257        println!("\x1b[33m⚠\x1b[0m No projects found in workspace '\x1b[33m{}\x1b[0m'", workspace);
2258        return Ok(());
2259    }
2260    
2261    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
2262    println!("\x1b[36m│\x1b[0m        \x1b[1;37mWorkspace Projects\x1b[0m               \x1b[36m│\x1b[0m");
2263    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
2264    println!("\x1b[36m│\x1b[0m Workspace: \x1b[33m{:<25}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&workspace, 25));
2265    println!("\x1b[36m│\x1b[0m Projects:  \x1b[32m{:<25}\x1b[0m \x1b[36m│\x1b[0m", format!("{} projects", projects.len()));
2266    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
2267    
2268    for project in &projects {
2269        let status_indicator = if !project.is_archived { "\x1b[32m●\x1b[0m" } else { "\x1b[31m○\x1b[0m" };
2270        println!("\x1b[36m│\x1b[0m {} \x1b[37m{:<33}\x1b[0m \x1b[36m│\x1b[0m", 
2271                status_indicator, 
2272                truncate_string(&project.name, 33));
2273    }
2274    
2275    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
2276    Ok(())
2277}
2278
2279async fn delete_workspace(workspace: String) -> Result<()> {
2280    let db_path = get_database_path()?;
2281    let db = Database::new(&db_path)?;
2282    
2283    // Find workspace by name
2284    let workspace_obj = WorkspaceQueries::find_by_name(&db.connection, &workspace)?
2285        .ok_or_else(|| anyhow::anyhow!("Workspace '{}' not found", workspace))?;
2286    
2287    let workspace_id = workspace_obj.id
2288        .ok_or_else(|| anyhow::anyhow!("Workspace ID is missing"))?;
2289    
2290    // Check if workspace has projects
2291    let projects = WorkspaceQueries::list_projects(&db.connection, workspace_id)?;
2292    if !projects.is_empty() {
2293        println!("\x1b[33m⚠\x1b[0m Cannot delete workspace '\x1b[33m{}\x1b[0m' - it contains {} project(s). Remove projects first.", 
2294                workspace, projects.len());
2295        return Ok(());
2296    }
2297    
2298    if WorkspaceQueries::delete(&db.connection, workspace_id)? {
2299        println!("\x1b[32m✓\x1b[0m Deleted workspace '\x1b[33m{}\x1b[0m'", workspace);
2300    } else {
2301        println!("\x1b[31m✗\x1b[0m Failed to delete workspace '\x1b[33m{}\x1b[0m'", workspace);
2302    }
2303    
2304    Ok(())
2305}
2306
2307// Calendar integration functions
2308async fn handle_calendar_action(action: CalendarAction) -> Result<()> {
2309    match action {
2310        CalendarAction::Add { name, start, end, event_type, project, description } => {
2311            add_calendar_event(name, start, end, event_type, project, description).await
2312        }
2313        CalendarAction::List { from, to, project } => {
2314            list_calendar_events(from, to, project).await
2315        }
2316        CalendarAction::Delete { id } => {
2317            delete_calendar_event(id).await
2318        }
2319    }
2320}
2321
2322async fn add_calendar_event(_name: String, _start: String, _end: Option<String>, _event_type: Option<String>, _project: Option<String>, _description: Option<String>) -> Result<()> {
2323    println!("\x1b[33m⚠  Calendar integration in development\x1b[0m");
2324    Ok(())
2325}
2326
2327async fn list_calendar_events(_from: Option<String>, _to: Option<String>, _project: Option<String>) -> Result<()> {
2328    println!("\x1b[33m⚠  Calendar integration in development\x1b[0m");
2329    Ok(())
2330}
2331
2332async fn delete_calendar_event(_id: i64) -> Result<()> {
2333    println!("\x1b[33m⚠  Calendar integration in development\x1b[0m");
2334    Ok(())
2335}
2336
2337// Issue tracker integration functions
2338async fn handle_issue_action(action: IssueAction) -> Result<()> {
2339    match action {
2340        IssueAction::Sync { project, tracker_type } => {
2341            sync_issues(project, tracker_type).await
2342        }
2343        IssueAction::List { project, status } => {
2344            list_issues(project, status).await
2345        }
2346        IssueAction::Link { session_id, issue_id } => {
2347            link_session_to_issue(session_id, issue_id).await
2348        }
2349    }
2350}
2351
2352async fn sync_issues(_project: String, _tracker_type: Option<String>) -> Result<()> {
2353    println!("\x1b[33m⚠  Issue tracker integration in development\x1b[0m");
2354    Ok(())
2355}
2356
2357async fn list_issues(_project: String, _status: Option<String>) -> Result<()> {
2358    println!("\x1b[33m⚠  Issue tracker integration in development\x1b[0m");
2359    Ok(())
2360}
2361
2362async fn link_session_to_issue(_session_id: i64, _issue_id: String) -> Result<()> {
2363    println!("\x1b[33m⚠  Issue tracker integration in development\x1b[0m");
2364    Ok(())
2365}
2366
2367// Client reporting functions
2368async fn handle_client_action(action: ClientAction) -> Result<()> {
2369    match action {
2370        ClientAction::Generate { client, from, to, projects, format } => {
2371            generate_client_report(client, from, to, projects, format).await
2372        }
2373        ClientAction::List { client } => {
2374            list_client_reports(client).await
2375        }
2376        ClientAction::View { id } => {
2377            view_client_report(id).await
2378        }
2379    }
2380}
2381
2382async fn generate_client_report(_client: String, _from: String, _to: String, _projects: Option<String>, _format: Option<String>) -> Result<()> {
2383    println!("\x1b[33m⚠  Client reporting in development\x1b[0m");
2384    Ok(())
2385}
2386
2387async fn list_client_reports(_client: Option<String>) -> Result<()> {
2388    println!("\x1b[33m⚠  Client reporting in development\x1b[0m");
2389    Ok(())
2390}
2391
2392async fn view_client_report(_id: i64) -> Result<()> {
2393    println!("\x1b[33m⚠  Client reporting in development\x1b[0m");
2394    Ok(())
2395}
2396
2397fn should_quit(event: crossterm::event::Event) -> bool {
2398    match event {
2399        crossterm::event::Event::Key(key) if key.kind == crossterm::event::KeyEventKind::Press => {
2400            matches!(key.code, crossterm::event::KeyCode::Char('q') | crossterm::event::KeyCode::Esc)
2401        }
2402        _ => false,
2403    }
2404}
2405
2406// Helper function for init_project with database connection
2407async fn init_project_with_db(
2408    name: Option<String>,
2409    canonical_path: Option<PathBuf>,
2410    description: Option<String>,
2411    conn: &rusqlite::Connection,
2412) -> Result<()> {
2413    let canonical_path = canonical_path.ok_or_else(|| anyhow::anyhow!("Canonical path required"))?;
2414    let project_name = name.unwrap_or_else(|| detect_project_name(&canonical_path));
2415
2416    // Check if project already exists
2417    if let Some(existing) = ProjectQueries::find_by_path(conn, &canonical_path)? {
2418        println!("\x1b[33m⚠  Project already exists:\x1b[0m {}", existing.name);
2419        return Ok(());
2420    }
2421
2422    // Get git hash if it's a git repository
2423    let git_hash = if is_git_repository(&canonical_path) {
2424        get_git_hash(&canonical_path)
2425    } else {
2426        None
2427    };
2428
2429    // Create project
2430    let mut project = Project::new(project_name.clone(), canonical_path.clone())
2431        .with_git_hash(git_hash.clone())
2432        .with_description(description.clone());
2433
2434    // Save to database
2435    let project_id = ProjectQueries::create(conn, &project)?;
2436    project.id = Some(project_id);
2437
2438    // Create .tempo marker file
2439    let marker_path = canonical_path.join(".tempo");
2440    if !marker_path.exists() {
2441        std::fs::write(&marker_path, format!("# Tempo time tracking project\nname: {}\n", project_name))?;
2442    }
2443
2444    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
2445    println!("\x1b[36m│\x1b[0m         \x1b[1;37mProject Initialized\x1b[0m               \x1b[36m│\x1b[0m");
2446    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
2447    println!("\x1b[36m│\x1b[0m Name:        \x1b[33m{:<25}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&project_name, 25));
2448    println!("\x1b[36m│\x1b[0m Path:        \x1b[37m{:<25}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&canonical_path.display().to_string(), 25));
2449    
2450    if let Some(desc) = &description {
2451        println!("\x1b[36m│\x1b[0m Description: \x1b[37m{:<25}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(desc, 25));
2452    }
2453    
2454    if is_git_repository(&canonical_path) {
2455        println!("\x1b[36m│\x1b[0m Git:         \x1b[32m{:<25}\x1b[0m \x1b[36m│\x1b[0m", "Repository detected");
2456        if let Some(hash) = &git_hash {
2457            println!("\x1b[36m│\x1b[0m Git Hash:    \x1b[37m{:<25}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(hash, 25));
2458        }
2459    }
2460    
2461    println!("\x1b[36m│\x1b[0m ID:          \x1b[37m{:<25}\x1b[0m \x1b[36m│\x1b[0m", project_id);
2462    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
2463    
2464    Ok(())
2465}
2466
2467// Show database connection pool statistics
2468async fn show_pool_stats() -> Result<()> {
2469    match get_pool_stats() {
2470        Ok(stats) => {
2471            println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
2472            println!("\x1b[36m│\x1b[0m        \x1b[1;37mDatabase Pool Statistics\x1b[0m          \x1b[36m│\x1b[0m");
2473            println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
2474            println!("\x1b[36m│\x1b[0m Total Created:    \x1b[32m{:<19}\x1b[0m \x1b[36m│\x1b[0m", stats.total_connections_created);
2475            println!("\x1b[36m│\x1b[0m Active:           \x1b[33m{:<19}\x1b[0m \x1b[36m│\x1b[0m", stats.active_connections);
2476            println!("\x1b[36m│\x1b[0m Available in Pool:\x1b[37m{:<19}\x1b[0m \x1b[36m│\x1b[0m", stats.connections_in_pool);
2477            println!("\x1b[36m│\x1b[0m Total Requests:   \x1b[37m{:<19}\x1b[0m \x1b[36m│\x1b[0m", stats.connection_requests);
2478            println!("\x1b[36m│\x1b[0m Timeouts:         \x1b[31m{:<19}\x1b[0m \x1b[36m│\x1b[0m", stats.connection_timeouts);
2479            println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
2480        }
2481        Err(_) => {
2482            println!("\x1b[33m⚠  Database pool not initialized or not available\x1b[0m");
2483            println!("   Using direct database connections as fallback");
2484        }
2485    }
2486    Ok(())
2487}