devprobe 0.1.0 → 0.2.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/README.md +68 -88
- package/dist/index.js +77 -20
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,126 +1,106 @@
|
|
|
1
|
-
#
|
|
1
|
+
# devprobe
|
|
2
2
|
|
|
3
|
-
Instant
|
|
3
|
+
**Instant diagnostics for your local dev environment.** One command — see every listening port, which process owns it, and whether it responds.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Every developer has hit it: you start a server and get `EADDRINUSE`, then spend minutes figuring out what's hogging the port. AI coding agents like Claude Code hit the same wall -- they burn tokens retrying commands and guessing what went wrong.
|
|
8
|
-
|
|
9
|
-
portprobe scans common dev ports in parallel, shows what's listening, which process owns it, and whether it responds to HTTP. One command, zero guesswork.
|
|
10
|
-
|
|
11
|
-
## Quick start
|
|
12
|
-
|
|
13
|
-
Run without installing:
|
|
5
|
+
[](https://www.npmjs.com/package/devprobe)
|
|
6
|
+
[](LICENSE)
|
|
14
7
|
|
|
15
|
-
|
|
16
|
-
npx portprobe
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Or install globally:
|
|
8
|
+
---
|
|
20
9
|
|
|
21
10
|
```bash
|
|
22
|
-
|
|
11
|
+
$ npx devprobe
|
|
12
|
+
|
|
13
|
+
PORT | PID | PROCESS | STATUS | LATENCY | PROTO
|
|
14
|
+
-------|-------|------------------|--------|---------|--------
|
|
15
|
+
3000 | 12841 | node | UP | 2ms | HTTP 200
|
|
16
|
+
5432 | 1023 | postgres | UP | 1ms | TCP
|
|
17
|
+
5500 | 9912 | node | UP | 1ms | HTTP 200
|
|
18
|
+
6379 | 1087 | redis-server | UP | <1ms | TCP
|
|
19
|
+
8080 | 5523 | java | UP | 12ms | HTTP 200
|
|
20
|
+
24282 | 3301 | python | UP | 3ms | HTTP 404
|
|
23
21
|
```
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
### Default scan
|
|
28
|
-
|
|
29
|
-
Scans common dev ports (3000, 3001, 4200, 5000, 5173, 5432, 6379, 8000, 8080, 8443, 9000, 27017) and prints a table:
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
portprobe
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### JSON output
|
|
36
|
-
|
|
37
|
-
Machine-readable output for scripts and AI agents:
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
portprobe --json
|
|
41
|
-
```
|
|
23
|
+
No config. No port lists to maintain. It asks the OS directly what's listening.
|
|
42
24
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
Check whether a specific port is in use:
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
portprobe 3000
|
|
49
|
-
```
|
|
25
|
+
## Why
|
|
50
26
|
|
|
51
|
-
|
|
27
|
+
- `EADDRINUSE` — what's using port 3000? Now you know instantly
|
|
28
|
+
- **AI agents** (Claude Code, Cursor, etc.) waste tokens running `lsof`, parsing output, retrying — `devprobe --json` gives them structured data in one call
|
|
29
|
+
- **Zero guesswork** — shows ALL listening ports, not just a predefined list
|
|
52
30
|
|
|
53
|
-
|
|
31
|
+
## Install
|
|
54
32
|
|
|
55
33
|
```bash
|
|
56
|
-
|
|
34
|
+
npx devprobe # run without installing
|
|
35
|
+
npm i -g devprobe # or install globally
|
|
57
36
|
```
|
|
58
37
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
Set the TCP connection timeout in milliseconds (default: 1000):
|
|
38
|
+
## Usage
|
|
62
39
|
|
|
63
40
|
```bash
|
|
64
|
-
|
|
41
|
+
devprobe # show all listening ports
|
|
42
|
+
devprobe 3000 # check specific port
|
|
43
|
+
devprobe --range 3000-9000 # scan a range
|
|
44
|
+
devprobe --json # JSON output (for scripts & AI agents)
|
|
45
|
+
devprobe --timeout 2000 # custom timeout in ms
|
|
65
46
|
```
|
|
66
47
|
|
|
67
|
-
|
|
48
|
+
### Flags
|
|
68
49
|
|
|
69
50
|
| Flag | Description | Default |
|
|
70
51
|
|---|---|---|
|
|
71
52
|
| `[port]` | Check a single port | — |
|
|
72
53
|
| `--json` | Output as JSON | `false` |
|
|
73
|
-
| `--range <from-to>` | Scan a port range
|
|
54
|
+
| `--range <from-to>` | Scan a port range | — |
|
|
74
55
|
| `--timeout <ms>` | Connection timeout in ms | `1000` |
|
|
75
56
|
| `--version` | Show version | — |
|
|
76
57
|
| `--help` | Show help | — |
|
|
77
58
|
|
|
78
|
-
## AI
|
|
79
|
-
|
|
80
|
-
portprobe is designed to work well with AI coding agents. Use `--json` to get structured output that an agent can parse directly:
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
portprobe --json
|
|
84
|
-
```
|
|
59
|
+
## For AI Agents
|
|
85
60
|
|
|
86
|
-
|
|
61
|
+
`devprobe --json` returns structured data that any AI coding agent can parse:
|
|
87
62
|
|
|
88
63
|
```json
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
"pid": 501,
|
|
102
|
-
"process": "postgres",
|
|
103
|
-
"latency": 1,
|
|
104
|
-
"protocol": null
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
"port": 8080,
|
|
108
|
-
"status": "free",
|
|
109
|
-
"pid": null,
|
|
110
|
-
"process": null,
|
|
111
|
-
"latency": null,
|
|
112
|
-
"protocol": null
|
|
113
|
-
}
|
|
114
|
-
]
|
|
64
|
+
{
|
|
65
|
+
"ports": [
|
|
66
|
+
{
|
|
67
|
+
"port": 3000,
|
|
68
|
+
"status": "up",
|
|
69
|
+
"pid": 12841,
|
|
70
|
+
"process": "node",
|
|
71
|
+
"latency": 2,
|
|
72
|
+
"protocol": "HTTP 200"
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
115
76
|
```
|
|
116
77
|
|
|
117
|
-
|
|
78
|
+
Add to your project's `CLAUDE.md` or agent config:
|
|
79
|
+
```
|
|
80
|
+
Use `devprobe --json` to diagnose port conflicts instead of lsof/netstat.
|
|
81
|
+
```
|
|
118
82
|
|
|
119
83
|
## Platforms
|
|
120
84
|
|
|
121
|
-
- **macOS** — full support (
|
|
122
|
-
- **Linux** — full support (
|
|
123
|
-
- **Windows** — best-effort (
|
|
85
|
+
- **macOS** — full support (`lsof`)
|
|
86
|
+
- **Linux** — full support (`lsof`)
|
|
87
|
+
- **Windows** — best-effort (`netstat`)
|
|
88
|
+
|
|
89
|
+
## Roadmap
|
|
90
|
+
|
|
91
|
+
- [ ] `--watch` — realtime monitoring
|
|
92
|
+
- [ ] `--wait <port>` — wait until port is available
|
|
93
|
+
- [ ] `--diagnose` — problem analysis + recommendations
|
|
94
|
+
- [ ] Auto-detect service type (PostgreSQL, Redis, MySQL, etc.)
|
|
95
|
+
- [ ] Docker container awareness
|
|
96
|
+
|
|
97
|
+
## Support the project
|
|
98
|
+
|
|
99
|
+
If devprobe saved you time, consider:
|
|
100
|
+
|
|
101
|
+
- Star this repo — it helps others find it
|
|
102
|
+
- [Sponsor on GitHub](https://github.com/sponsors/bogdanblare) — support development directly
|
|
103
|
+
- Share with your team — the more users, the better it gets
|
|
124
104
|
|
|
125
105
|
## License
|
|
126
106
|
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,65 @@ function checkTcp(port, timeout) {
|
|
|
33
33
|
import { execFile } from "child_process";
|
|
34
34
|
import { promisify } from "util";
|
|
35
35
|
var execFileAsync = promisify(execFile);
|
|
36
|
+
async function discoverListeningPorts() {
|
|
37
|
+
try {
|
|
38
|
+
if (process.platform === "win32") {
|
|
39
|
+
return discoverWindows();
|
|
40
|
+
}
|
|
41
|
+
return discoverUnix();
|
|
42
|
+
} catch {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function discoverUnix() {
|
|
47
|
+
const { stdout } = await execFileAsync("lsof", [
|
|
48
|
+
"-iTCP",
|
|
49
|
+
"-sTCP:LISTEN",
|
|
50
|
+
"-P",
|
|
51
|
+
"-n",
|
|
52
|
+
"-Fpn"
|
|
53
|
+
]);
|
|
54
|
+
const results = [];
|
|
55
|
+
let currentPid = null;
|
|
56
|
+
for (const line of stdout.split("\n")) {
|
|
57
|
+
if (line.startsWith("p")) {
|
|
58
|
+
currentPid = parseInt(line.slice(1), 10);
|
|
59
|
+
} else if (line.startsWith("n") && currentPid !== null) {
|
|
60
|
+
const match = line.match(/:(\d+)$/);
|
|
61
|
+
if (match) {
|
|
62
|
+
const port = parseInt(match[1], 10);
|
|
63
|
+
const name = await getProcessName(currentPid);
|
|
64
|
+
results.push({ port, pid: currentPid, name });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const seen = /* @__PURE__ */ new Set();
|
|
69
|
+
return results.filter((r) => {
|
|
70
|
+
if (seen.has(r.port)) return false;
|
|
71
|
+
seen.add(r.port);
|
|
72
|
+
return true;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
async function discoverWindows() {
|
|
76
|
+
const { stdout } = await execFileAsync("netstat", ["-ano"]);
|
|
77
|
+
const results = [];
|
|
78
|
+
const seen = /* @__PURE__ */ new Set();
|
|
79
|
+
for (const line of stdout.split("\n")) {
|
|
80
|
+
if (!line.includes("LISTENING")) continue;
|
|
81
|
+
const parts = line.trim().split(/\s+/);
|
|
82
|
+
const addrPart = parts[1];
|
|
83
|
+
if (!addrPart) continue;
|
|
84
|
+
const match = addrPart.match(/:(\d+)$/);
|
|
85
|
+
if (!match) continue;
|
|
86
|
+
const port = parseInt(match[1], 10);
|
|
87
|
+
if (seen.has(port)) continue;
|
|
88
|
+
seen.add(port);
|
|
89
|
+
const pid = parseInt(parts[parts.length - 1], 10);
|
|
90
|
+
const name = await getProcessName(pid);
|
|
91
|
+
results.push({ port, pid, name });
|
|
92
|
+
}
|
|
93
|
+
return results;
|
|
94
|
+
}
|
|
36
95
|
async function resolveProcess(port) {
|
|
37
96
|
try {
|
|
38
97
|
if (process.platform === "win32") {
|
|
@@ -121,30 +180,30 @@ function checkHttp(port, timeout) {
|
|
|
121
180
|
}
|
|
122
181
|
|
|
123
182
|
// src/constants.ts
|
|
124
|
-
var DEFAULT_PORTS = [
|
|
125
|
-
3e3,
|
|
126
|
-
3001,
|
|
127
|
-
4200,
|
|
128
|
-
5e3,
|
|
129
|
-
5173,
|
|
130
|
-
5432,
|
|
131
|
-
6379,
|
|
132
|
-
8e3,
|
|
133
|
-
8080,
|
|
134
|
-
8443,
|
|
135
|
-
9e3,
|
|
136
|
-
27017
|
|
137
|
-
];
|
|
138
183
|
var DEFAULT_TIMEOUT = 1e3;
|
|
139
184
|
|
|
140
185
|
// src/scanner/index.ts
|
|
141
186
|
async function scanPorts(options) {
|
|
142
187
|
const timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
143
|
-
|
|
188
|
+
if (options.ports && options.ports.length > 0 || options.range) {
|
|
189
|
+
const ports = resolvePorts(options);
|
|
190
|
+
return Promise.all(ports.map((port) => scanSinglePort(port, timeout)));
|
|
191
|
+
}
|
|
192
|
+
const listening = await discoverListeningPorts();
|
|
144
193
|
const results = await Promise.all(
|
|
145
|
-
|
|
194
|
+
listening.map(async (entry) => {
|
|
195
|
+
const httpResult = await checkHttp(entry.port, timeout);
|
|
196
|
+
return {
|
|
197
|
+
port: entry.port,
|
|
198
|
+
status: "up",
|
|
199
|
+
pid: entry.pid,
|
|
200
|
+
process: entry.name,
|
|
201
|
+
latency: httpResult.latency,
|
|
202
|
+
protocol: httpResult.protocol ?? "TCP"
|
|
203
|
+
};
|
|
204
|
+
})
|
|
146
205
|
);
|
|
147
|
-
return results;
|
|
206
|
+
return results.sort((a, b) => a.port - b.port);
|
|
148
207
|
}
|
|
149
208
|
function resolvePorts(options) {
|
|
150
209
|
if (options.ports && options.ports.length > 0) return options.ports;
|
|
@@ -205,7 +264,7 @@ function formatJson(results) {
|
|
|
205
264
|
}
|
|
206
265
|
|
|
207
266
|
// src/index.ts
|
|
208
|
-
program.name("
|
|
267
|
+
program.name("devprobe").description("Instant diagnostics for your local dev environment").version("0.1.0").argument("[port]", "specific port to check", parseInt).option("--json", "output as JSON (for AI agents)").option("--range <from-to>", "scan port range (e.g. 3000-9000)").option("--timeout <ms>", "connection timeout in ms", parseInt).action(async (port, options) => {
|
|
209
268
|
const timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
210
269
|
let ports;
|
|
211
270
|
let range;
|
|
@@ -214,8 +273,6 @@ program.name("portprobe").description("Instant diagnostics for your local dev en
|
|
|
214
273
|
} else if (options.range) {
|
|
215
274
|
const [from, to] = options.range.split("-").map(Number);
|
|
216
275
|
range = { from, to };
|
|
217
|
-
} else {
|
|
218
|
-
ports = DEFAULT_PORTS;
|
|
219
276
|
}
|
|
220
277
|
const results = await scanPorts({ ports, range, timeout });
|
|
221
278
|
if (options.json) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devprobe",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Instant diagnostics for your local dev environment",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
],
|
|
32
32
|
"repository": {
|
|
33
33
|
"type": "git",
|
|
34
|
-
"url": "git+https://github.com/bogdanblare/
|
|
34
|
+
"url": "git+https://github.com/bogdanblare/devprobe.git"
|
|
35
35
|
},
|
|
36
|
-
"homepage": "https://github.com/bogdanblare/
|
|
36
|
+
"homepage": "https://github.com/bogdanblare/devprobe#readme",
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"chalk": "^5.6.2",
|
|
39
39
|
"commander": "^14.0.3"
|