Skip to content

Commit 1a3e5db

Browse files
factorydroidFactory Bot
authored andcommitted
fix(cli): add date filtering options to sessions command
Fixes bounty issue #1662 Added --days, --since, and --until options to the sessions command to allow users to filter sessions by date range. Users can now: - Filter sessions from the last N days: cortex sessions --days 7 - Filter sessions since a date: cortex sessions --since 2024-01-01 - Filter sessions until a date: cortex sessions --until 2024-12-31 - Combine filters: cortex sessions --since 2024-01-01 --until 2024-06-30
1 parent 5579873 commit 1a3e5db

1 file changed

Lines changed: 150 additions & 35 deletions

File tree

cortex-cli/src/main.rs

Lines changed: 150 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,18 @@ struct SessionsCommand {
304304
/// Show all sessions including from other directories
305305
#[arg(long)]
306306
all: bool,
307+
308+
/// Filter sessions from the last N days
309+
#[arg(long, short = 'd')]
310+
days: Option<u32>,
311+
312+
/// Filter sessions since a specific date (YYYY-MM-DD format)
313+
#[arg(long)]
314+
since: Option<String>,
315+
316+
/// Filter sessions until a specific date (YYYY-MM-DD format)
317+
#[arg(long)]
318+
until: Option<String>,
307319
}
308320

309321
/// Config command.
@@ -478,7 +490,15 @@ async fn main() -> Result<()> {
478490
}
479491
},
480492
Some(Commands::Resume(resume_cli)) => run_resume(resume_cli).await,
481-
Some(Commands::Sessions(sessions_cli)) => list_sessions(sessions_cli.all).await,
493+
Some(Commands::Sessions(sessions_cli)) => {
494+
list_sessions(
495+
sessions_cli.all,
496+
sessions_cli.days,
497+
sessions_cli.since,
498+
sessions_cli.until,
499+
)
500+
.await
501+
}
482502
Some(Commands::Export(export_cli)) => export_cli.run().await,
483503
Some(Commands::Import(import_cli)) => import_cli.run().await,
484504
Some(Commands::Config(config_cli)) => show_config(config_cli).await,
@@ -676,7 +696,14 @@ async fn run_resume(resume_cli: ResumeCommand) -> Result<()> {
676696
Ok(())
677697
}
678698

