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    /// Executes the kill command, forcefully terminating matched processes.
44    pub fn execute(&self) -> Result<()> {
45        let format = if self.json {
46            OutputFormat::Json
47        } else {
48            OutputFormat::Human
49        };
50        let printer = Printer::new(format, self.verbose);
51
52        // Determine what to kill based on target format
53        let processes = self.resolve_target_processes()?;
54
55        if processes.is_empty() {
56            return Err(ProcError::ProcessNotFound(self.target.clone()));
57        }
58
59        // Dry run: just show what would be killed
60        if self.dry_run {
61            printer.warning(&format!(
62                "Dry run: would kill {} process{}",
63                processes.len(),
64                if processes.len() == 1 { "" } else { "es" }
65            ));
66            printer.print_processes(&processes);
67            return Ok(());
68        }
69
70        // Confirm before killing (unless --yes)
71        if !self.yes && !self.json {
72            self.print_confirmation_prompt(&processes);
73
74            let confirmed = Confirm::new()
75                .with_prompt(format!(
76                    "Kill {} process{}?",
77                    processes.len(),
78                    if processes.len() == 1 { "" } else { "es" }
79                ))
80                .default(false)
81                .interact()
82                .unwrap_or(false);
83
84            if !confirmed {
85                printer.warning("Cancelled");
86                return Ok(());
87            }
88        }
89
90        // Kill the processes
91        let mut killed = Vec::new();
92        let mut failed = Vec::new();
93
94        for proc in processes {
95            let result = if self.graceful {
96                proc.terminate()
97            } else {
98                proc.kill()
99            };
100
101            match result {
102                Ok(()) => killed.push(proc),
103                Err(e) => failed.push((proc, e.to_string())),
104            }
105        }
106
107        printer.print_kill_result(&killed, &failed);
108
109        if failed.is_empty() {
110            Ok(())
111        } else {
112            Err(ProcError::SignalError(format!(
113                "Failed to kill {} process(es)",
114                failed.len()
115            )))
116        }
117    }
118
119    /// Resolve the target to a list of processes
120    fn resolve_target_processes(&self) -> Result<Vec<Process>> {
121        resolve_target(&self.target)
122    }
123
124    fn print_confirmation_prompt(&self, processes: &[Process]) {
125        use colored::*;
126
127        println!(
128            "\n{} Found {} process{} to kill:\n",
129            "⚠".yellow().bold(),
130            processes.len().to_string().cyan().bold(),
131            if processes.len() == 1 { "" } else { "es" }
132        );
133
134        for proc in processes {
135            println!(
136                "  {} {} [PID {}] - CPU: {:.1}%, MEM: {:.1}MB",
137                "→".bright_black(),
138                proc.name.white().bold(),
139                proc.pid.to_string().cyan(),
140                proc.cpu_percent,
141                proc.memory_mb
142            );
143        }
144        println!();
145    }
146}