1use crate::core::session::SessionState;
2use crate::core::stats;
3use crate::tools::ctx_session::{self, SessionToolOptions};
4
5use super::common::{format_tokens_cli, load_shell_history};
6
7pub fn cmd_session_action(args: &[String]) {
8 let action = args.first().map(String::as_str);
9
10 match action {
11 Some("task") => {
12 let desc = args.get(1).map_or("(no description)", String::as_str);
13 #[cfg(unix)]
14 {
15 #[cfg(unix)]
16 if let Some(out) = crate::daemon_client::try_daemon_tool_call_blocking_text(
17 "ctx_session",
18 Some(serde_json::json!({ "action": "task", "value": desc })),
19 ) {
20 println!("{out}");
21 return;
22 }
23 }
24 let mut session = load_or_create_session();
25 let out =
26 ctx_session::handle(&mut session, &[], "task", Some(desc), None, default_opts());
27 let _ = session.save();
28 println!("{out}");
29 }
30 Some("finding") => {
31 let summary = args.get(1).map_or("(no summary)", String::as_str);
32 #[cfg(unix)]
33 {
34 #[cfg(unix)]
35 if let Some(out) = crate::daemon_client::try_daemon_tool_call_blocking_text(
36 "ctx_session",
37 Some(serde_json::json!({ "action": "finding", "value": summary })),
38 ) {
39 println!("{out}");
40 return;
41 }
42 }
43 let mut session = load_or_create_session();
44 let out = ctx_session::handle(
45 &mut session,
46 &[],
47 "finding",
48 Some(summary),
49 None,
50 default_opts(),
51 );
52 let _ = session.save();
53 println!("{out}");
54 }
55 Some("save") => {
56 #[cfg(unix)]
57 {
58 #[cfg(unix)]
59 if let Some(out) = crate::daemon_client::try_daemon_tool_call_blocking_text(
60 "ctx_session",
61 Some(serde_json::json!({ "action": "save" })),
62 ) {
63 println!("{out}");
64 return;
65 }
66 }
67 let mut session = load_or_create_session();
68 let out = ctx_session::handle(&mut session, &[], "save", None, None, default_opts());
69 println!("{out}");
70 }
71 Some("load") => {
72 let id = args.get(1).map(String::as_str);
73 #[cfg(unix)]
74 {
75 #[cfg(unix)]
76 if let Some(out) = crate::daemon_client::try_daemon_tool_call_blocking_text(
77 "ctx_session",
78 Some(serde_json::json!({ "action": "load", "session_id": id })),
79 ) {
80 println!("{out}");
81 return;
82 }
83 }
84 let mut session = SessionState::new();
85 let out = ctx_session::handle(&mut session, &[], "load", None, id, default_opts());
86 println!("{out}");
87 }
88 Some("status") => {
89 #[cfg(unix)]
90 {
91 #[cfg(unix)]
92 if let Some(out) = crate::daemon_client::try_daemon_tool_call_blocking_text(
93 "ctx_session",
94 Some(serde_json::json!({ "action": "status" })),
95 ) {
96 println!("{out}");
97 return;
98 }
99 }
100 let mut session = load_or_create_session();
101 let out = ctx_session::handle(&mut session, &[], "status", None, None, default_opts());
102 println!("{out}");
103 }
104 Some("decision") => {
105 let desc = args.get(1).map_or("(no description)", String::as_str);
106 #[cfg(unix)]
107 {
108 #[cfg(unix)]
109 if let Some(out) = crate::daemon_client::try_daemon_tool_call_blocking_text(
110 "ctx_session",
111 Some(serde_json::json!({ "action": "decision", "value": desc })),
112 ) {
113 println!("{out}");
114 return;
115 }
116 }
117 let mut session = load_or_create_session();
118 let out = ctx_session::handle(
119 &mut session,
120 &[],
121 "decision",
122 Some(desc),
123 None,
124 default_opts(),
125 );
126 let _ = session.save();
127 println!("{out}");
128 }
129 Some("reset") => {
130 #[cfg(unix)]
131 {
132 #[cfg(unix)]
133 if let Some(out) = crate::daemon_client::try_daemon_tool_call_blocking_text(
134 "ctx_session",
135 Some(serde_json::json!({ "action": "reset" })),
136 ) {
137 println!("{out}");
138 return;
139 }
140 }
141 let mut session = load_or_create_session();
142 let out = ctx_session::handle(&mut session, &[], "reset", None, None, default_opts());
143 println!("{out}");
144 }
145 None => {
146 cmd_session_legacy();
147 }
148 Some(other) => {
149 eprintln!("Unknown session action: {other}");
150 print_session_help();
151 std::process::exit(1);
152 }
153 }
154}
155
156fn load_or_create_session() -> SessionState {
157 SessionState::load_latest().unwrap_or_default()
158}
159
160fn default_opts() -> SessionToolOptions<'static> {
161 SessionToolOptions {
162 format: None,
163 path: None,
164 write: false,
165 privacy: None,
166 terse: None,
167 }
168}
169
170fn print_session_help() {
171 eprintln!(
172 "\
173lean-ctx session — Session management
174
175Usage:
176 lean-ctx session Show adoption statistics
177 lean-ctx session task <description> Set current task
178 lean-ctx session finding <summary> Record a finding
179 lean-ctx session decision <summary> Record a decision
180 lean-ctx session save Save current session
181 lean-ctx session load [session-id] Load a session (latest if no ID)
182 lean-ctx session status Show session status
183 lean-ctx session reset Reset session
184
185Examples:
186 lean-ctx session task \"implement JWT authentication\"
187 lean-ctx session finding \"auth.rs:42 — missing token validation\"
188 lean-ctx session save
189 lean-ctx session load"
190 );
191}
192
193fn cmd_session_legacy() {
194 let history = load_shell_history();
195 let gain = stats::load_stats();
196
197 let compressible_commands = [
198 "git ",
199 "npm ",
200 "yarn ",
201 "pnpm ",
202 "cargo ",
203 "docker ",
204 "kubectl ",
205 "gh ",
206 "pip ",
207 "pip3 ",
208 "eslint",
209 "prettier",
210 "ruff ",
211 "go ",
212 "golangci-lint",
213 "curl ",
214 "wget ",
215 "grep ",
216 "rg ",
217 "find ",
218 "ls ",
219 ];
220
221 let mut total = 0u32;
222 let mut via_hook = 0u32;
223
224 for line in &history {
225 let cmd = line.trim().to_lowercase();
226 if cmd.starts_with("lean-ctx") {
227 via_hook += 1;
228 total += 1;
229 } else {
230 for p in &compressible_commands {
231 if cmd.starts_with(p) {
232 total += 1;
233 break;
234 }
235 }
236 }
237 }
238
239 let pct = if total > 0 {
240 (via_hook as f64 / total as f64 * 100.0).round() as u32
241 } else {
242 0
243 };
244
245 println!("lean-ctx session statistics\n");
246 println!("Adoption: {pct}% ({via_hook}/{total} compressible commands)");
247 println!("Saved: {} tokens total", gain.total_saved);
248 println!("Calls: {} compressed", gain.total_calls);
249
250 if total > via_hook {
251 let missed = total - via_hook;
252 let est = missed * 150;
253 println!("Missed: {missed} commands (~{est} tokens saveable)");
254 }
255
256 println!("\nRun 'lean-ctx discover' for details on missed commands.");
257}
258
259pub fn cmd_wrapped(args: &[String]) {
260 let period = if args.iter().any(|a| a == "--month") {
261 "month"
262 } else if args.iter().any(|a| a == "--all") {
263 "all"
264 } else {
265 "week"
266 };
267
268 eprintln!("[DEPRECATED] Use `lean-ctx gain --wrapped`.");
269 println!(
270 "{}",
271 crate::tools::ctx_gain::handle("wrapped", Some(period), None, None)
272 );
273}
274
275pub fn cmd_sessions(args: &[String]) {
276 use crate::core::session::SessionState;
277
278 let action = args.first().map_or("list", std::string::String::as_str);
279
280 match action {
281 "list" | "ls" => {
282 let sessions = SessionState::list_sessions();
283 if sessions.is_empty() {
284 println!("No sessions found.");
285 return;
286 }
287 println!("Sessions ({}):\n", sessions.len());
288 for s in sessions.iter().take(20) {
289 let task = s.task.as_deref().unwrap_or("(no task)");
290 let task_short: String = task.chars().take(50).collect();
291 let date = s.updated_at.format("%Y-%m-%d %H:%M");
292 println!(
293 " {} | v{:3} | {:5} calls | {:>8} tok | {} | {}",
294 s.id,
295 s.version,
296 s.tool_calls,
297 format_tokens_cli(s.tokens_saved),
298 date,
299 task_short
300 );
301 }
302 if sessions.len() > 20 {
303 println!(" ... +{} more", sessions.len() - 20);
304 }
305 }
306 "show" => {
307 let id = args.get(1);
308 let session = if let Some(id) = id {
309 SessionState::load_by_id(id)
310 } else {
311 SessionState::load_latest()
312 };
313 match session {
314 Some(s) => println!("{}", s.format_compact()),
315 None => println!("Session not found."),
316 }
317 }
318 "cleanup" => {
319 let days = args.get(1).and_then(|s| s.parse::<i64>().ok()).unwrap_or(7);
320 let removed = SessionState::cleanup_old_sessions(days);
321 println!("Cleaned up {removed} session(s) older than {days} days.");
322 }
323 _ => {
324 eprintln!("Usage: lean-ctx sessions [list|show [id]|cleanup [days]]");
325 std::process::exit(1);
326 }
327 }
328}