679-
async fn list_sessions(show_all: bool) -> Result<()> {
699+
async fn list_sessions(
700+
show_all: bool,
701+
days: Option<u32>,
702+
since: Option<String>,
703+
until: Option<String>,
704+
) -> Result<()> {
705+
use chrono::{DateTime, NaiveDate, Utc};
706+
680707
let config = cortex_engine::Config::default();
681708
let sessions = cortex_engine::list_sessions(&config.cortex_home)?;
682709

@@ -689,58 +716,146 @@ async fn list_sessions(show_all: bool) -> Result<()> {
689716
return Ok(());
690717
}
691718

692-
let current_dir = std::env::current_dir().ok();
693-
let filtered: Vec<_> = if show_all {
694-
sessions.iter().collect()
719+
// Parse date filters
720+
let now = Utc::now();
721+
let since_date: Option<DateTime<Utc>> = if let Some(days_val) = days {
722+
Some(now - chrono::Duration::days(days_val as i64))
723+
} else if let Some(ref since_str) = since {
724+
match NaiveDate::parse_from_str(since_str, "%Y-%m-%d") {
725+
Ok(date) => Some(
726+
date.and_hms_opt(0, 0, 0)
727+
.unwrap()
728+
.and_local_timezone(Utc)
729+
.unwrap(),
730+
),
731+
Err(_) => {
732+
eprintln!(
733+
"Warning: Invalid --since date format '{}'. Expected YYYY-MM-DD.",
734+
since_str
735+
);
736+
None
737+
}
738+
}
695739
} else {
696-
sessions
697-
.iter()
698-
.filter(|s| current_dir.as_ref().is_none_or(|cwd| s.cwd == *cwd))
699-
.collect()
740+
None
741+
};
742+
743+
let until_date: Option<DateTime<Utc>> = if let Some(ref until_str) = until {
744+
match NaiveDate::parse_from_str(until_str, "%Y-%m-%d") {
745+
Ok(date) => Some(
746+
date.and_hms_opt(23, 59, 59)
747+
.unwrap()
748+
.and_local_timezone(Utc)
749+
.unwrap(),
750+
),
751+
Err(_) => {
752+
eprintln!(
753+
"Warning: Invalid --until date format '{}'. Expected YYYY-MM-DD.",
754+
until_str
755+
);
756+
None
757+
}
758+
}
759+
} else {
760+
None
700761
};
701762

702-
let display_sessions = if filtered.is_empty() {
763+
let current_dir = std::env::current_dir().ok();
764+
765+
// Apply all filters: directory, since, and until
766+
let filtered: Vec<_> = sessions
767+
.iter()
768+
.filter(|s| {
769+
// Directory filter
770+
if !show_all && current_dir.as_ref().is_some_and(|cwd| s.cwd != *cwd) {
771+
return false;
772+
}
773+
774+
// Parse session timestamp for date filtering
775+
if since_date.is_some() || until_date.is_some() {
776+
if let Ok(session_time) = DateTime::parse_from_rfc3339(&s.timestamp) {
777+
let session_utc = session_time.with_timezone(&Utc);
778+
if let Some(ref since) = since_date {
779+
if session_utc < *since {
780+
return false;
781+
}
782+
}
783+
if let Some(ref until) = until_date {
784+
if session_utc > *until {
785+
return false;
786+
}
787+
}
788+
}
789+
}
790+
791+
true
792+
})
793+
.collect();
794+
795+
let display_sessions = if filtered.is_empty() && since_date.is_none() && until_date.is_none() {
703796
&sessions
704797
} else {
705798
&filtered.iter().map(|s| (*s).clone()).collect()
706799
};
707800

708-
println!("Recent Sessions{}:", if show_all { " (all)" } else { "" });
709-
println!("{:-<80}", "");
801+
// Build header with active filters
802+
let mut filter_info = String::new();
803+
if show_all {
804+
filter_info.push_str(" (all)");
805+
}
806+
if let Some(days_val) = days {
807+
filter_info.push_str(&format!(" (last {} days)", days_val));
808+
} else {
809+
if since_date.is_some() {
810+
filter_info.push_str(&format!(" (since {})", since.as_deref().unwrap_or("")));
811+
}
812+
if until_date.is_some() {
813+
filter_info.push_str(&format!(" (until {})", until.as_deref().unwrap_or("")));
814+
}
815+
}
710816

711-
for session in display_sessions.iter().take(15) {
712-
let model = session.model.as_deref().unwrap_or("unknown");
713-
let date = if session.timestamp.len() >= 19 {
714-
session.timestamp[..19].replace('T', " ")
715-
} else {
716-
session.timestamp.clone()
717-
};
817+
println!("Recent Sessions{}:", filter_info);
818+
println!("{:-<80}", "");
718819

719-
let branch_info = session
720-
.git_branch
721-
.as_ref()
722-
.map(|b| format!(" [{b}]"))
723-
.unwrap_or_default();
820+
if display_sessions.is_empty() {
821+
println!("No sessions match the specified filters.");
822+
} else {
823+
for session in display_sessions.iter().take(15) {
824+
let model = session.model.as_deref().unwrap_or("unknown");
825+
let date = if session.timestamp.len() >= 19 {
826+
session.timestamp[..19].replace('T', " ")
827+
} else {
828+
session.timestamp.clone()
829+
};
724830

725-
println!(
726-
"{} | {} | {} msgs | {}{branch_info}",
727-
&session.id[..8.min(session.id.len())],
728-
date,
729-
session.message_count,
730-
model,
731-
);
732-
println!(" {}", session.cwd.display());
733-
}
831+
let branch_info = session
832+
.git_branch
833+
.as_ref()
834+
.map(|b| format!(" [{b}]"))
835+
.unwrap_or_default();
836+
837+
println!(
838+
"{} | {} | {} msgs | {}{branch_info}",
839+
&session.id[..8.min(session.id.len())],
840+
date,
841+
session.message_count,
842+
model,
843+
);
844+
println!(" {}", session.cwd.display());
845+
}
734846

735-
if display_sessions.len() > 15 {
736-
println!("\n... and {} more sessions", display_sessions.len() - 15);
847+
if display_sessions.len() > 15 {
848+
println!("\n... and {} more sessions", display_sessions.len() - 15);
849+
}
737850
}
738851

739852
println!("\nTo resume: Cortex resume <session-id>");
740853
println!(" Cortex resume --last");
741854
if !show_all {
742855
println!(" Cortex sessions --all (show all directories)");
743856
}
857+
println!(" Cortex sessions --days 7 (last 7 days)");
858+
println!(" Cortex sessions --since 2024-01-01 --until 2024-12-31 (date range)");
744859
Ok(())
745860
}
746861

0 commit comments

Comments
 (0)