port-nuker 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,11 +26,20 @@ npx port-nuker 3000
26
26
  ## Usage
27
27
 
28
28
  ```bash
29
+ # Kill process on specific port
29
30
  nuke <PORT>
31
+
32
+ # Kill and wait until port is actually free
33
+ nuke <PORT> --wait
34
+
35
+ # List all active ports and select one to kill interactively
36
+ nuke list
30
37
  ```
31
38
 
32
39
  ### Examples
33
40
 
41
+ **Direct Port Killing:**
42
+
34
43
  ```bash
35
44
  # Kill process on port 3000
36
45
  nuke 3000
@@ -42,12 +51,48 @@ nuke 8080
42
51
  npx port-nuker 3000
43
52
  ```
44
53
 
54
+ **Interactive Port Selection:**
55
+
56
+ ```bash
57
+ # List all active ports with details
58
+ nuke list
59
+
60
+ # You'll see a table like this:
61
+ # ┌──────┬──────────┬──────────┬──────────────────────────────┬───────────────┬────────────┐
62
+ # │ Port │ PID │ Protocol │ Command │ User │ Memory │
63
+ # ├──────┼──────────┼──────────┼──────────────────────────────┼───────────────┼────────────┤
64
+ # │ 3000 │ 12345 │ TCP │ node.exe │ username │ 45,232 K │
65
+ # │ 8080 │ 67890 │ TCP │ java.exe │ username │ 128,456 K │
66
+ # └──────┴──────────┴──────────┴──────────────────────────────┴───────────────┴────────────┘
67
+ #
68
+ # Use arrow keys to select a process and press Enter to kill it
69
+ ```
70
+
71
+ **Wait Mode (Command Chaining):**
72
+
73
+ ```bash
74
+ # Kill and wait for port to be released (polls every 500ms)
75
+ nuke 3000 --wait
76
+
77
+ # Chain commands - start dev server only after port is free
78
+ nuke 3000 --wait && npm run dev
79
+
80
+ # Works with stubborn processes that take time to release sockets
81
+ nuke 8080 --wait && docker-compose up
82
+
83
+ # Timeout after 30 seconds if port isn't released
84
+ nuke 5000 --wait && echo "Port is free!"
85
+ ```
86
+
45
87
  ## Features
46
88
 
47
89
  - **Cross-Platform**: Works on Windows (using `netstat` + `taskkill`) and Unix-like systems (Linux/macOS using `lsof` + `kill`)
90
+ - **Interactive Mode**: Browse all active ports with `nuke list` and select which one to kill
91
+ - **Wait Mode**: Block until port is actually released with `--wait` flag for safe command chaining
92
+ - **Detailed Process Info**: See PID, command name, user, memory usage, and protocol for each port
48
93
  - **Fast**: Instantly frees up your port
