redpill-cli 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Miguel Medeiros
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,138 @@
1
+ <p align="center">
2
+ <br />
3
+ <img src="assets/banner.jpg" alt="redpill" width="600" />
4
+ <br />
5
+ <br />
6
+ <code>&nbsp;💊 redpill&nbsp;</code>
7
+ <br />
8
+ <br />
9
+ <em>"You take the red pill, I show you how deep the rabbit hole goes."</em>
10
+ <br />
11
+ <sub>— Morpheus</sub>
12
+ <br />
13
+ <br />
14
+ <a href="https://www.npmjs.com/package/redpill"><img src="https://img.shields.io/npm/v/redpill.svg?style=flat-square&color=cc0000" alt="npm version" /></a>
15
+ <a href="https://www.npmjs.com/package/redpill"><img src="https://img.shields.io/npm/dm/redpill.svg?style=flat-square&color=cc0000" alt="downloads" /></a>
16
+ <a href="https://github.com/miguelmedeiros/port/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/redpill.svg?style=flat-square&color=cc0000" alt="license" /></a>
17
+ </p>
18
+
19
+ ---
20
+
21
+ > **The Matrix has your ports.** Every developer has seen `EADDRINUSE`. You Google it. You run `lsof`. You copy the PID. You run `kill`. Every. Single. Time.
22
+ >
23
+ > **Take the red pill.** See the truth. Free your ports. One command.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ npm i -g redpill
29
+ ```
30
+
31
+ Or run directly — no install needed:
32
+
33
+ ```bash
34
+ npx redpill 3000
35
+ ```
36
+
37
+ ## Wake Up, Neo...
38
+
39
+ ### See the truth about a port
40
+
41
+ ```bash
42
+ redpill 3000
43
+ ```
44
+
45
+ ```
46
+ 💊 redpill 3000
47
+
48
+ PID 2847
49
+ Name node
50
+ Cmd next dev
51
+ User neo
52
+
53
+ Kill this process? (y/n)
54
+ ```
55
+
56
+ ### Free your port — no questions asked
57
+
58
+ ```bash
59
+ redpill free 3000
60
+ ```
61
+
62
+ > _"There is no spoon."_ There is no port conflict. Just free it.
63
+
64
+ ### Free an entire range
65
+
66
+ ```bash
67
+ redpill free 3000-3010
68
+ ```
69
+
70
+ > _"I know Kung Fu."_ — You, after freeing 10 ports in one command.
71
+
72
+ ### See all listening ports
73
+
74
+ ```bash
75
+ redpill list
76
+ # or
77
+ redpill ls
78
+ ```
79
+
80
+ ```
81
+ 💊 redpill ls
82
+
83
+ PORT PID NAME COMMAND
84
+ ────────────────────────────────────────────────────────────
85
+ 3000 2847 node next dev
86
+ 5432 1203 postgres /usr/lib/postgresql/14/bin/…
87
+ 8080 3891 node vite
88
+
89
+ 3 ports in use
90
+ ```
91
+
92
+ > _"I can only show you the door. You're the one that has to walk through it."_
93
+
94
+ ### Help
95
+
96
+ ```bash
97
+ redpill --help
98
+ ```
99
+
100
+ ## Why?
101
+
102
+ You've been living in the **blue pill** world:
103
+
104
+ ```
105
+ Error: listen EADDRINUSE: address already in use :::3000
106
+ ```
107
+
108
+ ```bash
109
+ # The blue pill way (every single time)
110
+ lsof -i :3000
111
+ # scroll through output...
112
+ kill -9 2847
113
+ # pray it worked...
114
+ ```
115
+
116
+ **Take the red pill:**
117
+
118
+ ```bash
119
+ # The red pill way
120
+ redpill 3000
121
+ # → node (PID 2847) - next dev
122
+ # → Kill? y
123
+ # ✓ Done.
124
+ ```
125
+
126
+ > _"Welcome to the real world."_
127
+
128
+ ## Platforms
129
+
130
+ Works on **macOS** and **Linux** (uses `lsof` under the hood).
131
+
132
+ > _"Unfortunately, no one can be told what the Matrix is. You have to see it for yourself."_
133
+ >
134
+ > Run `redpill ls` and see.
135
+
136
+ ## License
137
+
138
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import{execSync as f}from"child_process";function h(e){try{let n=f(`lsof -i :${e} -P -n -sTCP:LISTEN 2>/dev/null`,{encoding:"utf-8"}).trim().split(`
3
+ `).slice(1),t=new Set,l=[];for(let r of n){let i=r.trim().split(/\s+/);if(i.length<9)continue;let c=i[0],p=parseInt(i[1],10),$=i[2];if(Number.isNaN(p)||t.has(p))continue;t.add(p);let a=N(p)||c;l.push({pid:p,name:c,user:$,command:a})}return l}catch{return[]}}function y(){try{let s=f("lsof -i -P -n -sTCP:LISTEN 2>/dev/null",{encoding:"utf-8"}).trim().split(`
4
+ `).slice(1),n=new Set,t=[];for(let l of s){let r=l.trim().split(/\s+/);if(r.length<9)continue;let i=r[0],c=parseInt(r[1],10),p=r[2],$=r[8];if(Number.isNaN(c))continue;let a=$.match(/:(\d+)$/);if(!a)continue;let S=parseInt(a[1],10),x=`${c}:${S}`;if(n.has(x))continue;n.add(x);let H=N(c)||i;t.push({port:S,process:{pid:c,name:i,user:p,command:H}})}return t.sort((l,r)=>l.port-r.port),t}catch{return[]}}function N(e){try{return f(`ps -p ${e} -o command= 2>/dev/null`,{encoding:"utf-8"}).trim()}catch{return""}}function w(e){try{process.kill(e,"SIGTERM");let s=!0;for(let n=0;n<10;n++)try{process.kill(e,0),f("sleep 0.1")}catch{s=!1;break}if(s)try{process.kill(e,"SIGKILL")}catch{}return!0}catch{return!1}}function k(e){let s=[];for(let n of e)if(n.includes("-")){let[t,l]=n.split("-"),r=parseInt(t,10),i=parseInt(l,10);if(Number.isNaN(r)||Number.isNaN(i)||r>i||!g(r)||!g(i))continue;for(let c=r;c<=i;c++)s.push(c)}else{let t=parseInt(n,10);!Number.isNaN(t)&&g(t)&&s.push(t)}return s}function g(e){return e>=1&&e<=65535}import{createInterface as A}from"readline";import D from"picocolors";function I(e){return new Promise(s=>{let n=A({input:process.stdin,output:process.stdout});n.question(` ${e} ${D.dim("(y/n)")} `,t=>{n.close();let l=t.trim().toLowerCase();s(l==="y"||l==="yes")})})}import o from"picocolors";var L="1.0.0",G=`
5
+ ${o.bold(o.red("\u{1F48A} redpill"))} ${o.dim(`v${L}`)}
6
+ ${o.dim('Kill "port already in use" forever.')}
7
+ `;function E(){console.log(`redpill v${L}`)}function b(){console.log(G),console.log(` ${o.bold("Usage:")}`),console.log(),console.log(` ${o.red("redpill")} ${o.white("<port>")} Check port & offer to kill`),console.log(` ${o.red("redpill")} ${o.white("free <port|range>")} Free ports (no prompt)`),console.log(` ${o.red("redpill")} ${o.white("list")} | ${o.white("ls")} List all listening ports`),console.log(` ${o.red("redpill")} ${o.dim("-h, --help")} Show this help`),console.log(` ${o.red("redpill")} ${o.dim("-v, --version")} Show version`),console.log(),console.log(` ${o.bold("Examples:")}`),console.log(),console.log(` ${o.dim("$")} ${o.white("redpill 3000")} ${o.dim("Who's using port 3000?")}`),console.log(` ${o.dim("$")} ${o.white("redpill free 3000")} ${o.dim("Kill it immediately")}`),console.log(` ${o.dim("$")} ${o.white("redpill free 3000-3010")} ${o.dim("Free an entire range")}`),console.log(` ${o.dim("$")} ${o.white("redpill ls")} ${o.dim("Show all listening ports")}`),console.log()}function F(e,s){console.log(),console.log(` ${o.bold(o.red("\u{1F48A} redpill"))} ${o.white(String(e))}`),console.log(),console.log(` ${o.dim("PID")} ${o.bold(o.white(String(s.pid)))}`),console.log(` ${o.dim("Name")} ${o.white(s.name)}`),console.log(` ${o.dim("Cmd")} ${o.white(s.command)}`),console.log(` ${o.dim("User")} ${o.white(s.user)}`),console.log()}function K(e){console.log(` ${o.green("\u2713")} Port ${o.bold(String(e))} is free`)}function P(e,s){console.log(` ${o.green("\u2713")} Killed process ${o.bold(String(s))} on port ${o.bold(String(e))}`)}function v(e){console.log(` ${o.red("\u2717")} Failed to kill process ${o.bold(String(e))}. Try with ${o.yellow("sudo")}.`)}function C(){console.log(` ${o.dim("Skipped.")}`)}function u(e){console.log(` ${o.red("\u2717")} ${e}`)}function T(){console.log(),console.log(` ${o.bold(o.red("\u{1F48A} redpill"))} ${o.dim("ls")}`),console.log(),console.log(` ${o.dim(m("PORT",8))}${o.dim(m("PID",10))}${o.dim(m("NAME",16))}${o.dim("COMMAND")}`),console.log(` ${o.dim("\u2500".repeat(60))}`)}function M(e,s){console.log(` ${o.cyan(m(String(e),8))}${o.white(m(String(s.pid),10))}${o.white(m(s.name,16))}${o.dim(U(s.command,40))}`)}function O(){console.log(),console.log(` ${o.dim("No listening ports found.")}`),console.log()}function R(e,s){console.log(),console.log(e===0&&s===0?` ${o.dim("No processes found on the specified ports.")}`:` ${o.green("\u2713")} Freed ${o.bold(String(e))}/${s} ports`),console.log()}function m(e,s){return e.length>=s?`${e} `:e+" ".repeat(s-e.length)}function U(e,s){return e.length>s?`${e.slice(0,s-1)}\u2026`:e}var V=process.argv.slice(2),d=V[0];async function j(){!d||d==="-h"||d==="--help"?b():d==="-v"||d==="--version"?E():d==="list"||d==="ls"?W():d==="free"?await z(V.slice(1)):/^\d+$/.test(d)?await q(parseInt(d,10)):(u(`Unknown command: ${d}`),console.log(),b(),process.exit(1))}async function q(e){g(e)||(u(`Invalid port: ${e}. Must be between 1 and 65535.`),process.exit(1));let s=h(e);if(s.length===0){console.log(),K(e),console.log();return}for(let n of s)F(e,n),await I("Kill this process?")?w(n.pid)?P(e,n.pid):v(n.pid):C(),console.log()}async function z(e){e.length===0&&(u("Please specify a port or range. Example: port free 3000-3010"),process.exit(1));let s=k(e);s.length===0&&(u("No valid ports in the specified range."),process.exit(1));let n=0,t=0;for(let l of s){let r=h(l);for(let i of r)t++,w(i.pid)?(n++,P(l,i.pid)):v(i.pid)}R(n,t)}function W(){let e=y();if(e.length===0){O();return}T();for(let s of e)M(s.port,s.process);console.log(),console.log(` ${e.length} port${e.length!==1?"s":""} in use`),console.log()}j().catch(e=>{u(e instanceof Error?e.message:String(e)),process.exit(1)});
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "redpill-cli",
3
+ "version": "1.0.0",
4
+ "description": "Kill \"port already in use\" forever. A fast CLI to find and free ports.",
5
+ "type": "module",
6
+ "bin": {
7
+ "redpill": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsup",
14
+ "dev": "tsup --watch",
15
+ "test": "vitest run",
16
+ "test:watch": "vitest",
17
+ "test:coverage": "vitest run --coverage",
18
+ "lint": "biome check src/",
19
+ "lint:fix": "biome check --write src/",
20
+ "format": "biome format src/",
21
+ "format:fix": "biome format --write src/",
22
+ "check": "biome check --write .",
23
+ "ci": "biome ci . && vitest run && tsup",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "keywords": [
27
+ "port",
28
+ "kill",
29
+ "process",
30
+ "cli",
31
+ "lsof",
32
+ "free",
33
+ "dev",
34
+ "server",
35
+ "listen",
36
+ "address-in-use"
37
+ ],
38
+ "author": "Miguel Medeiros",
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/MiguelMedeiros/redpill.git"
43
+ },
44
+ "homepage": "https://github.com/MiguelMedeiros/redpill#readme",
45
+ "bugs": {
46
+ "url": "https://github.com/MiguelMedeiros/redpill/issues"
47
+ },
48
+ "engines": {
49
+ "node": ">=18"
50
+ },
51
+ "devDependencies": {
52
+ "@biomejs/biome": "^2.3.15",
53
+ "@types/node": "^25.2.3",
54
+ "tsup": "^8",
55
+ "typescript": "^5",
56
+ "vitest": "^4.0.18"
57
+ },
58
+ "dependencies": {
59
+ "picocolors": "^1"
60
+ }
61
+ }