aster_cli/
project_tracker.rs1use 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#[derive(Debug, Serialize, Deserialize)]
11pub struct ProjectInfo {
12 pub path: String,
14 pub last_accessed: DateTime<Utc>,
16 pub last_instruction: Option<String>,
18 pub last_session_id: Option<String>,
20}
21
22#[derive(Debug, Serialize, Deserialize)]
24pub struct ProjectTracker {
25 projects: HashMap<String, ProjectInfo>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct ProjectInfoDisplay {
31 pub path: String,
33 pub last_accessed: DateTime<Utc>,
35 pub last_instruction: Option<String>,
37 pub last_session_id: Option<String>,
39}
40
41impl ProjectTracker {
42 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 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 Ok(ProjectTracker {
66 projects: HashMap::new(),
67 })
68 }
69 }
70
71 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 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 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 project_info.last_accessed = Utc::now();
103
104 if let Some(instr) = instruction {
106 project_info.last_instruction = Some(instr.to_string());
107 }
108
109 if let Some(id) = session_id {
111 project_info.last_session_id = Some(id.to_string());
112 }
113
114 self.save()
115 }
116
117 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
133pub 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(¤t_dir, instruction, session_id)
142}