1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use clap::{Parser, Subcommand, ValueEnum};
use clap_complete::Shell;
#[derive(Parser)]
#[command(name = "ports")]
#[command(version, about = "Modern cross-platform port inspector")]
pub struct Cli {
/// Port number or process name to query
pub query: Option<String>,
/// Output as JSON
#[arg(long, global = true)]
pub json: bool,
/// Watch mode: refresh continuously
#[arg(short, long, global = true)]
pub watch: bool,
/// Refresh interval in seconds (default: 1)
#[arg(short = 'n', long, default_value = "1", global = true)]
pub interval: f64,
/// Show established connections instead of listening ports
#[arg(short, long, global = true)]
pub connections: bool,
/// Sort results by field
#[arg(short, long, value_enum, global = true)]
pub sort: Option<SortField>,
/// Filter by protocol
#[arg(short, long, value_enum, global = true)]
pub protocol: Option<ProtocolFilter>,
/// Interactive mode: select a port to kill
#[arg(short, long, global = true)]
pub interactive: bool,
/// Treat query as a regular expression
#[arg(long, global = true)]
pub regex: bool,
/// Show process ancestry and source information
#[arg(long, global = true)]
pub why: bool,
#[command(subcommand)]
pub command: Option<Commands>,
}
#[derive(Clone, Copy, ValueEnum)]
pub enum SortField {
Port,
Pid,
Name,
}
#[derive(Clone, Copy, ValueEnum, PartialEq, Eq)]
pub enum ProtocolFilter {
Tcp,
Udp,
}
#[derive(Subcommand)]
pub enum Commands {
/// List all listening ports
List,
/// Kill process using a port or by name
Kill {
/// Port number or process name
target: String,
/// Skip confirmation prompt
#[arg(short, long)]
force: bool,
/// Kill all matching processes (instead of erroring on multiple matches)
#[arg(short, long)]
all: bool,
/// Search established connections in addition to listening ports
#[arg(long)]
connections: bool,
},
/// Interactive real-time view (like htop for ports)
Top {
/// Show connections instead of listening ports
#[arg(short, long)]
connections: bool,
},
/// Generate shell completions
Completions {
/// Shell to generate completions for
#[arg(value_enum)]
shell: Shell,
},
/// Show why a process is running (ancestry, source, supervisor)
Why {
/// Port number, process name, or PID to investigate
target: String,
},
/// Track port usage over time
History {
#[command(subcommand)]
action: HistoryAction,
},
}
#[derive(Subcommand)]
pub enum HistoryAction {
/// Record current port state (run periodically via cron)
Record {
/// Include established connections (not just listening ports)
#[arg(short, long)]
connections: bool,
},
/// Show recorded history
Show {
/// Filter by port number
#[arg(long)]
port: Option<u16>,
/// Filter by process name
#[arg(short = 'P', long)]
process: Option<String>,
/// Hours of history to show (default: 24)
#[arg(short = 'H', long, default_value = "24")]
hours: i64,
/// Maximum entries to show
#[arg(short, long, default_value = "100")]
limit: usize,
},
/// Show timeline for a specific port
Timeline {
/// Port number to show timeline for
port: u16,
/// Hours of history (default: 24)
#[arg(short = 'H', long, default_value = "24")]
hours: i64,
},
/// Show statistics about recorded history
Stats,
/// Clean up old history entries
Clean {
/// Hours of history to keep (default: 168 = 1 week)
#[arg(short, long, default_value = "168")]
keep: i64,
},
/// Show ports that appeared or disappeared between two snapshots
Diff {
/// Compare latest snapshot against this many snapshots ago (default: 1)
#[arg(short, long, default_value = "1")]
ago: usize,
},
}