use crate::core::session::SessionState;
use crate::core::stats;
use crate::tools::ctx_session::{self, SessionToolOptions};
use super::common::{format_tokens_cli, load_shell_history};
pub fn cmd_session_action(args: &[String]) {
let action = args.first().map(String::as_str);
match action {
Some("task") => {
let desc = args.get(1).map_or("(no description)", String::as_str);
#[cfg(unix)]
{
#[cfg(unix)]
if let Some(out) = crate::daemon_client::try_daemon_tool_call_blocking_text(
"ctx_session",
Some(serde_json::json!({ "action": "task", "value": desc })),
) {
println!("{out}");
return;
}
}
let mut session = load_or_create_session();
let out =
ctx_session::handle(&mut session, &[], "task", Some(desc), None, default_opts());
let _ = session.save();
println!("{out}");
}
Some("finding") => {
let summary = args.get(1).map_or("(no summary)", String::as_str);
#[cfg(unix)]
{
#[cfg(unix)]
if let Some(out) = crate::daemon_client::try_daemon_tool_call_blocking_text(
"ctx_session",
Some(serde_json::json!({ "action": "finding", "value": summary })),
) {
println!("{out}");
return;
}
}
let mut session = load_or_create_session();
let out = ctx_session::handle(
&mut session,
&[],
"finding",
Some(summary),
None,
default_opts(),
);
let _ = session.save();
println!("{out}");
}
Some("save") => {
#[cfg(unix)]
{
#[cfg(unix)]
if let Some(out) = crate::daemon_client::try_daemon_tool_call_blocking_text(
"ctx_session",
Some(serde_json::json!({ "action": "save" })),
) {
println!("{out}");
return;
}
}
let mut session = load_or_create_session();
let out = ctx_session::handle(&mut session, &[], "save", None, None, default_opts());
println!("{out}");
}
Some("load") => {
let id = args.get(1).map(String::as_str);
#[cfg(unix)]
{
#[cfg(unix)]
if let Some(out) = crate::daemon_client::try_daemon_tool_call_blocking_text(
"ctx_session",
Some(serde_json::json!({ "action": "load", "session_id": id })),
) {
println!("{out}");
return;
}
}
let mut session = SessionState::new();
let out = ctx_session::handle(&mut session, &[], "load", None, id, default_opts());
println!("{out}");
}
Some("status") => {
#[cfg(unix)]
{
#[cfg(unix)]
if let Some(out) = crate::daemon_client::try_daemon_tool_call_blocking_text(
"ctx_session",
Some(serde_json::json!({ "action": "status" })),
) {
println!("{out}");
return;
}
}
let mut session = load_or_create_session();
let out = ctx_session::handle(&mut session, &[], "status", None, None, default_opts());
println!("{out}");
}
Some("decision") => {
let desc = args.get(1).map_or("(no description)", String::as_str);
#[cfg(unix)]
{
#[cfg(unix)]
if let Some(out) = crate::daemon_client::try_daemon_tool_call_blocking_text(
"ctx_session",
Some(serde_json::json!({ "action": "decision", "value": desc })),
) {
println!("{out}");
return;
}
}
let mut session = load_or_create_session();
let out = ctx_session::handle(
&mut session,
&[],
"decision",
Some(desc),
None,
default_opts(),
);
let _ = session.save();
println!("{out}");
}
Some("reset") => {
#[cfg(unix)]
{
#[cfg(unix)]
if let Some(out) = crate::daemon_client::try_daemon_tool_call_blocking_text(
"ctx_session",
Some(serde_json::json!({ "action": "reset" })),
) {
println!("{out}");
return;
}
}
let mut session = load_or_create_session();
let out = ctx_session::handle(&mut session, &[], "reset", None, None, default_opts());
println!("{out}");
}
None => {
cmd_session_legacy();
}
Some(other) => {
eprintln!("Unknown session action: {other}");
print_session_help();
std::process::exit(1);
}
}
}
fn load_or_create_session() -> SessionState {
SessionState::load_latest().unwrap_or_default()
}
fn default_opts() -> SessionToolOptions<'static> {
SessionToolOptions {
format: None,
path: None,
write: false,
privacy: None,
terse: None,
}
}
fn print_session_help() {
eprintln!(
"\
lean-ctx session — Session management
Usage:
lean-ctx session Show adoption statistics
lean-ctx session task <description> Set current task
lean-ctx session finding <summary> Record a finding
lean-ctx session decision <summary> Record a decision
lean-ctx session save Save current session
lean-ctx session load [session-id] Load a session (latest if no ID)
lean-ctx session status Show session status
lean-ctx session reset Reset session
Examples:
lean-ctx session task \"implement JWT authentication\"
lean-ctx session finding \"auth.rs:42 — missing token validation\"
lean-ctx session save
lean-ctx session load"
);
}
fn cmd_session_legacy() {
let history = load_shell_history();
let gain = stats::load_stats();
let compressible_commands = [
"git ",
"npm ",
"yarn ",
"pnpm ",
"cargo ",
"docker ",
"kubectl ",
"gh ",
"pip ",
"pip3 ",
"eslint",
"prettier",
"ruff ",
"go ",
"golangci-lint",
"curl ",
"wget ",
"grep ",
"rg ",
"find ",
"ls ",
];
let mut total = 0u32;
let mut via_hook = 0u32;
for line in &history {
let cmd = line.trim().to_lowercase();
if cmd.starts_with("lean-ctx") {
via_hook += 1;
total += 1;
} else {
for p in &compressible_commands {
if cmd.starts_with(p) {
total += 1;
break;
}
}
}
}
let pct = if total > 0 {
(via_hook as f64 / total as f64 * 100.0).round() as u32
} else {
0
};
println!("lean-ctx session statistics\n");
println!("Adoption: {pct}% ({via_hook}/{total} compressible commands)");
println!("Saved: {} tokens total", gain.total_saved);
println!("Calls: {} compressed", gain.total_calls);
if total > via_hook {
let missed = total - via_hook;
let est = missed * 150;
println!("Missed: {missed} commands (~{est} tokens saveable)");
}
println!("\nRun 'lean-ctx discover' for details on missed commands.");
}
pub fn cmd_wrapped(args: &[String]) {
let period = if args.iter().any(|a| a == "--month") {
"month"
} else if args.iter().any(|a| a == "--all") {
"all"
} else {
"week"
};
eprintln!("[DEPRECATED] Use `lean-ctx gain --wrapped`.");
println!(
"{}",
crate::tools::ctx_gain::handle("wrapped", Some(period), None, None)
);
}
pub fn cmd_sessions(args: &[String]) {
use crate::core::session::SessionState;
let action = args.first().map_or("list", std::string::String::as_str);
match action {
"list" | "ls" => {
let sessions = SessionState::list_sessions();
if sessions.is_empty() {
println!("No sessions found.");
return;
}
println!("Sessions ({}):\n", sessions.len());
for s in sessions.iter().take(20) {
let task = s.task.as_deref().unwrap_or("(no task)");
let task_short: String = task.chars().take(50).collect();
let date = s.updated_at.format("%Y-%m-%d %H:%M");
println!(
" {} | v{:3} | {:5} calls | {:>8} tok | {} | {}",
s.id,
s.version,
s.tool_calls,
format_tokens_cli(s.tokens_saved),
date,
task_short
);
}
if sessions.len() > 20 {
println!(" ... +{} more", sessions.len() - 20);
}
}
"show" => {
let id = args.get(1);
let session = if let Some(id) = id {
SessionState::load_by_id(id)
} else {
SessionState::load_latest()
};
match session {
Some(s) => println!("{}", s.format_compact()),
None => println!("Session not found."),
}
}
"cleanup" => {
let days = args.get(1).and_then(|s| s.parse::<i64>().ok()).unwrap_or(7);
let removed = SessionState::cleanup_old_sessions(days);
println!("Cleaned up {removed} session(s) older than {days} days.");
}
_ => {
eprintln!("Usage: lean-ctx sessions [list|show [id]|cleanup [days]]");
std::process::exit(1);
}
}
}