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::{apply_filters, parse_targets, resolve_targets_excluding_self};
12use crate::error::{ProcError, Result};
13use crate::ui::Printer;
14use clap::Args;
15
16/// Kill process(es)
17#[derive(Args, Debug)]
18pub struct KillCommand {
19    /// Target(s): process name, PID, or :port (comma-separated for multiple)
20    pub target: String,
21
22    /// Skip confirmation prompt
23    #[arg(long, short = 'y')]
24    pub yes: bool,
25
26    /// Show what would be killed without actually killing
27    #[arg(long)]
28    pub dry_run: bool,
29
30    /// Output as JSON
31    #[arg(long, short = 'j')]
32    pub json: bool,
33
34    /// Show verbose output
35    #[arg(long, short = 'v')]
36    pub verbose: bool,
37
38    /// Send SIGTERM instead of SIGKILL (graceful)
39    #[arg(long, short = 'g')]
40    pub graceful: bool,
41
42    /// Filter by directory (defaults to current directory if no path given)
43    #[arg(long = "in", short = 'i', num_args = 0..=1, default_missing_value = ".")]
44    pub in_dir: Option<String>,
45
46    /// Filter by process name
47    #[arg(long = "by", short = 'b')]
48    pub by_name: Option<String>,
49}
50
51impl KillCommand {
52    /// Executes the kill command, forcefully terminating matched processes.
53    pub fn execute(&self) -> Result<()> {
54        let printer = Printer::from_flags(self.json, self.verbose);
55
56        // Parse comma-separated targets and resolve to processes
57        // Use resolve_targets_excluding_self to avoid killing ourselves
58        let targets = parse_targets(&self.target);
59        let (mut processes, not_found) = resolve_targets_excluding_self(&targets);
60
61        // Warn about targets that weren't found
62        if !not_found.is_empty() {
63            printer.warning(&format!("Not found: {}", not_found.join(", ")));
64        }
65
66        // Apply --in and --by filters
67        apply_filters(&mut processes, &self.in_dir, &self.by_name);
68
69        if processes.is_empty() {
70            return Err(ProcError::ProcessNotFound(self.target.clone()));
71        }
72
73        // Dry run: just show what would be killed
74        if self.dry_run {
75            printer.print_dry_run("kill", &processes);
76            return Ok(());
77        }
78
79        // Confirm before killing (unless --yes)
80        if !printer.ask_confirm("kill", &processes, self.yes)? {
81            return Ok(());
82        }
83
84        // Kill the processes
85        let mut killed = Vec::new();
86        let mut failed = Vec::new();
87
88        for proc in processes {
89            let result = if self.graceful {
90                proc.terminate()
91            } else {
92                proc.kill()
93            };
94
95            match result {
96                Ok(()) => killed.push(proc),
97                Err(e) => failed.push((proc, e.to_string())),
98            }
99        }
100
101        printer.print_kill_result(&killed, &failed);
102
103        if failed.is_empty() {
104            Ok(())
105        } else {
106            Err(ProcError::SignalError(format!(
107                "Failed to kill {} process(es)",
108                failed.len()
109            )))
110        }
111    }
112}