Skip to main content

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