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_excluding_self, 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        // Use resolve_targets_excluding_self to avoid killing ourselves
56        let targets = parse_targets(&self.target);
57        let (processes, not_found) = resolve_targets_excluding_self(&targets);
58
59        // Warn about targets that weren't found
60        for target in &not_found {
61            printer.warning(&format!("Target not found: {}", target));
62        }
63
64        if processes.is_empty() {
65            return Err(ProcError::ProcessNotFound(self.target.clone()));
66        }
67
68        // Dry run: just show what would be killed
69        if self.dry_run {
70            printer.print_processes(&processes);
71            printer.warning(&format!(
72                "Dry run: would kill {} process{}",
73                processes.len(),
74                if processes.len() == 1 { "" } else { "es" }
75            ));
76            return Ok(());
77        }
78
79        // Confirm before killing (unless --yes)
80        if !self.yes && !self.json {
81            self.print_confirmation_prompt(&processes);
82
83            let confirmed = Confirm::new()
84                .with_prompt(format!(
85                    "Kill {} process{}?",
86                    processes.len(),
87                    if processes.len() == 1 { "" } else { "es" }
88                ))
89                .default(false)
90                .interact()
91                .unwrap_or(false);
92
93            if !confirmed {
94                printer.warning("Cancelled");
95                return Ok(());
96            }
97        }
98
99        // Kill the processes
100        let mut killed = Vec::new();
101        let mut failed = Vec::new();
102
103        for proc in processes {
104            let result = if self.graceful {
105                proc.terminate()
106            } else {
107                proc.kill()
108            };
109
110            match result {
111                Ok(()) => killed.push(proc),
112                Err(e) => failed.push((proc, e.to_string())),
113            }
114        }
115
116        printer.print_kill_result(&killed, &failed);
117
118        if failed.is_empty() {
119            Ok(())
120        } else {
121            Err(ProcError::SignalError(format!(
122                "Failed to kill {} process(es)",
123                failed.len()
124            )))
125        }
126    }
127
128    fn print_confirmation_prompt(&self, processes: &[Process]) {
129        use colored::*;
130
131        println!(
132            "\n{} Found {} process{} to kill:\n",
133            "⚠".yellow().bold(),
134            processes.len().to_string().cyan().bold(),
135            if processes.len() == 1 { "" } else { "es" }
136        );
137
138        for proc in processes {
139            println!(
140                "  {} {} [PID {}] - CPU: {:.1}%, MEM: {:.1}MB",
141                "→".bright_black(),
142                proc.name.white().bold(),
143                proc.pid.to_string().cyan(),
144                proc.cpu_percent,
145                proc.memory_mb
146            );
147        }
148        println!();
149    }
150}