proc_cli/commands/
kill.rs

1//! `proc kill` - Kill processes
2//!
3//! Examples:
4//!   proc kill node          # Kill all Node.js processes
5//!   proc kill :3000         # Kill what's on port 3000
6//!   proc kill 1234          # Kill specific PID
7//!   proc kill node --yes    # Skip confirmation
8
9use crate::core::{resolve_target, Process};
10use crate::error::{ProcError, Result};
11use crate::ui::{OutputFormat, Printer};
12use clap::Args;
13use dialoguer::Confirm;
14
15/// Kill process(es)
16#[derive(Args, Debug)]
17pub struct KillCommand {
18    /// Target: process name, PID, or :port
19    pub target: String,
20
21    /// Skip confirmation prompt
22    #[arg(long, short = 'y')]
23    pub yes: bool,
24
25    /// Show what would be killed without actually killing
26    #[arg(long)]
27    pub dry_run: bool,
28
29    /// Output as JSON
30    #[arg(long, short = 'j')]
31    pub json: bool,
32
33    /// Show verbose output
34    #[arg(long, short = 'v')]
35    pub verbose: bool,
36
37    /// Send SIGTERM instead of SIGKILL (graceful)
38    #[arg(long, short = 'g')]
39    pub graceful: bool,
40}
41
42impl KillCommand {
43    pub fn execute(&self) -> Result<()> {
44        let format = if self.json {
45            OutputFormat::Json
46        } else {
47            OutputFormat::Human
48        };
49        let printer = Printer::new(format, self.verbose);
50
51        // Determine what to kill based on target format
52        let processes = self.resolve_target_processes()?;
53
54        if processes.is_empty() {
55            return Err(ProcError::ProcessNotFound(self.target.clone()));
56        }
57
58        // Dry run: just show what would be killed
59        if self.dry_run {
60            printer.warning(&format!(
61                "Dry run: would kill {} process{}",
62                processes.len(),
63                if processes.len() == 1 { "" } else { "es" }
64            ));
65            printer.print_processes(&processes);
66            return Ok(());
67        }
68
69        // Confirm before killing (unless --yes)
70        if !self.yes && !self.json {
71            self.print_confirmation_prompt(&processes);
72
73            let confirmed = Confirm::new()
74                .with_prompt(format!(
75                    "Kill {} process{}?",
76                    processes.len(),
77                    if processes.len() == 1 { "" } else { "es" }
78                ))
79                .default(false)
80                .interact()
81                .unwrap_or(false);
82
83            if !confirmed {
84                printer.warning("Cancelled");
85                return Ok(());
86            }
87        }
88
89        // Kill the processes
90        let mut killed = Vec::new();
91        let mut failed = Vec::new();
92
93        for proc in processes {
94            let result = if self.graceful {
95                proc.terminate()
96            } else {
97                proc.kill()
98            };
99
100            match result {
101                Ok(()) => killed.push(proc),
102                Err(e) => failed.push((proc, e.to_string())),
103            }
104        }
105
106        printer.print_kill_result(&killed, &failed);
107
108        if failed.is_empty() {
109            Ok(())
110        } else {
111            Err(ProcError::SignalError(format!(
112                "Failed to kill {} process(es)",
113                failed.len()
114            )))
115        }
116    }
117
118    /// Resolve the target to a list of processes
119    fn resolve_target_processes(&self) -> Result<Vec<Process>> {
120        resolve_target(&self.target)
121    }
122
123    fn print_confirmation_prompt(&self, processes: &[Process]) {
124        use colored::*;
125
126        println!(
127            "\n{} Found {} process{} to kill:\n",
128            "⚠".yellow().bold(),
129            processes.len().to_string().cyan().bold(),
130            if processes.len() == 1 { "" } else { "es" }
131        );
132
133        for proc in processes {
134            println!(
135                "  {} {} [PID {}] - CPU: {:.1}%, MEM: {:.1}MB",
136                "→".bright_black(),
137                proc.name.white().bold(),
138                proc.pid.to_string().cyan(),
139                proc.cpu_percent,
140                proc.memory_mb
141            );
142        }
143        println!();
144    }
145}