49
- - **Safe**: Checks if the port matches exactly to avoid killing wrong processes (e.g., `nuke 80` won't kill `8080`)
50
- - **Zero Dependencies**: No external packages required
94
+ - **Safe**: Confirmation prompt before killing processes in interactive mode
95
+ - **Exact Port Matching**: `nuke 80` won't kill processes on `8080`
51
96
  - **Simple**: Just one command to remember
52
97
 
53
98
  ## Common Use Cases
package/index.js CHANGED
@@ -1,21 +1,346 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  const { exec } = require('child_process');
4
4
  const os = require('os');
5
+ const inquirer = require('inquirer');
6
+ const Table = require('cli-table3');
5
7
 
6
- const port = process.argv[2];
8
+ const args = process.argv.slice(2);
7
9
 
8
- if (!port) {
9
- console.error('Please specify a port to nuke. Usage: nuke <PORT>');
10
+ // Parse arguments
11
+ let port = null;
12
+ let shouldWait = false;
13
+ let command = null;
14
+
15
+ for (let i = 0; i < args.length; i++) {
16
+ if (args[i] === '--wait') {
17
+ shouldWait = true;
18
+ } else if (args[i] === 'list') {
19
+ command = 'list';
20
+ } else if (!isNaN(args[i])) {
21
+ port = args[i];
22
+ }
23
+ }
24
+
25
+ // Handle different commands
26
+ if (command === 'list') {
27
+ listPorts();
28
+ } else if (port) {
29
+ // nuke <PORT> [--wait]
30
+ nukePort(port, shouldWait);
31
+ } else {
32
+ console.error('Usage:');
33
+ console.error(' nuke <PORT> Kill process on specific port');
34
+ console.error(' nuke <PORT> --wait Kill process and wait until port is free');
35
+ console.error(' nuke list List all active ports and select one to kill');
10
36
  process.exit(1);
11
37
  }
12
38
 
13
- console.log(`Searching for process holding port ${port}...`);
39
+ // List all active ports with process details
40
+ function listPorts() {
41
+ const platform = os.platform();
42
+ let listCommand;
43
+
44
+ if (platform === 'win32') {
45
+ // Get network connections and process details
46
+ listCommand = 'netstat -ano';
47
+ } else {
48
+ // Unix-like: get detailed process info
49
+ listCommand = 'lsof -i -P -n';
50
+ }
51
+
52
+ console.log('Scanning for active ports...\n');
53
+
54
+ exec(listCommand, (error, stdout, stderr) => {
55
+ if (error) {
56
+ console.error(`Error scanning ports: ${error.message}`);
57
+ process.exit(1);
58
+ }
59
+
60
+ const processes = parseProcessList(stdout, platform);
61
+
62
+ if (processes.length === 0) {
63
+ console.log('No active ports found.');
64
+ process.exit(0);
65
+ }
66
+
67
+ displayPortsTable(processes);
68
+ promptKillProcess(processes);
69
+ });
70
+ }
71
+
72
+ // Parse process list based on platform
73
+ function parseProcessList(output, platform) {
74
+ const processMap = new Map();
75
+
76
+ if (platform === 'win32') {
77
+ const lines = output.trim().split('\n');
78
+
79
+ lines.forEach(line => {
80
+ const parts = line.trim().split(/\s+/);
81
+ // Proto Local-Address Foreign-Address State PID
82
+ // TCP 0.0.0.0:3000 0.0.0.0:0 LISTENING 1234
83
+
84
+ if (parts.length >= 5 && (parts[0] === 'TCP' || parts[0] === 'UDP')) {
85
+ const localAddress = parts[1];
86
+ const state = parts[3];
87
+ const pid = parts[4];
88
+
89
+ // Only show LISTENING ports
90
+ if (state === 'LISTENING') {
91
+ const lastColonIndex = localAddress.lastIndexOf(':');
92
+ const port = localAddress.substring(lastColonIndex + 1);
93
+ const protocol = parts[0];
94
+
95
+ const key = `${pid}-${port}`;
96
+ if (!processMap.has(key)) {
97
+ processMap.set(key, {
98
+ pid,
99
+ port,
100
+ protocol,
101
+ command: 'Loading...',
102
+ user: os.userInfo().username,
103
+ memory: 'N/A'
104
+ });
105
+ }
106
+ }
107
+ }
108
+ });
109
+
110
+ // Get process details for each PID
111
+ const pids = [...new Set([...processMap.values()].map(p => p.pid))];
112
+ getWindowsProcessDetails(pids, processMap);
113
+
114
+ } else {
115
+ // Unix-like systems
116
+ const lines = output.trim().split('\n');
117
+
118
+ lines.forEach((line, index) => {
119
+ if (index === 0) return; // Skip header
120
+
121
+ const parts = line.trim().split(/\s+/);
122
+ // COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
123
+
124
+ if (parts.length >= 9) {
125
+ const command = parts[0];
126
+ const pid = parts[1];
127
+ const user = parts[2];
128
+ const name = parts[parts.length - 1];
129
+
130
+ // Parse port from NAME (e.g., *:3000, 127.0.0.1:8080)
131
+ const portMatch = name.match(/:(\d+)$/);
132
+ if (portMatch) {
133
+ const port = portMatch[1];
134
+ const protocol = parts[7] === 'TCP' ? 'TCP' : 'UDP';
135
+
136
+ const key = `${pid}-${port}`;
137
+ if (!processMap.has(key)) {
138
+ processMap.set(key, {
139
+ pid,
140
+ port,
141
+ protocol,
142
+ command,
143
+ user,
144
+ memory: 'N/A'
145
+ });
146
+ }
147
+ }
148
+ }
149
+ });
150
+ }
151
+
152
+ return Array.from(processMap.values()).sort((a, b) =>
153
+ parseInt(a.port) - parseInt(b.port)
154
+ );
155
+ }
156
+
157
+ // Get detailed process info on Windows
158
+ function getWindowsProcessDetails(pids, processMap) {
159
+ if (pids.length === 0) return;
160
+
161
+ const tasklistCmd = `tasklist /FI "PID eq ${pids.join('" /FI "PID eq ')}" /FO CSV /NH`;
162
+
163
+ exec(tasklistCmd, (error, stdout) => {
164
+ if (!error && stdout) {
165
+ const lines = stdout.trim().split('\n');
166
+
167
+ lines.forEach(line => {
168
+ // "Image Name","PID","Session Name","Session#","Mem Usage"
169
+ const matches = line.match(/"([^"]+)"/g);
170
+ if (matches && matches.length >= 5) {
171
+ const imageName = matches[0].replace(/"/g, '');
172
+ const pid = matches[1].replace(/"/g, '');
173
+ const memory = matches[4].replace(/"/g, '');
174
+
175
+ // Update all entries with this PID
176
+ processMap.forEach(process => {
177
+ if (process.pid === pid) {
178
+ process.command = imageName;
179
+ process.memory = memory;
180
+ }
181
+ });
182
+ }
183
+ });
184
+ }
185
+ });
186
+ }
187
+
188
+ // Display ports in a formatted table
189
+ function displayPortsTable(processes) {
190
+ const table = new Table({
191
+ head: ['Port', 'PID', 'Protocol', 'Command', 'User', 'Memory'],
192
+ colWidths: [8, 10, 10, 30, 15, 12],
193
+ style: {
194
+ head: ['cyan', 'bold'],
195
+ border: ['gray']
196
+ }
197
+ });
198
+
199
+ processes.forEach(p => {
200
+ table.push([
201
+ p.port,
202
+ p.pid,
203
+ p.protocol,
204
+ p.command.length > 28 ? p.command.substring(0, 25) + '...' : p.command,
205
+ p.user.length > 13 ? p.user.substring(0, 10) + '...' : p.user,
206
+ p.memory
207
+ ]);
208
+ });
209
+
210
+ console.log(table.toString());
211
+ console.log('');
212
+ }
213
+
214
+ // Prompt user to select and kill a process
215
+ async function promptKillProcess(processes) {
216
+ const choices = processes.map(p => ({
217
+ name: `Port ${p.port} - ${p.command} (PID: ${p.pid})`,
218
+ value: p.pid
219
+ }));
220
+
221
+ choices.push(new inquirer.Separator());
222
+ choices.push({ name: 'Cancel', value: null });
223
+
224
+ try {
225
+ const answer = await inquirer.prompt([
226
+ {
227
+ type: 'list',
228
+ name: 'pid',
229
+ message: 'Select a process to kill:',
230
+ choices,
231
+ pageSize: 15
232
+ }
233
+ ]);
234
+
235
+ if (answer.pid) {
236
+ const selectedProcess = processes.find(p => p.pid === answer.pid);
237
+
238
+ const confirm = await inquirer.prompt([
239
+ {
240
+ type: 'confirm',
241
+ name: 'confirmed',
242
+ message: `Kill process ${selectedProcess.command} (PID: ${answer.pid}) on port ${selectedProcess.port}?`,
243
+ default: false
244
+ }
245
+ ]);
14
246
 
15
- function nukePort(port) {
247
+ if (confirm.confirmed) {
248
+ console.log(`\nNuking process ${answer.pid}...`);
249
+ killPid(answer.pid);
250
+ } else {
251
+ console.log('Cancelled.');
252
+ }
253
+ } else {
254
+ console.log('Cancelled.');
255
+ }
256
+ } catch (error) {
257
+ if (error.isTtyError) {
258
+ console.error('Interactive mode not supported in this environment.');
259
+ } else {
260
+ console.error('Selection cancelled.');
261
+ }
262
+ }
263
+ }
264
+
265
+ // Check if a port is currently in use
266
+ function checkPortInUse(port, callback) {
267
+ const platform = os.platform();
268
+ let checkCommand;
269
+
270
+ if (platform === 'win32') {
271
+ checkCommand = `netstat -ano | findstr :${port}`;
272
+ } else {
273
+ checkCommand = `lsof -i :${port} -t`;
274
+ }
275
+
276
+ exec(checkCommand, (error, stdout) => {
277
+ if (error || !stdout) {
278
+ // Port is free
279
+ callback(false);
280
+ return;
281
+ }
282
+
283
+ // Check if it's the exact port (for Windows)
284
+ if (platform === 'win32') {
285
+ const lines = stdout.trim().split('\n');
286
+ let found = false;
287
+
288
+ lines.forEach(line => {
289
+ const parts = line.trim().split(/\s+/);
290
+ if (parts.length > 1) {
291
+ const localAddress = parts[1];
292
+ const lastColonIndex = localAddress.lastIndexOf(':');
293
+ const portFound = localAddress.substring(lastColonIndex + 1);
294
+
295
+ if (portFound === port) {
296
+ found = true;
297
+ }
298
+ }
299
+ });
300
+
301
+ callback(found);
302
+ } else {
303
+ // Unix: lsof -t returns PIDs if port is in use
304
+ callback(stdout.trim().length > 0);
305
+ }
306
+ });
307
+ }
308
+
309
+ // Poll port until it's free
310
+ function pollPortUntilFree(port, maxWaitSeconds = 30) {
311
+ console.log(`Waiting for port ${port} to be released...`);
312
+
313
+ const pollInterval = 500; // ms
314
+ const maxAttempts = (maxWaitSeconds * 1000) / pollInterval;
315
+ let attempts = 0;
316
+
317
+ const interval = setInterval(() => {
318
+ checkPortInUse(port, (inUse) => {
319
+ if (!inUse) {
320
+ clearInterval(interval);
321
+ console.log(`\nPort ${port} is now free!`);
322
+ process.exit(0);
323
+ }
324
+
325
+ attempts++;
326
+ process.stdout.write('.');
327
+
328
+ if (attempts >= maxAttempts) {
329
+ clearInterval(interval);
330
+ console.error(`\nTimeout: Port ${port} is still in use after ${maxWaitSeconds} seconds`);
331
+ process.exit(1);
332
+ }
333
+ });
334
+ }, pollInterval);
335
+ }
336
+
337
+ // Original nuke port function
338
+ function nukePort(port, shouldWait = false) {
16
339
  const platform = os.platform();
17
340
  let findCommand;
18
341
 
342
+ console.log(`Searching for process holding port ${port}...`);
343
+
19
344
  if (platform === 'win32') {
20
345
  findCommand = `netstat -ano | findstr :${port}`;
21
346
  } else {
@@ -79,12 +404,19 @@ function nukePort(port) {
79
404
 
80
405
  pids.forEach(pid => {
81
406
  console.log(`Found process with PID: ${pid}. Nuking it...`);
82
- killPid(pid);
407
+ killPid(pid, () => {
408
+ if (shouldWait) {
409
+ // Wait a moment for the process to fully terminate
410
+ setTimeout(() => {
411
+ pollPortUntilFree(port);
412
+ }, 1000);
413
+ }
414
+ });
83
415
  });
84
416
  });
85
417
  }
86
418
 
87
- function killPid(pid) {
419
+ function killPid(pid, callback) {
88
420
  const platform = os.platform();
89
421
  let killCommand;
90
422
 
@@ -97,10 +429,10 @@ function killPid(pid) {
97
429
  exec(killCommand, (error, stdout, stderr) => {
98
430
  if (error) {
99
431
  console.error(`Failed to kill process ${pid}: ${error.message}`);
432
+ if (callback) callback(error);
100
433
  return;
101
434
  }
102
435
  console.log(`Successfully nuked process ${pid}.`);
436
+ if (callback) callback(null);
103
437
  });
104
438
  }
105
-
106
- nukePort(port);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "port-nuker",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "A simple CLI utility to kill processes holding a specific port. Solves EADDRINUSE errors instantly.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -27,13 +27,17 @@
27
27
  "license": "MIT",
28
28
  "repository": {
29
29
  "type": "git",
30
- "url": "https://github.com/yourusername/port-nuker.git"
30
+ "url": "https://github.com/alexgutscher26/Port-Nuker.git"
31
31
  },
32
32
  "bugs": {
33
- "url": "https://github.com/yourusername/port-nuker/issues"
33
+ "url": "https://github.com/alexgutscher26/port-nuker/issues"
34
34
  },
35
- "homepage": "https://github.com/yourusername/port-nuker#readme",
35
+ "homepage": "https://github.com/alexgutscher26/port-nuker#readme",
36
36
  "engines": {
37
37
  "node": ">=12.0.0"
38
+ },
39
+ "dependencies": {
40
+ "inquirer": "^8.2.6",
41
+ "cli-table3": "^0.6.3"
38
42
  }
39
43
  }
@@ -1,8 +0,0 @@
1
- # Global owners
2
- * @sickn33
3
-
4
- # Skills
5
- /skills/ @sickn33
6
-
7
- # Documentation
8
- *.md @sickn33
@@ -1,33 +0,0 @@
1
- ---
2
- name: Bug Report
3
- about: Create a report to help us improve the skills
4
- title: "[BUG] "
5
- labels: bug
6
- assignees: sickn33
7
- ---
8
-
9
- **Describe the bug**
10
- A clear and concise description of what the bug is.
11
-
12
- **To Reproduce**
13
- Steps to reproduce the behavior:
14
-
15
- 1. Go to '...'
16
- 2. Click on '...'
17
- 3. Scroll down to '...'
18
- 4. See error
19
-
20
- **Expected behavior**
21
- A clear and concise description of what you expected to happen.
22
-
23
- **Screenshots**
24
- If applicable, add screenshots to help explain your problem.
25
-
26
- **Environment (please complete the following information):**
27
-
28
- - OS: [e.g. macOS, Windows]
29
- - Tool: [e.g. Claude Code, Antigravity]
30
- - Version [if known]
31
-
32
- **Additional context**
33
- Add any other context about the problem here.
@@ -1,19 +0,0 @@
1
- ---
2
- name: Skill Request
3
- about: Suggest a new skill for the collection
4
- title: "[REQ] "
5
- labels: enhancement
6
- assignees: sickn33
7
- ---
8
-
9
- **Is your feature request related to a problem? Please describe.**
10
- A clear and concise description of what the problem is. Ex: I'm always frustrated when [...]
11
-
12
- **Describe the solution you'd like**
13
- A description of the skill you want. What trigger should it have? What files should it effect?
14
-
15
- **Describe alternatives you've considered**
16
- A clear and concise description of any alternative solutions or features you've considered.
17
-
18
- **Additional context**
19
- Add any other context or screenshots about the feature request here.