@dprrwt/ports 1.0.0
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/LICENSE +21 -0
- package/README.md +158 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +165 -0
- package/dist/display.d.ts +32 -0
- package/dist/display.js +205 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/scanner.d.ts +40 -0
- package/dist/scanner.js +251 -0
- package/package.json +46 -0
- package/src/cli.ts +218 -0
- package/src/display.ts +228 -0
- package/src/index.ts +16 -0
- package/src/scanner.ts +282 -0
- package/tsconfig.json +17 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Deepankar Rawat
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# ⚡ ports
|
|
2
|
+
|
|
3
|
+
> Beautiful port manager CLI — because `netstat -ano | findstr :3000` is a crime against developer experience.
|
|
4
|
+
|
|
5
|
+
See what's running. Kill by port. Scan ranges. Find free ports. All with colors and sanity.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @dprrwt/ports
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or use without installing:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx @dprrwt/ports
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### List all listening ports
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
ports
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
⚡ ports — what's running on your machine
|
|
29
|
+
|
|
30
|
+
Listening Ports
|
|
31
|
+
┌──────────────────────┬───────┬───────┬──────────┬───────────┬───────────┐
|
|
32
|
+
│ Port │ Proto │ PID │ Process │ State │ Address │
|
|
33
|
+
├──────────────────────┼───────┼───────┼──────────┼───────────┼───────────┤
|
|
34
|
+
│ 3000 (Dev Server) │ TCP │ 12345 │ node │ LISTENING │ 0.0.0.0 │
|
|
35
|
+
├──────────────────────┼───────┼───────┼──────────┼───────────┼───────────┤
|
|
36
|
+
│ 5432 (PostgreSQL) │ TCP │ 6789 │ postgres │ LISTENING │ 127.0.0.1 │
|
|
37
|
+
├──────────────────────┼───────┼───────┼──────────┼───────────┼───────────┤
|
|
38
|
+
│ 8080 (HTTP Alt) │ TCP │ 4567 │ java │ LISTENING │ 0.0.0.0 │
|
|
39
|
+
└──────────────────────┴───────┴───────┴──────────┴───────────┴───────────┘
|
|
40
|
+
3 ports found
|
|
41
|
+
|
|
42
|
+
Legend:
|
|
43
|
+
■ JS Runtime ■ Python ■ Java ■ Container ■ Database ■ System ■ Unknown
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Check a specific port
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
ports 3000
|
|
50
|
+
# or
|
|
51
|
+
ports check 3000
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
Port 3000 (Dev Server)
|
|
56
|
+
|
|
57
|
+
Process: node (PID 12345)
|
|
58
|
+
Proto: TCP
|
|
59
|
+
State: LISTENING
|
|
60
|
+
Address: 0.0.0.0:3000
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Kill a process by port
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
ports kill 3000 # asks for confirmation
|
|
67
|
+
ports kill 3000 --yes # skip confirmation
|
|
68
|
+
ports kill 3000 -f --yes # force kill (SIGKILL)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Scan a port range
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
ports scan 3000-4000
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
Scanning ports 3000-4000
|
|
79
|
+
2 in use · 999 free · 1001 total
|
|
80
|
+
|
|
81
|
+
┌──────┬───────┬──────┬─────────┬───────────┬─────────┐
|
|
82
|
+
│ Port │ Proto │ PID │ Process │ State │ Address │
|
|
83
|
+
├──────┼───────┼──────┼─────────┼───────────┼─────────┤
|
|
84
|
+
│ 3000 │ TCP │ 1234 │ node │ LISTENING │ 0.0.0.0 │
|
|
85
|
+
├──────┼───────┼──────┼─────────┼───────────┼─────────┤
|
|
86
|
+
│ 3306 │ TCP │ 5678 │ mysqld │ LISTENING │ 0.0.0.0 │
|
|
87
|
+
└──────┴───────┴──────┴─────────┴───────────┴─────────┘
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Find next free port
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
ports free # starts from 3000
|
|
94
|
+
ports free 8000 # starts from 8000
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
✓ Next free port: 3000
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Features
|
|
102
|
+
|
|
103
|
+
- 🎨 **Color-coded processes** — JS runtimes (green), Python (blue), Java (yellow), databases (magenta), system (gray), unknown (red)
|
|
104
|
+
- 🏷️ **Smart port labels** — recognizes 30+ common ports (PostgreSQL, Redis, Vite, Django, etc.)
|
|
105
|
+
- 🛡️ **Safety warnings** — warns before killing critical processes
|
|
106
|
+
- 📊 **JSON output** — `--json` flag on any command for scripting
|
|
107
|
+
- 🖥️ **Cross-platform** — Windows (netstat) + macOS/Linux (ss/lsof)
|
|
108
|
+
- ⚡ **Fast** — process name caching, no unnecessary lookups
|
|
109
|
+
|
|
110
|
+
## Options
|
|
111
|
+
|
|
112
|
+
| Flag | Description |
|
|
113
|
+
|------|-------------|
|
|
114
|
+
| `--json` | Output as JSON (works on all commands) |
|
|
115
|
+
| `--tcp` | Show only TCP ports (list command) |
|
|
116
|
+
| `--udp` | Show only UDP ports (list command) |
|
|
117
|
+
| `-a, --all` | Show all states, not just LISTENING |
|
|
118
|
+
| `-f, --force` | Force kill (SIGKILL / taskkill /F) |
|
|
119
|
+
| `-y, --yes` | Skip confirmation prompts |
|
|
120
|
+
|
|
121
|
+
## Programmatic API
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { getListeningPorts, isPortInUse, findFreePort, killProcess } from "@dprrwt/ports";
|
|
125
|
+
|
|
126
|
+
// Get all listening ports
|
|
127
|
+
const ports = getListeningPorts();
|
|
128
|
+
|
|
129
|
+
// Check if a port is in use
|
|
130
|
+
if (isPortInUse(3000)) {
|
|
131
|
+
console.log("Port 3000 is taken!");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Find next free port
|
|
135
|
+
const free = findFreePort(3000); // → 3001
|
|
136
|
+
|
|
137
|
+
// Kill a process
|
|
138
|
+
killProcess(12345, true); // force = true
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Why?
|
|
142
|
+
|
|
143
|
+
Every developer has done this dance:
|
|
144
|
+
|
|
145
|
+
1. "Port 3000 is already in use"
|
|
146
|
+
2. Google "how to find what's using port 3000"
|
|
147
|
+
3. Copy-paste some arcane `netstat` or `lsof` incantation
|
|
148
|
+
4. Parse the output with your eyes
|
|
149
|
+
5. Find the PID
|
|
150
|
+
6. Google "how to kill process by PID"
|
|
151
|
+
7. Finally kill it
|
|
152
|
+
8. Repeat next week because you forgot the commands
|
|
153
|
+
|
|
154
|
+
**`ports` makes this a one-liner.** See what's running, kill what you need, move on with your life.
|
|
155
|
+
|
|
156
|
+
## License
|
|
157
|
+
|
|
158
|
+
MIT © [Deepankar Rawat](https://dprrwt.me)
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ports — A beautiful port manager CLI
|
|
4
|
+
*
|
|
5
|
+
* Because `netstat -ano | findstr :3000` is a crime against developer experience.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ports List all listening ports
|
|
9
|
+
* ports <port> Check what's on a specific port
|
|
10
|
+
* ports kill <port> Kill whatever's on that port
|
|
11
|
+
* ports scan <range> Scan a port range (e.g. 3000-4000)
|
|
12
|
+
* ports free [start] Find next available port
|
|
13
|
+
*
|
|
14
|
+
* @author Deepankar Rawat <dprrwt@gmail.com>
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ports — A beautiful port manager CLI
|
|
4
|
+
*
|
|
5
|
+
* Because `netstat -ano | findstr :3000` is a crime against developer experience.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ports List all listening ports
|
|
9
|
+
* ports <port> Check what's on a specific port
|
|
10
|
+
* ports kill <port> Kill whatever's on that port
|
|
11
|
+
* ports scan <range> Scan a port range (e.g. 3000-4000)
|
|
12
|
+
* ports free [start] Find next available port
|
|
13
|
+
*
|
|
14
|
+
* @author Deepankar Rawat <dprrwt@gmail.com>
|
|
15
|
+
*/
|
|
16
|
+
import { Command } from "commander";
|
|
17
|
+
import chalk from "chalk";
|
|
18
|
+
import { getListeningPorts, getPortInfo, killProcess, scanRange, findFreePort, } from "./scanner.js";
|
|
19
|
+
import { displayPortTable, displayPortDetail, displayScanRange, displayFreePort, displayKillResult, displayLegend, } from "./display.js";
|
|
20
|
+
const program = new Command();
|
|
21
|
+
program
|
|
22
|
+
.name("ports")
|
|
23
|
+
.description("Beautiful port manager — see, kill, scan, find")
|
|
24
|
+
.version("1.0.0")
|
|
25
|
+
.option("--json", "Output as JSON")
|
|
26
|
+
.option("--no-legend", "Hide color legend");
|
|
27
|
+
// Default command: list all ports
|
|
28
|
+
program
|
|
29
|
+
.command("list", { isDefault: true })
|
|
30
|
+
.description("List all listening ports")
|
|
31
|
+
.option("-a, --all", "Show all states (not just LISTENING)")
|
|
32
|
+
.option("--tcp", "Show only TCP ports")
|
|
33
|
+
.option("--udp", "Show only UDP ports")
|
|
34
|
+
.option("--json", "Output as JSON")
|
|
35
|
+
.action((opts) => {
|
|
36
|
+
let ports = getListeningPorts();
|
|
37
|
+
if (!opts.all) {
|
|
38
|
+
ports = ports.filter((p) => p.state === "LISTENING" || p.state === "LISTEN" || p.state === "*");
|
|
39
|
+
}
|
|
40
|
+
if (opts.tcp) {
|
|
41
|
+
ports = ports.filter((p) => p.protocol === "TCP");
|
|
42
|
+
}
|
|
43
|
+
if (opts.udp) {
|
|
44
|
+
ports = ports.filter((p) => p.protocol === "UDP");
|
|
45
|
+
}
|
|
46
|
+
if (opts.json || program.opts().json) {
|
|
47
|
+
console.log(JSON.stringify(ports, null, 2));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
console.log(chalk.bold.cyan("\n ⚡ ports") + chalk.dim(" — what's running on your machine\n"));
|
|
51
|
+
displayPortTable(ports, "Listening Ports");
|
|
52
|
+
displayLegend();
|
|
53
|
+
});
|
|
54
|
+
// Check specific port
|
|
55
|
+
program
|
|
56
|
+
.command("check <port>")
|
|
57
|
+
.description("Check what's using a specific port")
|
|
58
|
+
.option("--json", "Output as JSON")
|
|
59
|
+
.action((portStr, opts) => {
|
|
60
|
+
const port = parseInt(portStr, 10);
|
|
61
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
62
|
+
console.error(chalk.red(`\n ✗ Invalid port: ${portStr} (must be 1-65535)\n`));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
const infos = getPortInfo(port);
|
|
66
|
+
if (opts.json || program.opts().json) {
|
|
67
|
+
console.log(JSON.stringify(infos, null, 2));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
displayPortDetail(port, infos);
|
|
71
|
+
});
|
|
72
|
+
// Kill process on port
|
|
73
|
+
program
|
|
74
|
+
.command("kill <port>")
|
|
75
|
+
.description("Kill the process using a port")
|
|
76
|
+
.option("-f, --force", "Force kill (SIGKILL / taskkill /F)")
|
|
77
|
+
.option("-y, --yes", "Skip confirmation")
|
|
78
|
+
.option("--json", "Output as JSON")
|
|
79
|
+
.action(async (portStr, opts) => {
|
|
80
|
+
const port = parseInt(portStr, 10);
|
|
81
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
82
|
+
console.error(chalk.red(`\n ✗ Invalid port: ${portStr}\n`));
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
const infos = getPortInfo(port);
|
|
86
|
+
if (infos.length === 0) {
|
|
87
|
+
console.log(chalk.yellow(`\n Nothing is running on port ${port}\n`));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
// Safety check: warn about system processes
|
|
91
|
+
const systemPorts = [18791, 18800]; // OpenClaw
|
|
92
|
+
if (systemPorts.includes(port)) {
|
|
93
|
+
console.log(chalk.red.bold(`\n ⚠ Port ${port} is used by OpenClaw — killing it may break your agent system!`));
|
|
94
|
+
if (!opts.yes) {
|
|
95
|
+
console.log(chalk.dim(" Use --yes to confirm\n"));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
for (const info of infos) {
|
|
100
|
+
if (!opts.yes) {
|
|
101
|
+
console.log(chalk.yellow(`\n Kill ${chalk.bold(info.processName)} (PID ${info.pid}) on port ${port}?`));
|
|
102
|
+
console.log(chalk.dim(" Use --yes to skip confirmation\n"));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const success = killProcess(info.pid, opts.force);
|
|
106
|
+
if (opts.json || program.opts().json) {
|
|
107
|
+
console.log(JSON.stringify({ port, pid: info.pid, processName: info.processName, killed: success }));
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
displayKillResult(port, info.pid, info.processName, success);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
// Scan port range
|
|
115
|
+
program
|
|
116
|
+
.command("scan <range>")
|
|
117
|
+
.description("Scan a port range (e.g. 3000-4000)")
|
|
118
|
+
.option("--json", "Output as JSON")
|
|
119
|
+
.action((range, opts) => {
|
|
120
|
+
const match = range.match(/^(\d+)-(\d+)$/);
|
|
121
|
+
if (!match) {
|
|
122
|
+
console.error(chalk.red(`\n ✗ Invalid range: ${range} (use format: 3000-4000)\n`));
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
const startPort = parseInt(match[1], 10);
|
|
126
|
+
const endPort = parseInt(match[2], 10);
|
|
127
|
+
if (startPort > endPort || startPort < 1 || endPort > 65535) {
|
|
128
|
+
console.error(chalk.red(`\n ✗ Invalid range: ${startPort}-${endPort}\n`));
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
if (endPort - startPort > 10000) {
|
|
132
|
+
console.log(chalk.yellow(`\n Scanning ${endPort - startPort + 1} ports — this may take a moment...\n`));
|
|
133
|
+
}
|
|
134
|
+
const ports = scanRange(startPort, endPort);
|
|
135
|
+
if (opts.json || program.opts().json) {
|
|
136
|
+
console.log(JSON.stringify(ports, null, 2));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
displayScanRange(startPort, endPort, ports);
|
|
140
|
+
});
|
|
141
|
+
// Find free port
|
|
142
|
+
program
|
|
143
|
+
.command("free [startPort]")
|
|
144
|
+
.description("Find the next available port")
|
|
145
|
+
.option("--json", "Output as JSON")
|
|
146
|
+
.action((startStr, opts) => {
|
|
147
|
+
const startPort = startStr ? parseInt(startStr, 10) : 3000;
|
|
148
|
+
if (isNaN(startPort) || startPort < 1 || startPort > 65535) {
|
|
149
|
+
console.error(chalk.red(`\n ✗ Invalid start port: ${startStr}\n`));
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
const freePort = findFreePort(startPort);
|
|
153
|
+
if (opts.json || program.opts().json) {
|
|
154
|
+
console.log(JSON.stringify({ freePort, startedFrom: startPort }));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
displayFreePort(freePort, startPort);
|
|
158
|
+
});
|
|
159
|
+
// Handle bare port number: `ports 3000` → `ports check 3000`
|
|
160
|
+
const args = process.argv.slice(2);
|
|
161
|
+
if (args.length > 0 && /^\d+$/.test(args[0])) {
|
|
162
|
+
// Rewrite `ports 3000` → `ports check 3000`
|
|
163
|
+
process.argv.splice(2, 0, "check");
|
|
164
|
+
}
|
|
165
|
+
program.parse();
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Display Layer — Making port data beautiful
|
|
3
|
+
*
|
|
4
|
+
* netstat is powerful but ugly. The information is there — it just needs
|
|
5
|
+
* a human-friendly presentation. Like Da Vinci's anatomical drawings:
|
|
6
|
+
* the same body everyone could see, but rendered with clarity and beauty.
|
|
7
|
+
*/
|
|
8
|
+
import type { PortInfo } from "./scanner.js";
|
|
9
|
+
/**
|
|
10
|
+
* Display a full port table
|
|
11
|
+
*/
|
|
12
|
+
export declare function displayPortTable(ports: PortInfo[], title?: string): void;
|
|
13
|
+
/**
|
|
14
|
+
* Display compact port info (for single port lookup)
|
|
15
|
+
*/
|
|
16
|
+
export declare function displayPortDetail(port: number, infos: PortInfo[]): void;
|
|
17
|
+
/**
|
|
18
|
+
* Display scan range results
|
|
19
|
+
*/
|
|
20
|
+
export declare function displayScanRange(startPort: number, endPort: number, ports: PortInfo[]): void;
|
|
21
|
+
/**
|
|
22
|
+
* Display free port result
|
|
23
|
+
*/
|
|
24
|
+
export declare function displayFreePort(port: number | null, startPort: number): void;
|
|
25
|
+
/**
|
|
26
|
+
* Display kill result
|
|
27
|
+
*/
|
|
28
|
+
export declare function displayKillResult(port: number, pid: number, processName: string, success: boolean): void;
|
|
29
|
+
/**
|
|
30
|
+
* Display legend
|
|
31
|
+
*/
|
|
32
|
+
export declare function displayLegend(): void;
|
package/dist/display.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Display Layer — Making port data beautiful
|
|
3
|
+
*
|
|
4
|
+
* netstat is powerful but ugly. The information is there — it just needs
|
|
5
|
+
* a human-friendly presentation. Like Da Vinci's anatomical drawings:
|
|
6
|
+
* the same body everyone could see, but rendered with clarity and beauty.
|
|
7
|
+
*/
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import Table from "cli-table3";
|
|
10
|
+
// Well-known ports that developers care about
|
|
11
|
+
const KNOWN_PORTS = {
|
|
12
|
+
80: "HTTP",
|
|
13
|
+
443: "HTTPS",
|
|
14
|
+
3000: "Dev Server",
|
|
15
|
+
3001: "Dev Server",
|
|
16
|
+
3333: "Dev Server",
|
|
17
|
+
4000: "Dev Server",
|
|
18
|
+
4200: "Angular",
|
|
19
|
+
5000: "Flask/Vite",
|
|
20
|
+
5173: "Vite",
|
|
21
|
+
5174: "Vite",
|
|
22
|
+
5432: "PostgreSQL",
|
|
23
|
+
5500: "Live Server",
|
|
24
|
+
6379: "Redis",
|
|
25
|
+
8000: "Django/FastAPI",
|
|
26
|
+
8080: "HTTP Alt",
|
|
27
|
+
8443: "HTTPS Alt",
|
|
28
|
+
8888: "Jupyter",
|
|
29
|
+
9000: "PHP-FPM",
|
|
30
|
+
9090: "Prometheus",
|
|
31
|
+
9229: "Node Debug",
|
|
32
|
+
18791: "OpenClaw",
|
|
33
|
+
18800: "OpenClaw Browser",
|
|
34
|
+
27017: "MongoDB",
|
|
35
|
+
2960: "Mission Control",
|
|
36
|
+
};
|
|
37
|
+
// Process name to color category
|
|
38
|
+
function getProcessColor(name) {
|
|
39
|
+
const lower = name.toLowerCase();
|
|
40
|
+
if (lower.includes("node") || lower.includes("deno") || lower.includes("bun")) {
|
|
41
|
+
return chalk.green; // JS runtimes — green (your dev servers)
|
|
42
|
+
}
|
|
43
|
+
if (lower.includes("python") || lower.includes("flask") || lower.includes("django")) {
|
|
44
|
+
return chalk.blue; // Python
|
|
45
|
+
}
|
|
46
|
+
if (lower.includes("java") || lower.includes("gradle") || lower.includes("maven")) {
|
|
47
|
+
return chalk.yellow; // Java
|
|
48
|
+
}
|
|
49
|
+
if (lower.includes("docker") || lower.includes("containerd")) {
|
|
50
|
+
return chalk.cyan; // Containers
|
|
51
|
+
}
|
|
52
|
+
if (lower.includes("postgres") || lower.includes("mysql") || lower.includes("mongo") || lower.includes("redis")) {
|
|
53
|
+
return chalk.magenta; // Databases
|
|
54
|
+
}
|
|
55
|
+
if (lower === "system" || lower === "svchost" || lower.includes("system")) {
|
|
56
|
+
return chalk.gray; // System processes
|
|
57
|
+
}
|
|
58
|
+
if (lower === "unknown") {
|
|
59
|
+
return chalk.red; // Unknown — potential concern
|
|
60
|
+
}
|
|
61
|
+
return chalk.white; // Everything else
|
|
62
|
+
}
|
|
63
|
+
function getStateColor(state) {
|
|
64
|
+
switch (state) {
|
|
65
|
+
case "LISTENING":
|
|
66
|
+
return chalk.green(state);
|
|
67
|
+
case "ESTABLISHED":
|
|
68
|
+
return chalk.cyan(state);
|
|
69
|
+
case "TIME_WAIT":
|
|
70
|
+
return chalk.gray(state);
|
|
71
|
+
case "CLOSE_WAIT":
|
|
72
|
+
return chalk.yellow(state);
|
|
73
|
+
default:
|
|
74
|
+
return chalk.white(state);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function getPortLabel(port) {
|
|
78
|
+
const label = KNOWN_PORTS[port];
|
|
79
|
+
return label ? chalk.dim(` (${label})`) : "";
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Display a full port table
|
|
83
|
+
*/
|
|
84
|
+
export function displayPortTable(ports, title) {
|
|
85
|
+
if (ports.length === 0) {
|
|
86
|
+
console.log(chalk.yellow("\n No ports found.\n"));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (title) {
|
|
90
|
+
console.log(chalk.bold(`\n ${title}`));
|
|
91
|
+
}
|
|
92
|
+
const table = new Table({
|
|
93
|
+
head: [
|
|
94
|
+
chalk.bold.white("Port"),
|
|
95
|
+
chalk.bold.white("Proto"),
|
|
96
|
+
chalk.bold.white("PID"),
|
|
97
|
+
chalk.bold.white("Process"),
|
|
98
|
+
chalk.bold.white("State"),
|
|
99
|
+
chalk.bold.white("Address"),
|
|
100
|
+
],
|
|
101
|
+
style: {
|
|
102
|
+
head: [],
|
|
103
|
+
border: ["gray"],
|
|
104
|
+
},
|
|
105
|
+
chars: {
|
|
106
|
+
top: "─",
|
|
107
|
+
"top-mid": "┬",
|
|
108
|
+
"top-left": "┌",
|
|
109
|
+
"top-right": "┐",
|
|
110
|
+
bottom: "─",
|
|
111
|
+
"bottom-mid": "┴",
|
|
112
|
+
"bottom-left": "└",
|
|
113
|
+
"bottom-right": "┘",
|
|
114
|
+
left: "│",
|
|
115
|
+
"left-mid": "├",
|
|
116
|
+
mid: "─",
|
|
117
|
+
"mid-mid": "┼",
|
|
118
|
+
right: "│",
|
|
119
|
+
"right-mid": "┤",
|
|
120
|
+
middle: "│",
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
for (const p of ports) {
|
|
124
|
+
const colorFn = getProcessColor(p.processName);
|
|
125
|
+
table.push([
|
|
126
|
+
chalk.bold.white(String(p.port)) + getPortLabel(p.port),
|
|
127
|
+
p.protocol === "UDP" ? chalk.yellow(p.protocol) : chalk.cyan(p.protocol),
|
|
128
|
+
chalk.dim(String(p.pid)),
|
|
129
|
+
colorFn(p.processName),
|
|
130
|
+
getStateColor(p.state),
|
|
131
|
+
chalk.dim(p.localAddress),
|
|
132
|
+
]);
|
|
133
|
+
}
|
|
134
|
+
console.log(table.toString());
|
|
135
|
+
console.log(chalk.dim(` ${ports.length} port${ports.length === 1 ? "" : "s"} found\n`));
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Display compact port info (for single port lookup)
|
|
139
|
+
*/
|
|
140
|
+
export function displayPortDetail(port, infos) {
|
|
141
|
+
if (infos.length === 0) {
|
|
142
|
+
console.log(chalk.green(`\n ✓ Port ${port} is free\n`));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
console.log(chalk.bold(`\n Port ${port}`) + getPortLabel(port));
|
|
146
|
+
console.log();
|
|
147
|
+
for (const info of infos) {
|
|
148
|
+
const colorFn = getProcessColor(info.processName);
|
|
149
|
+
console.log(` ${chalk.dim("Process:")} ${colorFn(info.processName)} ${chalk.dim(`(PID ${info.pid})`)}`);
|
|
150
|
+
console.log(` ${chalk.dim("Proto:")} ${info.protocol}`);
|
|
151
|
+
console.log(` ${chalk.dim("State:")} ${getStateColor(info.state)}`);
|
|
152
|
+
console.log(` ${chalk.dim("Address:")} ${info.localAddress}:${port}`);
|
|
153
|
+
if (infos.indexOf(info) < infos.length - 1) {
|
|
154
|
+
console.log(chalk.dim(" ─────────────────────────"));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
console.log();
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Display scan range results
|
|
161
|
+
*/
|
|
162
|
+
export function displayScanRange(startPort, endPort, ports) {
|
|
163
|
+
const total = endPort - startPort + 1;
|
|
164
|
+
const used = new Set(ports.map((p) => p.port)).size;
|
|
165
|
+
const free = total - used;
|
|
166
|
+
console.log(chalk.bold(`\n Scanning ports ${startPort}-${endPort}`));
|
|
167
|
+
console.log(` ${chalk.red(String(used))} in use · ${chalk.green(String(free))} free · ${chalk.dim(String(total) + " total")}`);
|
|
168
|
+
if (ports.length > 0) {
|
|
169
|
+
displayPortTable(ports);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
console.log(chalk.green(`\n ✓ All ${total} ports are free\n`));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Display free port result
|
|
177
|
+
*/
|
|
178
|
+
export function displayFreePort(port, startPort) {
|
|
179
|
+
if (port === null) {
|
|
180
|
+
console.log(chalk.red(`\n ✗ No free ports found starting from ${startPort}\n`));
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
console.log(chalk.green(`\n ✓ Next free port: ${chalk.bold.white(String(port))}\n`));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Display kill result
|
|
188
|
+
*/
|
|
189
|
+
export function displayKillResult(port, pid, processName, success) {
|
|
190
|
+
if (success) {
|
|
191
|
+
console.log(chalk.green(`\n ✓ Killed ${processName} (PID ${pid}) on port ${port}\n`));
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
console.log(chalk.red(`\n ✗ Failed to kill ${processName} (PID ${pid}) on port ${port}`));
|
|
195
|
+
console.log(chalk.dim(" Try running with --force or as administrator\n"));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Display legend
|
|
200
|
+
*/
|
|
201
|
+
export function displayLegend() {
|
|
202
|
+
console.log(chalk.dim(" Legend:"));
|
|
203
|
+
console.log(` ${chalk.green("■")} JS Runtime ${chalk.blue("■")} Python ${chalk.yellow("■")} Java ${chalk.cyan("■")} Container ${chalk.magenta("■")} Database ${chalk.gray("■")} System ${chalk.red("■")} Unknown`);
|
|
204
|
+
console.log();
|
|
205
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @dprrwt/ports — Programmatic API
|
|
3
|
+
*
|
|
4
|
+
* Use the CLI for interactive use, or import these functions
|
|
5
|
+
* for programmatic port management in your Node.js scripts.
|
|
6
|
+
*/
|
|
7
|
+
export { getListeningPorts, getPortInfo, isPortInUse, findFreePort, scanRange, killProcess, type PortInfo, } from "./scanner.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @dprrwt/ports — Programmatic API
|
|
3
|
+
*
|
|
4
|
+
* Use the CLI for interactive use, or import these functions
|
|
5
|
+
* for programmatic port management in your Node.js scripts.
|
|
6
|
+
*/
|
|
7
|
+
export { getListeningPorts, getPortInfo, isPortInUse, findFreePort, scanRange, killProcess, } from "./scanner.js";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port Scanner — Cross-platform port detection
|
|
3
|
+
*
|
|
4
|
+
* Da Vinci's curiosity: How do operating systems track network connections?
|
|
5
|
+
* Windows uses netstat, macOS/Linux use ss/lsof. Different tools, same truth.
|
|
6
|
+
* The abstraction layer here unifies them — like translating between languages
|
|
7
|
+
* to find the same underlying meaning.
|
|
8
|
+
*/
|
|
9
|
+
export interface PortInfo {
|
|
10
|
+
port: number;
|
|
11
|
+
pid: number;
|
|
12
|
+
protocol: "TCP" | "UDP";
|
|
13
|
+
state: string;
|
|
14
|
+
localAddress: string;
|
|
15
|
+
processName: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Get all listening ports on the system
|
|
19
|
+
*/
|
|
20
|
+
export declare function getListeningPorts(): PortInfo[];
|
|
21
|
+
/**
|
|
22
|
+
* Get info for a specific port
|
|
23
|
+
*/
|
|
24
|
+
export declare function getPortInfo(port: number): PortInfo[];
|
|
25
|
+
/**
|
|
26
|
+
* Check if a port is in use
|
|
27
|
+
*/
|
|
28
|
+
export declare function isPortInUse(port: number): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Find the next free port starting from a given port
|
|
31
|
+
*/
|
|
32
|
+
export declare function findFreePort(startPort?: number, endPort?: number): number | null;
|
|
33
|
+
/**
|
|
34
|
+
* Scan a range of ports and return which are in use
|
|
35
|
+
*/
|
|
36
|
+
export declare function scanRange(startPort: number, endPort: number): PortInfo[];
|
|
37
|
+
/**
|
|
38
|
+
* Kill a process by PID
|
|
39
|
+
*/
|
|
40
|
+
export declare function killProcess(pid: number, force?: boolean): boolean;
|