mi6_core/context/
process.rs1use std::process::Command;
8
9#[derive(Debug, Clone)]
11pub struct ClaudeProcessInfo {
12 pub pid: i32,
14 pub comm: String,
16}
17
18pub fn is_process_alive(pid: i32) -> bool {
36 Command::new("ps")
37 .args(["-p", &pid.to_string()])
38 .stdout(std::process::Stdio::null())
39 .stderr(std::process::Stdio::null())
40 .status()
41 .is_ok_and(|status| status.success())
42}
43
44pub fn get_parent_pid() -> Option<i32> {
59 let pid = std::process::id() as i32;
60 get_process_info(pid).map(|info| info.ppid)
61}
62
63pub fn find_claude_process() -> Option<ClaudeProcessInfo> {
70 find_claude_process_from_pid(get_parent_pid()?)
71}
72
73fn find_claude_process_from_pid(start_pid: i32) -> Option<ClaudeProcessInfo> {
75 let mut current_pid = start_pid;
76 let mut visited = std::collections::HashSet::new();
77
78 for _ in 0..50 {
80 if current_pid <= 1 || visited.contains(¤t_pid) {
81 break;
82 }
83 visited.insert(current_pid);
84
85 let Some(info) = get_process_info(current_pid) else {
87 break;
88 };
89
90 if is_claude_process(&info.comm) {
92 return Some(ClaudeProcessInfo {
93 pid: current_pid,
94 comm: info.comm,
95 });
96 }
97
98 current_pid = info.ppid;
100 }
101
102 None
103}
104
105struct ProcessInfo {
106 ppid: i32,
107 comm: String,
108}
109
110fn get_process_info(pid: i32) -> Option<ProcessInfo> {
112 let output = Command::new("ps")
114 .args(["-o", "ppid=,comm=", "-p", &pid.to_string()])
115 .output()
116 .ok()?;
117
118 if !output.status.success() {
119 return None;
120 }
121
122 let stdout = String::from_utf8_lossy(&output.stdout);
123 let line = stdout.trim();
124
125 let mut parts = line.split_whitespace();
127 let ppid: i32 = parts.next()?.parse().ok()?;
128 let comm = parts.next()?.to_string();
129
130 Some(ProcessInfo { ppid, comm })
131}
132
133fn is_claude_process(comm: &str) -> bool {
135 let comm_lower = comm.to_lowercase();
136
137 comm_lower == "node" || comm_lower.contains("claude") || comm_lower.contains("electron")
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn test_is_process_alive_self() {
147 assert!(is_process_alive(std::process::id() as i32));
149 }
150
151 #[test]
152 fn test_is_process_alive_invalid_pid() {
153 assert!(!is_process_alive(999_999_999));
155 }
156
157 #[test]
158 fn test_get_parent_pid() -> Result<(), String> {
159 let ppid = get_parent_pid().ok_or("expected parent pid")?;
161 assert!(ppid > 0);
162 Ok(())
163 }
164
165 #[test]
166 fn test_get_process_info_self() {
167 let pid = std::process::id() as i32;
169 let info = get_process_info(pid);
170 assert!(info.is_some(), "should get info for own process");
171 }
172
173 #[test]
174 fn test_get_process_info_invalid_pid() {
175 let info = get_process_info(999_999_999);
177 assert!(info.is_none());
178 }
179}