killtask 1.1.1 → 1.1.3
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/index.min.js +2 -0
- package/package.json +5 -5
- package/readme.md +27 -2
- package/index.js +0 -378
package/index.min.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{exec}from"child_process";import os from"os";import readline from"readline";const R="[0m",B="[1m",_g="[90m",_r="[31m",_G="[32m",_y="[33m",_c="[36m",_w="[97m",_m="[35m",cl=(n,t)=>`${n}${t}${R}`,g=n=>cl(_g,n),r=n=>cl(_r,n),G=n=>cl(_G,n),y=n=>cl(_y,n),c=n=>cl(_c,n),w=n=>cl(_w,n),bo=n=>cl(B,n),bc=n=>`${B}${_c}${n}${R}`,bm=n=>`${B}${_m}${n}${R}`,args=process.argv.slice(2),FL=new Set(["--soft","--no-force","--all","--yes","-y","--verbose","-v","--help","-h"]),isFo=!args.includes("--soft")&&!args.includes("--no-force"),isAl=args.includes("--all"),isYe=args.includes("--yes")||args.includes("-y"),isVe=args.includes("--verbose")||args.includes("-v"),ports=args.filter(n=>!FL.has(n)&&!n.startsWith("-")),isWin="win32"===os.platform(),sl=n=>new Promise(t=>setTimeout(t,n)),run=n=>new Promise((t,o)=>exec(n,{timeout:8e3},(n,e)=>n?o(n):t(e.trim()))),clrLine=()=>process.stdout.write("\r"+" ".repeat(72)+"\r"),log=(...n)=>isVe&&console.log(g(" ·"),...n);function confirm(n){return isYe?Promise.resolve(!0):new Promise(t=>{const o=readline.createInterface({input:process.stdin,output:process.stdout});o.question(y(` ⚠️ ${n} [y/N] `),n=>{o.close(),t("y"===n.toLowerCase())})})}const T=24,MA=r("♜"),EN=`${_G}♟${R}`;function drawFrame(n){let t="";for(let o=0;o<T;o++)t+=o===n?y("►"):g(o<n?"·":"─");process.stdout.write(`\r ${MA} ${t} ${EN} ${w("pew…")}`)}async function animateBullet(){process.stdout.write(`\r ${MA} ${g("─".repeat(T))} ${EN} ${y("🔫")}`),await sl(60);for(let n=0;n<T;n++)drawFrame(n),await sl(10)}async function animateExplosion(){for(const[n,t]of[[y("✸"),r("💥 BOOM!")],[y("✦"),r("☠ TERMINATED")],[" ",G("✔ port killed")]])process.stdout.write(`\r ${MA} ${g("·".repeat(T))} ${n} ${t}`),await sl(55);clrLine()}async function animateBounce(){for(let n=23;n>=0;n--){let t="";for(let o=0;o<T;o++)t+=o===n?r("◄"):g(o>n?"·":"─");process.stdout.write(`\r ${MA} ${t} ${r("⛨")} ${r("blocked!")}`),await sl(8)}clrLine()}async function killWithAnimation(n){const t=killPid(n);await animateBullet();const o=await t;return await(o?animateExplosion():animateBounce()),o}const RT=new Set(["node","python","python3","ruby","java","php","deno","bun"]);async function getAppLabel(n){try{let t="",o="";if(isWin){const e=await run(`tasklist /FI "PID eq ${n}" /FO CSV /NH`).catch(()=>"");t=e.split(",")[0]?.replace(/"/g,"").trim()??"",o=await run(`powershell -NoProfile -Command "(Get-Process -Id ${n} -ErrorAction SilentlyContinue).CommandLine"`).catch(()=>"")}else[o,t]=await Promise.all([run(`ps -p ${n} -o args=`).catch(()=>""),run(`ps -p ${n} -o comm=`).catch(()=>"")]);const e=(o||t).trim().split(/\s+/),s=(t||e[0]?.split(/[/\\]/).pop()||"").replace(/\.exe$/i,"").toLowerCase(),i=(e.slice(1).find(n=>!n.startsWith("-"))??"").split(/[/\\]/).pop();return RT.has(s)&&i?`${w(s)} ${g("·")} ${c(i)}`:w(s||"unknown")}catch{return g("unknown")}}async function getPidsForPort(n){const t=new Map;if(isWin){const o=await run("netstat -ano -p TCP").catch(()=>"");for(const e of o.split("\n")){if(!e.match(new RegExp(`:${n}[^\\d]`)))continue;const o=e.trim().split(/\s+/);if(o.length<5)continue;const[,,,s,i]=o;["LISTENING","ESTABLISHED"].includes(s)&&i&&!t.has(i)&&t.set(i,{pid:i,appLabel:await getAppLabel(i),port:n})}}else{const o=await run(`lsof -nP -i TCP:${n}`).catch(()=>"");for(const e of o.split("\n").slice(1)){if(!e)continue;const o=e.trim().split(/\s+/)[1];o&&!t.has(o)&&t.set(o,{pid:o,appLabel:await getAppLabel(o),port:n})}}return t}async function getAllListeningPids(){const n=new Map;if(isWin){const t=await run("netstat -ano -p TCP").catch(()=>"");for(const o of t.split("\n")){if(!o.includes("LISTENING"))continue;const t=o.trim().split(/\s+/);if(t.length<5)continue;const e=t[4],s=t[1]?.split(":").pop()??"?";e&&"0"!==e&&"4"!==e&&!n.has(e)&&n.set(e,{pid:e,appLabel:await getAppLabel(e),port:s})}}else{const t=await run("lsof -nP -i TCP -s TCP:LISTEN").catch(()=>"");for(const o of t.split("\n").slice(1)){if(!o)continue;const t=o.trim().split(/\s+/)[1],e=o.match(/:(\d+)\s+\(LISTEN\)/),s=e?.[1]??"?";t&&!n.has(t)&&n.set(t,{pid:t,appLabel:await getAppLabel(t),port:s})}}return n}async function killPid(n){if(!isWin){for(const t of isFo?["-9"]:["-15","-9"])try{await run(`kill ${t} ${n}`),await sl(200);if(!(await run(`ps -p ${n} -o pid=`).catch(()=>"")).trim())return log(`PID ${n} gone (${t})`),!0}catch{return!0}return!1}try{return await run(`taskkill ${isFo?"/F":""} /PID ${n}`),log(`PID ${n} terminated`),!0}catch(n){return log(r(n.message.split("\n").find(n=>n.trim())??n.message)),!1}}let nK=0,nF=0,nN=0;async function killPort(n){if(!/^\d+$/.test(n)||+n<1||+n>65535)return void console.log(r(` ✗ Invalid port: ${n}`));const t=await getPidsForPort(n);if(0===t.size)return console.log(` ${g("○")} :${w(n)} ${g("— nothing listening")}`),void nN++;for(const o of t.values()){console.log(`\n ${c("▸")} :${bc(n)} ${g(`PID ${o.pid}`)} ${o.appLabel}\n`);const t=await killWithAnimation(o.pid);console.log(t?` ${r("☠")} :${bm(n)} killed ${g("←")} ${o.appLabel}`:` ${r("✗")} :${bo(n)} could not be killed — try ${y("--soft")}`),t?nK++:nF++,console.log()}}async function killAll(){const n=await getAllListeningPids();if(0!==n.size){console.log(c(`\n ${n.size} process(es) listening:\n`));for(const{pid:t,appLabel:o,port:e}of n.values())console.log(` ${g(`:${String(e).padEnd(6)}`)} ${g(`PID ${String(t).padStart(6)}`)} ${o}`);if(console.log(),await confirm(`Terminate ALL ${n.size} processes?`))for(const t of n.values()){console.log(`\n ${c("▸")} :${bc(t.port)} ${g(`PID ${t.pid}`)} ${t.appLabel}\n`);const n=await killWithAnimation(t.pid);console.log(n?` ${r("☠")} :${bm(t.port)} killed ${g("←")} ${t.appLabel}`:` ${r("✗")} :${bo(t.port)} could not be killed`),n?nK++:nF++,console.log()}else console.log(g("\n Aborted.\n"))}else console.log(g(" ○ No listening processes found."))}function showHelp(){console.log(`\n ${r("♜")} ${bo("killtask")}\n\n ${c("Usage:")}\n killtask <port> [port ...]\n killtask --all\n\n ${c("Options:")}\n --soft, --no-force Graceful SIGTERM before SIGKILL\n --all Kill every listening process\n --yes, -y Skip --all confirmation\n --verbose, -v Show signal details\n --help, -h This help\n\n ${c("Examples:")}\n killtask 3000\n killtask 3000 8080\n killtask --all -y\n`)}async function main(){(args.includes("--help")||args.includes("-h"))&&(showHelp(),process.exit(0)),isAl||0!==ports.length||(console.error(r("\n ❌ Provide at least one port, or use --all\n")),showHelp(),process.exit(1));const n=isAl?r("all processes"):ports.map(n=>c(`:${n}`)).join(g(" "));console.log(`\n ${r("♜")} ${bo("killtask")} ${g("·")} ${n}\n`);try{if(isAl)await killAll();else for(const n of ports)await killPort(n);const n=[];nK&&n.push(G(`☠ ${nK} killed`)),nF&&n.push(r(`✗ ${nF} failed`)),nN&&n.push(g(`○ ${nN} not found`)),n.length&&console.log(" "+n.join(g(" · "))),console.log()}catch(n){clrLine(),console.error(r(" ❌"),n.message),process.exit(1)}}main();
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "killtask",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "Port killer CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"killtask": "./index.js"
|
|
7
|
+
"killtask": "./index.min.js"
|
|
8
8
|
},
|
|
9
9
|
"keywords": [
|
|
10
10
|
"cli",
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
21
|
-
"url": "https://github.com/ksanjeeb/
|
|
21
|
+
"url": "https://github.com/ksanjeeb/killtask.git"
|
|
22
22
|
},
|
|
23
|
-
"homepage": "https://github.com/ksanjeeb/
|
|
23
|
+
"homepage": "https://github.com/ksanjeeb/killtask#readme",
|
|
24
24
|
"bugs": {
|
|
25
|
-
"url": "https://github.com/ksanjeeb/
|
|
25
|
+
"url": "https://github.com/ksanjeeb/killtask/issues"
|
|
26
26
|
}
|
|
27
27
|
}
|
package/readme.md
CHANGED
|
@@ -1,9 +1,34 @@
|
|
|
1
|
+
<img width="615" height="137" alt="image" src="https://github.com/user-attachments/assets/f54de43b-f9a3-4ab8-86e7-ee50e6bc59f6" />
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
# killtask
|
|
2
|
-
|
|
5
|
+
|
|
6
|
+
CLI to kill processes running on specific ports.
|
|
3
7
|
|
|
4
8
|
## Usage
|
|
5
9
|
|
|
10
|
+
Kill a single port:
|
|
11
|
+
|
|
12
|
+
```
|
|
6
13
|
npx killtask 8080
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Kill multiple ports:
|
|
17
|
+
|
|
18
|
+
```
|
|
7
19
|
npx killtask 8080 3000
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Kill all listening ports:
|
|
23
|
+
|
|
24
|
+
```
|
|
8
25
|
npx killtask --all
|
|
9
|
-
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## Options
|
|
30
|
+
|
|
31
|
+
* `--all` – Kill all listening ports
|
|
32
|
+
* `--help` – Show help
|
|
33
|
+
* `--version` – Show version
|
|
34
|
+
|
package/index.js
DELETED
|
@@ -1,378 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { exec } from "child_process";
|
|
4
|
-
import os from "os";
|
|
5
|
-
import readline from "readline";
|
|
6
|
-
|
|
7
|
-
// ─── ANSI ─────────────────────────────────────────────────────────────────────
|
|
8
|
-
const c = {
|
|
9
|
-
reset: "\x1b[0m",
|
|
10
|
-
bold: "\x1b[1m",
|
|
11
|
-
gray: "\x1b[90m",
|
|
12
|
-
red: "\x1b[31m",
|
|
13
|
-
green: "\x1b[32m",
|
|
14
|
-
yellow: "\x1b[33m",
|
|
15
|
-
cyan: "\x1b[36m",
|
|
16
|
-
white: "\x1b[97m",
|
|
17
|
-
magenta: "\x1b[35m",
|
|
18
|
-
bgRed: "\x1b[41m",
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const clr = (code, str) => `${code}${str}${c.reset}`;
|
|
22
|
-
const gray = s => clr(c.gray, s);
|
|
23
|
-
const red = s => clr(c.red, s);
|
|
24
|
-
const green = s => clr(c.green, s);
|
|
25
|
-
const yellow = s => clr(c.yellow, s);
|
|
26
|
-
const cyan = s => clr(c.cyan, s);
|
|
27
|
-
const white = s => clr(c.white, s);
|
|
28
|
-
const magenta = s => clr(c.magenta, s);
|
|
29
|
-
const bold = s => clr(c.bold, s);
|
|
30
|
-
const boldCyan = s => `${c.bold}${c.cyan}${s}${c.reset}`;
|
|
31
|
-
const boldMagenta = s => `${c.bold}${c.magenta}${s}${c.reset}`;
|
|
32
|
-
const hitLabel = s => `${c.bgRed}${c.bold} ${s} ${c.reset}`;
|
|
33
|
-
|
|
34
|
-
// ─── Args ─────────────────────────────────────────────────────────────────────
|
|
35
|
-
const args = process.argv.slice(2);
|
|
36
|
-
const FLAGS = new Set(["--soft","--no-force","--all","--yes","-y","--verbose","-v","--help","-h"]);
|
|
37
|
-
const isForce = !args.includes("--soft") && !args.includes("--no-force");
|
|
38
|
-
const isAll = args.includes("--all");
|
|
39
|
-
const isYes = args.includes("--yes") || args.includes("-y");
|
|
40
|
-
const isVerbose = args.includes("--verbose") || args.includes("-v");
|
|
41
|
-
const ports = args.filter(a => !FLAGS.has(a) && !a.startsWith("-"));
|
|
42
|
-
const isWin = os.platform() === "win32";
|
|
43
|
-
|
|
44
|
-
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
45
|
-
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
|
46
|
-
|
|
47
|
-
function run(cmd) {
|
|
48
|
-
return new Promise((resolve, reject) => {
|
|
49
|
-
exec(cmd, { timeout: 8_000 }, (err, stdout) => {
|
|
50
|
-
if (err) return reject(err);
|
|
51
|
-
resolve(stdout.trim());
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function log(...msg) {
|
|
57
|
-
if (isVerbose) console.log(gray(" ·"), ...msg);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function confirm(q) {
|
|
61
|
-
if (isYes) return Promise.resolve(true);
|
|
62
|
-
return new Promise(resolve => {
|
|
63
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
64
|
-
rl.question(yellow(` ⚠️ ${q} [y/N] `), a => { rl.close(); resolve(a.toLowerCase() === "y"); });
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function clearLine() {
|
|
69
|
-
process.stdout.write("\r" + " ".repeat(72) + "\r");
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// ─── Animation ────────────────────────────────────────────────────────────────
|
|
73
|
-
const TRACK = 24;
|
|
74
|
-
const MARIO = red("♜");
|
|
75
|
-
const ENEMY = `\x1b[32m♟${c.reset}`;
|
|
76
|
-
|
|
77
|
-
function drawBulletFrame(pos, enemyChar, label) {
|
|
78
|
-
let track = "";
|
|
79
|
-
for (let i = 0; i < TRACK; i++) {
|
|
80
|
-
if (i === pos) track += yellow("►");
|
|
81
|
-
else if (i < pos) track += gray("·");
|
|
82
|
-
else track += gray("─");
|
|
83
|
-
}
|
|
84
|
-
process.stdout.write(`\r ${MARIO} ${track} ${enemyChar} ${label}`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async function animateBullet() {
|
|
88
|
-
for (let i = 0; i < 3; i++) {
|
|
89
|
-
process.stdout.write(
|
|
90
|
-
`\r ${MARIO} ${gray("─".repeat(TRACK))} ${ENEMY} ` +
|
|
91
|
-
(i % 2 === 0 ? gray("aiming…") : yellow("🔫 FIRE!"))
|
|
92
|
-
);
|
|
93
|
-
await sleep(180);
|
|
94
|
-
}
|
|
95
|
-
for (let pos = 0; pos < TRACK; pos++) {
|
|
96
|
-
drawBulletFrame(pos, ENEMY, white("pew pew…"));
|
|
97
|
-
await sleep(36);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async function animateExplosion() {
|
|
102
|
-
const frames = [
|
|
103
|
-
[yellow("✸"), hitLabel("HIT") ],
|
|
104
|
-
[red("✺"), red("💥 BOOM!") ],
|
|
105
|
-
[yellow("✦"), red("☠ TERMINATED") ],
|
|
106
|
-
[white("·"), green("✔ port killed") ],
|
|
107
|
-
[" ", green("✔ port killed") ],
|
|
108
|
-
];
|
|
109
|
-
for (const [sym, label] of frames) {
|
|
110
|
-
process.stdout.write(`\r ${MARIO} ${gray("·".repeat(TRACK))} ${sym} ${label}`);
|
|
111
|
-
await sleep(85);
|
|
112
|
-
}
|
|
113
|
-
clearLine();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async function animateBounce() {
|
|
117
|
-
for (let pos = TRACK - 1; pos >= 0; pos--) {
|
|
118
|
-
let track = "";
|
|
119
|
-
for (let i = 0; i < TRACK; i++) {
|
|
120
|
-
if (i === pos) track += red("◄");
|
|
121
|
-
else if (i > pos) track += gray("·");
|
|
122
|
-
else track += gray("─");
|
|
123
|
-
}
|
|
124
|
-
process.stdout.write(`\r ${MARIO} ${track} ${red("⛨")} ${red("blocked!")}`);
|
|
125
|
-
await sleep(22);
|
|
126
|
-
}
|
|
127
|
-
clearLine();
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
async function killWithAnimation(pid) {
|
|
131
|
-
const killPromise = killPid(pid);
|
|
132
|
-
await animateBullet();
|
|
133
|
-
const ok = await killPromise;
|
|
134
|
-
if (ok) await animateExplosion();
|
|
135
|
-
else await animateBounce();
|
|
136
|
-
return ok;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// ─── App Name ─────────────────────────────────────────────────────────────────
|
|
140
|
-
const RUNTIMES = new Set(["node","python","python3","ruby","java","php","deno","bun"]);
|
|
141
|
-
|
|
142
|
-
async function getAppLabel(pid) {
|
|
143
|
-
try {
|
|
144
|
-
let name = "", cmdLine = "";
|
|
145
|
-
if (isWin) {
|
|
146
|
-
const tl = await run(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`).catch(() => "");
|
|
147
|
-
name = tl.split(",")[0]?.replace(/"/g, "").trim() ?? "";
|
|
148
|
-
const ps = await run(
|
|
149
|
-
`powershell -NoProfile -Command "(Get-Process -Id ${pid} -ErrorAction SilentlyContinue).CommandLine"`
|
|
150
|
-
).catch(() => "");
|
|
151
|
-
cmdLine = ps.trim();
|
|
152
|
-
} else {
|
|
153
|
-
[cmdLine, name] = await Promise.all([
|
|
154
|
-
run(`ps -p ${pid} -o args=`).catch(() => ""),
|
|
155
|
-
run(`ps -p ${pid} -o comm=`).catch(() => ""),
|
|
156
|
-
]);
|
|
157
|
-
}
|
|
158
|
-
return buildLabel(name, cmdLine);
|
|
159
|
-
} catch {
|
|
160
|
-
return gray("unknown");
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function buildLabel(name, cmdLine) {
|
|
165
|
-
const tokens = (cmdLine || name).trim().split(/\s+/);
|
|
166
|
-
const bin = (name || tokens[0]?.split(/[/\\]/).pop() || "")
|
|
167
|
-
.replace(/\.exe$/i, "").toLowerCase();
|
|
168
|
-
const entry = tokens.slice(1).find(t => !t.startsWith("-")) ?? "";
|
|
169
|
-
const short = entry.split(/[/\\]/).pop();
|
|
170
|
-
if (RUNTIMES.has(bin) && short)
|
|
171
|
-
return `${white(bin)} ${gray("·")} ${cyan(short)}`;
|
|
172
|
-
return white(bin || "unknown");
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// ─── Process Discovery ────────────────────────────────────────────────────────
|
|
176
|
-
async function getPidsForPort(port) {
|
|
177
|
-
const pids = new Map();
|
|
178
|
-
if (isWin) {
|
|
179
|
-
const out = await run(`netstat -ano -p TCP`).catch(() => "");
|
|
180
|
-
for (const line of out.split("\n")) {
|
|
181
|
-
if (!line.match(new RegExp(`:${port}[^\\d]`))) continue;
|
|
182
|
-
const p = line.trim().split(/\s+/);
|
|
183
|
-
if (p.length < 5) continue;
|
|
184
|
-
const [,,, state, pid] = p;
|
|
185
|
-
if (!["LISTENING","ESTABLISHED"].includes(state)) continue;
|
|
186
|
-
if (pid && !pids.has(pid))
|
|
187
|
-
pids.set(pid, { pid, appLabel: await getAppLabel(pid), port });
|
|
188
|
-
}
|
|
189
|
-
} else {
|
|
190
|
-
const out = await run(`lsof -nP -i TCP:${port}`).catch(() => "");
|
|
191
|
-
for (const line of out.split("\n").slice(1)) {
|
|
192
|
-
if (!line) continue;
|
|
193
|
-
const pid = line.trim().split(/\s+/)[1];
|
|
194
|
-
if (pid && !pids.has(pid))
|
|
195
|
-
pids.set(pid, { pid, appLabel: await getAppLabel(pid), port });
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
return pids;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
async function getAllListeningPids() {
|
|
202
|
-
const pids = new Map();
|
|
203
|
-
if (isWin) {
|
|
204
|
-
const out = await run(`netstat -ano -p TCP`).catch(() => "");
|
|
205
|
-
for (const line of out.split("\n")) {
|
|
206
|
-
if (!line.includes("LISTENING")) continue;
|
|
207
|
-
const p = line.trim().split(/\s+/);
|
|
208
|
-
if (p.length < 5) continue;
|
|
209
|
-
const pid = p[4];
|
|
210
|
-
const port = p[1]?.split(":").pop() ?? "?";
|
|
211
|
-
if (pid && pid !== "0" && pid !== "4" && !pids.has(pid))
|
|
212
|
-
pids.set(pid, { pid, appLabel: await getAppLabel(pid), port });
|
|
213
|
-
}
|
|
214
|
-
} else {
|
|
215
|
-
const out = await run(`lsof -nP -i TCP -s TCP:LISTEN`).catch(() => "");
|
|
216
|
-
for (const line of out.split("\n").slice(1)) {
|
|
217
|
-
if (!line) continue;
|
|
218
|
-
const parts = line.trim().split(/\s+/);
|
|
219
|
-
const pid = parts[1];
|
|
220
|
-
const m = line.match(/:(\d+)\s+\(LISTEN\)/);
|
|
221
|
-
const port = m?.[1] ?? "?";
|
|
222
|
-
if (pid && !pids.has(pid))
|
|
223
|
-
pids.set(pid, { pid, appLabel: await getAppLabel(pid), port });
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
return pids;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// ─── Kill ─────────────────────────────────────────────────────────────────────
|
|
230
|
-
async function killPid(pid) {
|
|
231
|
-
if (isWin) {
|
|
232
|
-
try {
|
|
233
|
-
await run(`taskkill ${isForce ? "/F" : ""} /PID ${pid}`);
|
|
234
|
-
log(`PID ${pid} terminated`);
|
|
235
|
-
return true;
|
|
236
|
-
} catch (e) {
|
|
237
|
-
log(red(e.message.split("\n").find(l => l.trim()) ?? e.message));
|
|
238
|
-
return false;
|
|
239
|
-
}
|
|
240
|
-
} else {
|
|
241
|
-
const sigs = isForce ? ["-9"] : ["-15", "-9"];
|
|
242
|
-
for (const sig of sigs) {
|
|
243
|
-
try {
|
|
244
|
-
await run(`kill ${sig} ${pid}`);
|
|
245
|
-
await sleep(200);
|
|
246
|
-
const still = await run(`ps -p ${pid} -o pid=`).catch(() => "");
|
|
247
|
-
if (!still.trim()) { log(`PID ${pid} gone (${sig})`); return true; }
|
|
248
|
-
} catch { return true; }
|
|
249
|
-
}
|
|
250
|
-
return false;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// ─── Counters ─────────────────────────────────────────────────────────────────
|
|
255
|
-
let nKilled = 0, nFailed = 0, nNotFound = 0;
|
|
256
|
-
|
|
257
|
-
// ─── killPort ─────────────────────────────────────────────────────────────────
|
|
258
|
-
async function killPort(port) {
|
|
259
|
-
if (!/^\d+$/.test(port) || +port < 1 || +port > 65535) {
|
|
260
|
-
console.log(red(` ✗ Invalid port: ${port}`)); return;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const pids = await getPidsForPort(port);
|
|
264
|
-
|
|
265
|
-
if (pids.size === 0) {
|
|
266
|
-
console.log(` ${gray("○")} :${white(port)} ${gray("— nothing listening")}`);
|
|
267
|
-
nNotFound++; return;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
for (const entry of pids.values()) {
|
|
271
|
-
console.log(
|
|
272
|
-
`\n ${cyan("▸")} :${boldCyan(port)} ` +
|
|
273
|
-
`${gray(`PID ${entry.pid}`)} ${entry.appLabel}\n`
|
|
274
|
-
);
|
|
275
|
-
const ok = await killWithAnimation(entry.pid);
|
|
276
|
-
if (ok) {
|
|
277
|
-
console.log(` ${red("☠")} :${boldMagenta(port)} killed ${gray("←")} ${entry.appLabel}`);
|
|
278
|
-
nKilled++;
|
|
279
|
-
} else {
|
|
280
|
-
console.log(` ${red("✗")} :${bold(port)} could not be killed — try ${yellow("--soft")}`);
|
|
281
|
-
nFailed++;
|
|
282
|
-
}
|
|
283
|
-
console.log();
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// ─── killAll ──────────────────────────────────────────────────────────────────
|
|
288
|
-
async function killAll() {
|
|
289
|
-
const pids = await getAllListeningPids();
|
|
290
|
-
if (pids.size === 0) {
|
|
291
|
-
console.log(gray(" ○ No listening processes found.")); return;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
console.log(cyan(`\n ${pids.size} process(es) listening:\n`));
|
|
295
|
-
for (const { pid, appLabel, port } of pids.values())
|
|
296
|
-
console.log(` ${gray(`:${String(port).padEnd(6)}`)} ${gray(`PID ${String(pid).padStart(6)}`)} ${appLabel}`);
|
|
297
|
-
console.log();
|
|
298
|
-
|
|
299
|
-
const ok = await confirm(`Terminate ALL ${pids.size} processes?`);
|
|
300
|
-
if (!ok) { console.log(gray("\n Aborted.\n")); return; }
|
|
301
|
-
|
|
302
|
-
for (const entry of pids.values()) {
|
|
303
|
-
console.log(
|
|
304
|
-
`\n ${cyan("▸")} :${boldCyan(entry.port)} ` +
|
|
305
|
-
`${gray(`PID ${entry.pid}`)} ${entry.appLabel}\n`
|
|
306
|
-
);
|
|
307
|
-
const killed = await killWithAnimation(entry.pid);
|
|
308
|
-
if (killed) {
|
|
309
|
-
console.log(` ${red("☠")} :${boldMagenta(entry.port)} killed ${gray("←")} ${entry.appLabel}`);
|
|
310
|
-
nKilled++;
|
|
311
|
-
} else {
|
|
312
|
-
console.log(` ${red("✗")} :${bold(entry.port)} could not be killed`);
|
|
313
|
-
nFailed++;
|
|
314
|
-
}
|
|
315
|
-
console.log();
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// ─── Help ─────────────────────────────────────────────────────────────────────
|
|
320
|
-
function showHelp() {
|
|
321
|
-
console.log(`
|
|
322
|
-
${red("♜")} ${bold("killport")}
|
|
323
|
-
|
|
324
|
-
${cyan("Usage:")}
|
|
325
|
-
killport <port> [port ...]
|
|
326
|
-
killport --all
|
|
327
|
-
|
|
328
|
-
${cyan("Options:")}
|
|
329
|
-
--soft, --no-force Graceful SIGTERM before SIGKILL
|
|
330
|
-
--all Kill every listening process
|
|
331
|
-
--yes, -y Skip --all confirmation
|
|
332
|
-
--verbose, -v Show signal details
|
|
333
|
-
--help, -h This help
|
|
334
|
-
|
|
335
|
-
${cyan("Examples:")}
|
|
336
|
-
killport 3000
|
|
337
|
-
killport 3000 8080
|
|
338
|
-
killport --all -y
|
|
339
|
-
`);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
343
|
-
async function main() {
|
|
344
|
-
if (args.includes("--help") || args.includes("-h")) { showHelp(); process.exit(0); }
|
|
345
|
-
|
|
346
|
-
if (!isAll && ports.length === 0) {
|
|
347
|
-
console.error(red("\n ❌ Provide at least one port, or use --all\n"));
|
|
348
|
-
showHelp(); process.exit(1);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
const target = isAll
|
|
352
|
-
? red("all processes")
|
|
353
|
-
: ports.map(p => cyan(`:${p}`)).join(gray(" "));
|
|
354
|
-
|
|
355
|
-
console.log(`\n ${red("♜")} ${bold("killport")} ${gray("·")} ${target}\n`);
|
|
356
|
-
|
|
357
|
-
try {
|
|
358
|
-
if (isAll) {
|
|
359
|
-
await killAll();
|
|
360
|
-
} else {
|
|
361
|
-
for (const port of ports) await killPort(port);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const parts = [];
|
|
365
|
-
if (nKilled) parts.push(green(`☠ ${nKilled} killed`));
|
|
366
|
-
if (nFailed) parts.push(red(`✗ ${nFailed} failed`));
|
|
367
|
-
if (nNotFound) parts.push(gray(`○ ${nNotFound} not found`));
|
|
368
|
-
if (parts.length) console.log(" " + parts.join(gray(" · ")));
|
|
369
|
-
console.log();
|
|
370
|
-
|
|
371
|
-
} catch (err) {
|
|
372
|
-
clearLine();
|
|
373
|
-
console.error(red(" ❌"), err.message);
|
|
374
|
-
process.exit(1);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
main();
|