cove_cli/commands/
kill.rs1use std::io::{self, BufRead};
2use std::process::{Command, Stdio};
3use std::thread;
4use std::time::{Duration, Instant};
5
6use crate::colors::*;
7use crate::events;
8use crate::tmux;
9
10const GRACEFUL_TIMEOUT: Duration = Duration::from_secs(15);
11
12fn write_end_event(window_name: &str) {
15 let pane_id = match tmux::get_claude_pane_id(window_name) {
16 Ok(id) => id,
17 Err(_) => return,
18 };
19 let session_id = match events::find_session_id(&pane_id) {
20 Some(id) => id,
21 None => return,
22 };
23 let cwd = tmux::list_windows()
24 .ok()
25 .and_then(|wins| {
26 wins.into_iter()
27 .find(|w| w.name == window_name)
28 .map(|w| w.pane_path)
29 })
30 .unwrap_or_default();
31 let _ = events::write_event(&session_id, &cwd, &pane_id, "end");
32}
33
34fn graceful_exit(window_name: &str) -> bool {
36 let _ = tmux::disable_respawn(window_name);
38
39 let _ = tmux::send_keys(window_name, &["C-c"]);
41 thread::sleep(Duration::from_millis(500));
42 let _ = tmux::send_keys(window_name, &["/exit", "Enter"]);
43
44 let start = Instant::now();
46 while start.elapsed() < GRACEFUL_TIMEOUT {
47 thread::sleep(Duration::from_secs(1));
48 match tmux::pane_command(window_name) {
49 Ok(cmd) if cmd != "claude" => return true,
50 Err(_) => return true,
51 _ => continue,
52 }
53 }
54 false
55}
56
57fn run_capture(window_name: &str) -> Option<String> {
59 let pane_id = tmux::get_claude_pane_id(window_name).ok()?;
60 let session_id = events::find_session_id(&pane_id)?;
61 let cwd = tmux::list_windows()
62 .ok()
63 .and_then(|wins| {
64 wins.into_iter()
65 .find(|w| w.name == window_name)
66 .map(|w| w.pane_path)
67 })
68 .unwrap_or_default();
69
70 let home = std::env::var("HOME").unwrap_or_default();
71 let capture_script = std::path::PathBuf::from(home).join(".claude/hooks/brain-os-capture.py");
72
73 if !capture_script.exists() {
74 return None;
75 }
76
77 let status = Command::new("python3")
78 .arg(&capture_script)
79 .arg("--session-id")
80 .arg(&session_id)
81 .arg("--cwd")
82 .arg(&cwd)
83 .stdout(Stdio::inherit())
84 .stderr(Stdio::inherit())
85 .status();
86
87 match status {
88 Ok(s) if s.success() => {
89 let _ = std::fs::write(format!("/tmp/cove-captured-{session_id}"), "");
91 Some(session_id)
92 }
93 _ => None,
94 }
95}
96
97pub fn run(name: &str, force: bool) -> Result<(), String> {
98 if !tmux::has_session() {
99 println!("{ANSI_OVERLAY}No active cove session.{ANSI_RESET}");
100 return Err(String::new());
101 }
102
103 write_end_event(name);
104
105 if !force {
106 run_capture(name);
107
108 println!("Press Enter to close {ANSI_PEACH}{name}{ANSI_RESET}, or Ctrl-C to cancel.");
109 let _ = io::stdin().lock().read_line(&mut String::new());
110
111 println!("Shutting down {ANSI_PEACH}{name}{ANSI_RESET} gracefully...");
112 graceful_exit(name);
113 }
114 tmux::kill_window(name)?;
115 println!("Killed: {ANSI_PEACH}{name}{ANSI_RESET}");
116 Ok(())
117}
118
119pub fn run_all(force: bool) -> Result<(), String> {
120 if !tmux::has_session() {
121 println!("{ANSI_OVERLAY}No active cove session.{ANSI_RESET}");
122 return Err(String::new());
123 }
124
125 let windows = tmux::list_windows().unwrap_or_default();
126 for win in &windows {
127 write_end_event(&win.name);
128 }
129
130 if !force {
131 for win in &windows {
133 run_capture(&win.name);
134 }
135
136 println!(
137 "\nPress Enter to close {} session(s), or Ctrl-C to cancel.",
138 windows.len()
139 );
140 let _ = io::stdin().lock().read_line(&mut String::new());
141
142 println!("Shutting down {} session(s) gracefully...", windows.len());
143 for win in &windows {
144 let exited = graceful_exit(&win.name);
145 let status = if exited { "exited" } else { "timed out" };
146 println!(" {}: {status}", win.name);
147 }
148 }
149
150 tmux::kill_session()?;
151 println!("Killed all sessions.");
152 Ok(())
153}