Skip to main content

aster_cli/
project_tracker.rs

1use anyhow::{Context, Result};
2use aster::config::paths::Paths;
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::fs;
7use std::path::{Path, PathBuf};
8
9/// Structure to track project information
10#[derive(Debug, Serialize, Deserialize)]
11pub struct ProjectInfo {
12    /// The absolute path to the project directory
13    pub path: String,
14    /// Last time the project was accessed
15    pub last_accessed: DateTime<Utc>,
16    /// Last instruction sent to aster (if available)
17    pub last_instruction: Option<String>,
18    /// Last session ID associated with this project
19    pub last_session_id: Option<String>,
20}
21
22/// Structure to hold all tracked projects
23#[derive(Debug, Serialize, Deserialize)]
24pub struct ProjectTracker {
25    projects: HashMap<String, ProjectInfo>,
26}
27
28/// Project information with path as a separate field for easier access
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct ProjectInfoDisplay {
31    /// The absolute path to the project directory
32    pub path: String,
33    /// Last time the project was accessed
34    pub last_accessed: DateTime<Utc>,
35    /// Last instruction sent to aster (if available)
36    pub last_instruction: Option<String>,
37    /// Last session ID associated with this project
38    pub last_session_id: Option<String>,
39}
40
41impl ProjectTracker {
42    /// Get the path to the projects.json file
43    fn get_projects_file() -> Result<PathBuf> {
44        let projects_file = Paths::in_data_dir("projects.json");
45        if let Some(parent) = projects_file.parent() {
46            if !parent.exists() {
47                fs::create_dir_all(parent)?;
48            }
49        }
50
51        Ok(projects_file)
52    }
53
54    /// Load the project tracker from the projects.json file
55    pub fn load() -> Result<Self> {
56        let projects_file = Self::get_projects_file()?;
57
58        if projects_file.exists() {
59            let file_content = fs::read_to_string(&projects_file)?;
60            let tracker: ProjectTracker = serde_json::from_str(&file_content)
61                .context("Failed to parse projects.json file")?;
62            Ok(tracker)
63        } else {
64            // If the file doesn't exist, create a new empty tracker
65            Ok(ProjectTracker {
66                projects: HashMap::new(),
67            })
68        }
69    }
70
71    /// Save the project tracker to the projects.json file
72    pub fn save(&self) -> Result<()> {
73        let projects_file = Self::get_projects_file()?;
74        let json = serde_json::to_string_pretty(self)?;
75        fs::write(projects_file, json)?;
76        Ok(())
77    }
78
79    /// Update project information for the current directory
80    ///
81    /// # Arguments
82    /// * `project_dir` - The project directory to update
83    /// * `instruction` - Optional instruction that was sent to aster
84    /// * `session_id` - Optional session ID associated with this project
85    pub fn update_project(
86        &mut self,
87        project_dir: &Path,
88        instruction: Option<&str>,
89        session_id: Option<&str>,
90    ) -> Result<()> {
91        let dir_str = project_dir.to_string_lossy().to_string();
92
93        // Create or update the project entry
94        let project_info = self.projects.entry(dir_str.clone()).or_insert(ProjectInfo {
95            path: dir_str,
96            last_accessed: Utc::now(),
97            last_instruction: None,
98            last_session_id: None,
99        });
100
101        // Update the last accessed time
102        project_info.last_accessed = Utc::now();
103
104        // Update the last instruction if provided
105        if let Some(instr) = instruction {
106            project_info.last_instruction = Some(instr.to_string());
107        }
108
109        // Update the session ID if provided
110        if let Some(id) = session_id {
111            project_info.last_session_id = Some(id.to_string());
112        }
113
114        self.save()
115    }
116
117    /// List all tracked projects
118    ///
119    /// Returns a vector of ProjectInfoDisplay objects
120    pub fn list_projects(&self) -> Vec<ProjectInfoDisplay> {
121        self.projects
122            .values()
123            .map(|info| ProjectInfoDisplay {
124                path: info.path.clone(),
125                last_accessed: info.last_accessed,
126                last_instruction: info.last_instruction.clone(),
127                last_session_id: info.last_session_id.clone(),
128            })
129            .collect()
130    }
131}
132
133/// Update the project tracker with the current directory and optional instruction
134///
135/// # Arguments
136/// * `instruction` - Optional instruction that was sent to aster
137/// * `session_id` - Optional session ID associated with this project
138pub fn update_project_tracker(instruction: Option<&str>, session_id: Option<&str>) -> Result<()> {
139    let current_dir = std::env::current_dir()?;
140    let mut tracker = ProjectTracker::load()?;
141    tracker.update_project(&current_dir, instruction, session_id)
142}