Skip to content

Commit b362508

Browse files
factorydroidechobt
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 ef0b3bc commit b362508

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,
@@ -680,7 +700,14 @@ async fn run_resume(resume_cli: ResumeCommand) -> Result<()> {
680700
Ok(())
681701
}
682702

683-
async fn list_sessions(show_all: bool) -> Result<()> {
703+
async fn list_sessions(
704+
show_all: bool,
705+
days: Option<u32>,
706+
since: Option<String>,
707+
until: Option<String>,
708+
) -> Result<()> {
709+
use chrono::{DateTime, NaiveDate, Utc};
710+
684711
let config = cortex_engine::Config::default();
685712
let sessions = cortex_engine::list_sessions(&config.cortex_home)?;
686713

@@ -693,58 +720,146 @@ async fn list_sessions(show_all: bool) -> Result<()> {
693720
return Ok(());
694721
}
695722

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

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

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

715-
for session in display_sessions.iter().take(15) {
716-
let model = session.model.as_deref().unwrap_or("unknown");
717-
let date = if session.timestamp.len() >= 19 {
718-
session.timestamp[..19].replace('T', " ")
719-
} else {
720-
session.timestamp.clone()
721-
};
821+
println!("Recent Sessions{}:", filter_info);
822+
println!("{:-<80}", "");
722823

723-
let branch_info = session
724-
.git_branch
725-
.as_ref()
726-
.map(|b| format!(" [{b}]"))
727-
.unwrap_or_default();
824+
if display_sessions.is_empty() {
825+
println!("No sessions match the specified filters.");
826+
} else {
827+
for session in display_sessions.iter().take(15) {
828+
let model = session.model.as_deref().unwrap_or("unknown");
829+
let date = if session.timestamp.len() >= 19 {
830+
session.timestamp[..19].replace('T', " ")
831+
} else {
832+
session.timestamp.clone()
833+
};
728834

729-
println!(
730-
"{} | {} | {} msgs | {}{branch_info}",
731-
&session.id[..8.min(session.id.len())],
732-
date,
733-
session.message_count,
734-
model,
735-
);
736-
println!(" {}", session.cwd.display());
737-
}
835+
let branch_info = session
836+
.git_branch
837+
.as_ref()
838+
.map(|b| format!(" [{b}]"))
839+
.unwrap_or_default();
840+
841+
println!(
842+
"{} | {} | {} msgs | {}{branch_info}",
843+
&session.id[..8.min(session.id.len())],
844+
date,
845+
session.message_count,
846+
model,
847+
);
848+
println!(" {}", session.cwd.display());
849+
}
738850

739-
if display_sessions.len() > 15 {
740-
println!("\n... and {} more sessions", display_sessions.len() - 15);
851+
if display_sessions.len() > 15 {
852+
println!("\n... and {} more sessions", display_sessions.len() - 15);
853+
}
741854
}
742855

743856
println!("\nTo resume: Cortex resume <session-id>");
744857
println!(" Cortex resume --last");
745858
if !show_all {
746859
println!(" Cortex sessions --all (show all directories)");
747860
}
861+
println!(" Cortex sessions --days 7 (last 7 days)");
862+
println!(" Cortex sessions --since 2024-01-01 --until 2024-12-31 (date range)");
748863
Ok(())
749864
}
750865

0 commit comments

Comments
 (0)