portctl 0.1.1__tar.gz
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.
- portctl-0.1.1/.github/workflows/ci.yml +44 -0
- portctl-0.1.1/.github/workflows/release.yml +57 -0
- portctl-0.1.1/.gitignore +31 -0
- portctl-0.1.1/LICENSE +21 -0
- portctl-0.1.1/PKG-INFO +209 -0
- portctl-0.1.1/README.md +178 -0
- portctl-0.1.1/pyproject.toml +59 -0
- portctl-0.1.1/src/portctl/__init__.py +7 -0
- portctl-0.1.1/src/portctl/classifier.py +43 -0
- portctl-0.1.1/src/portctl/cli.py +376 -0
- portctl-0.1.1/src/portctl/display.py +249 -0
- portctl-0.1.1/src/portctl/frameworks.py +258 -0
- portctl-0.1.1/src/portctl/killer.py +87 -0
- portctl-0.1.1/src/portctl/scanner.py +206 -0
- portctl-0.1.1/src/portctl/utils.py +89 -0
- portctl-0.1.1/tests/__init__.py +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, dev]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v5
|
|
14
|
+
- uses: actions/setup-python@v6
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.12"
|
|
17
|
+
- run: pip install ruff mypy
|
|
18
|
+
- run: ruff check src/
|
|
19
|
+
- run: mypy src/portctl/ --ignore-missing-imports
|
|
20
|
+
|
|
21
|
+
test:
|
|
22
|
+
runs-on: ${{ matrix.os }}
|
|
23
|
+
strategy:
|
|
24
|
+
matrix:
|
|
25
|
+
os:
|
|
26
|
+
- ubuntu-latest
|
|
27
|
+
- ubuntu-24.04-arm
|
|
28
|
+
- macos-latest
|
|
29
|
+
- windows-latest
|
|
30
|
+
- windows-11-arm
|
|
31
|
+
python-version: ["3.9", "3.12", "3.13"]
|
|
32
|
+
exclude:
|
|
33
|
+
- os: windows-11-arm
|
|
34
|
+
python-version: "3.9"
|
|
35
|
+
- os: ubuntu-24.04-arm
|
|
36
|
+
python-version: "3.9"
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v5
|
|
39
|
+
- uses: actions/setup-python@v6
|
|
40
|
+
with:
|
|
41
|
+
python-version: ${{ matrix.python-version }}
|
|
42
|
+
- run: pip install -e .
|
|
43
|
+
- run: portctl --version
|
|
44
|
+
- run: portctl --help
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: Release & Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
release:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
environment: pypi
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v5
|
|
17
|
+
with:
|
|
18
|
+
fetch-depth: 0
|
|
19
|
+
fetch-tags: true
|
|
20
|
+
|
|
21
|
+
- name: Get version from pyproject.toml
|
|
22
|
+
id: version
|
|
23
|
+
run: |
|
|
24
|
+
VERSION=$(grep '^version' pyproject.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
|
|
25
|
+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
26
|
+
echo "tag=v$VERSION" >> "$GITHUB_OUTPUT"
|
|
27
|
+
|
|
28
|
+
- name: Check if tag exists
|
|
29
|
+
id: check
|
|
30
|
+
run: |
|
|
31
|
+
if git rev-parse "v${{ steps.version.outputs.version }}" >/dev/null 2>&1; then
|
|
32
|
+
echo "exists=true" >> "$GITHUB_OUTPUT"
|
|
33
|
+
else
|
|
34
|
+
echo "exists=false" >> "$GITHUB_OUTPUT"
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
- name: Create release
|
|
38
|
+
if: steps.check.outputs.exists == 'false'
|
|
39
|
+
run: |
|
|
40
|
+
gh release create "${{ steps.version.outputs.tag }}" \
|
|
41
|
+
--title "${{ steps.version.outputs.tag }}" \
|
|
42
|
+
--generate-notes
|
|
43
|
+
env:
|
|
44
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
45
|
+
|
|
46
|
+
# Build and publish always run — skip-existing makes re-runs safe
|
|
47
|
+
- uses: actions/setup-python@v6
|
|
48
|
+
with:
|
|
49
|
+
python-version: "3.12"
|
|
50
|
+
|
|
51
|
+
- name: Build package
|
|
52
|
+
run: pip install build && python -m build
|
|
53
|
+
|
|
54
|
+
- name: Publish to PyPI
|
|
55
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
56
|
+
with:
|
|
57
|
+
skip-existing: true
|
portctl-0.1.1/.gitignore
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
*.pyo
|
|
5
|
+
*.egg-info/
|
|
6
|
+
*.egg
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
.eggs/
|
|
10
|
+
|
|
11
|
+
# Virtual environments
|
|
12
|
+
.venv/
|
|
13
|
+
venv/
|
|
14
|
+
env/
|
|
15
|
+
|
|
16
|
+
# Tools
|
|
17
|
+
.mypy_cache/
|
|
18
|
+
.pytest_cache/
|
|
19
|
+
.ruff_cache/
|
|
20
|
+
.coverage
|
|
21
|
+
htmlcov/
|
|
22
|
+
|
|
23
|
+
# IDE
|
|
24
|
+
.vscode/
|
|
25
|
+
.idea/
|
|
26
|
+
*.swp
|
|
27
|
+
*.swo
|
|
28
|
+
|
|
29
|
+
# OS
|
|
30
|
+
.DS_Store
|
|
31
|
+
Thumbs.db
|
portctl-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 porthog contributors
|
|
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.
|
portctl-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: portctl
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Manage your ports — view, inspect, and kill processes on TCP ports
|
|
5
|
+
Project-URL: Homepage, https://github.com/mubbie/portctl
|
|
6
|
+
Project-URL: Repository, https://github.com/mubbie/portctl
|
|
7
|
+
Project-URL: Issues, https://github.com/mubbie/portctl/issues
|
|
8
|
+
Author: Mubbie Idoko
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: cli,devtools,network,ports,process
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
24
|
+
Classifier: Topic :: System :: Networking
|
|
25
|
+
Classifier: Topic :: Utilities
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
|
+
Requires-Dist: psutil>=5.9
|
|
28
|
+
Requires-Dist: rich>=13.0
|
|
29
|
+
Requires-Dist: typer>=0.9
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# portctl
|
|
33
|
+
|
|
34
|
+
> Manage your ports.
|
|
35
|
+
|
|
36
|
+
A cross-platform Python CLI tool for viewing, managing, and killing processes on TCP ports.
|
|
37
|
+
|
|
38
|
+
Inspired by [port-whisperer](https://github.com/LarsenCundric/port-whisperer) by [Larsen Cundric](https://x.com/larsencc).
|
|
39
|
+
|
|
40
|
+
## What it looks like
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
$ portctl
|
|
44
|
+
|
|
45
|
+
┌──────────────────────────────────────────┐
|
|
46
|
+
│ portctl │
|
|
47
|
+
│ scanning your ports... │
|
|
48
|
+
└──────────────────────────────────────────┘
|
|
49
|
+
|
|
50
|
+
PORT PROCESS PID PROJECT FRAMEWORK UPTIME MEM BIND STATUS
|
|
51
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
52
|
+
:8000 python.exe 14320 payments-api FastAPI 3h 12m 48.7 MB public ● healthy
|
|
53
|
+
:5173 node 52981 dashboard Vite 22m 112.4 MB local ● healthy
|
|
54
|
+
:8080 go 8734 gateway Go 6d 1h 23.1 MB public ● healthy
|
|
55
|
+
:5432 docker 7712 — Docker (PG) 14d 8h 67.0 MB local ● healthy
|
|
56
|
+
|
|
57
|
+
4 ports active · Run portctl <port> for details · --all to show everything
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
$ portctl 5173
|
|
62
|
+
|
|
63
|
+
┌─────────────────────── Port :5173 ───────────────────────┐
|
|
64
|
+
│ │
|
|
65
|
+
│ Process node │
|
|
66
|
+
│ PID 52981 │
|
|
67
|
+
│ Status ● healthy │
|
|
68
|
+
│ Framework Vite │
|
|
69
|
+
│ Memory 112.4 MB │
|
|
70
|
+
│ Uptime 22m │
|
|
71
|
+
│ Started 2026-04-04 10:15:42 │
|
|
72
|
+
│ Bind local (127.0.0.1) │
|
|
73
|
+
│ Command node node_modules/.bin/vite --port 5173 │
|
|
74
|
+
│ │
|
|
75
|
+
│ Directory /home/mubarak/projects/dashboard │
|
|
76
|
+
│ Project dashboard │
|
|
77
|
+
│ Git Branch feat/charts │
|
|
78
|
+
│ │
|
|
79
|
+
│ Process Tree │
|
|
80
|
+
│ → node (52981) │
|
|
81
|
+
│ └─ bash (52900) │
|
|
82
|
+
│ └─ tmux: server (1120) │
|
|
83
|
+
│ │
|
|
84
|
+
└──────────────────────────────────────────────────────────┘
|
|
85
|
+
|
|
86
|
+
Run portctl kill 5173 to stop · portctl cmd 5173 to see startup command
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
$ portctl 8000-8100
|
|
91
|
+
|
|
92
|
+
:8000 python.exe (PID 14320) [FastAPI]
|
|
93
|
+
:8001 free
|
|
94
|
+
:8002 free
|
|
95
|
+
...
|
|
96
|
+
:8080 go (PID 8734) [Go]
|
|
97
|
+
:8081 free
|
|
98
|
+
...
|
|
99
|
+
:8100 free
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
$ portctl free 5173 -- npm run dev
|
|
104
|
+
|
|
105
|
+
✓ Killed node (PID 52981) on port 5173
|
|
106
|
+
Running: npm run dev
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
$ portctl kill 8000 8080
|
|
111
|
+
|
|
112
|
+
✓ Killed python.exe (PID 14320) on port 8000
|
|
113
|
+
✓ Killed go (PID 8734) on port 8080
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Install
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
pip install portctl
|
|
120
|
+
# or
|
|
121
|
+
pipx install portctl
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Quick Start
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
portctl # Show dev server ports
|
|
128
|
+
portctl 3000 # Inspect what's on port 3000
|
|
129
|
+
portctl kill 3000 # Kill it
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Commands
|
|
133
|
+
|
|
134
|
+
### List ports
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
portctl # Dev server ports only
|
|
138
|
+
portctl --all # All listening ports
|
|
139
|
+
portctl --sort mem # Sort by memory (also: uptime)
|
|
140
|
+
portctl -f django # Filter by process, framework, or project
|
|
141
|
+
portctl -n 5 # Limit to top N rows
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Inspect a port
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
portctl 3000 # Detail view if occupied, "available" if free
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Shows process info, framework, memory, uptime, bind address, working directory, git branch, and process tree.
|
|
151
|
+
|
|
152
|
+
### Scan a range
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
portctl 8000-9000 # Show which ports are in use
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Kill processes
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
portctl kill 3000 # Kill process on port 3000
|
|
162
|
+
portctl kill 3000 8080 # Kill multiple ports
|
|
163
|
+
portctl kill 3000 --force # Force kill (SIGKILL)
|
|
164
|
+
portctl kill 3000 --dry-run # Preview without killing
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Free a port and run a command
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
portctl free 3000 -- npm start # Kill port 3000, then run npm start
|
|
171
|
+
portctl free 3000 5432 -- docker compose up # Free multiple, then run
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Show startup command
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
portctl cmd 3000 # Print the command that started the process
|
|
178
|
+
portctl cmd 3000 --copy # Copy it to clipboard
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Utilities
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
portctl open 3000 # Open http://localhost:3000 in browser
|
|
185
|
+
portctl copy 3000 # Copy http://localhost:3000 to clipboard
|
|
186
|
+
portctl clean # Kill orphaned/zombie dev processes
|
|
187
|
+
portctl clean --dry-run # Preview without killing
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Features
|
|
191
|
+
|
|
192
|
+
- **Cross-platform** -- works on macOS, Linux, and Windows
|
|
193
|
+
- **Framework detection** -- identifies Next.js, Vite, Django, FastAPI, Flask, Express, and 20+ others
|
|
194
|
+
- **Project detection** -- finds project root and name from package.json, pyproject.toml, Cargo.toml, etc.
|
|
195
|
+
- **Smart filtering** -- shows only dev processes by default, use `--all` for everything
|
|
196
|
+
- **Protected processes** -- refuses to kill system-critical processes (systemd, lsass, etc.)
|
|
197
|
+
- **Git branch** -- shows which branch each process is running from
|
|
198
|
+
|
|
199
|
+
## Platform Support
|
|
200
|
+
|
|
201
|
+
| Platform | Status |
|
|
202
|
+
|----------|--------|
|
|
203
|
+
| macOS | Supported (use `sudo` for full visibility) |
|
|
204
|
+
| Linux | Supported |
|
|
205
|
+
| Windows | Supported (admin recommended for full PID visibility) |
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
MIT
|
portctl-0.1.1/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# portctl
|
|
2
|
+
|
|
3
|
+
> Manage your ports.
|
|
4
|
+
|
|
5
|
+
A cross-platform Python CLI tool for viewing, managing, and killing processes on TCP ports.
|
|
6
|
+
|
|
7
|
+
Inspired by [port-whisperer](https://github.com/LarsenCundric/port-whisperer) by [Larsen Cundric](https://x.com/larsencc).
|
|
8
|
+
|
|
9
|
+
## What it looks like
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
$ portctl
|
|
13
|
+
|
|
14
|
+
┌──────────────────────────────────────────┐
|
|
15
|
+
│ portctl │
|
|
16
|
+
│ scanning your ports... │
|
|
17
|
+
└──────────────────────────────────────────┘
|
|
18
|
+
|
|
19
|
+
PORT PROCESS PID PROJECT FRAMEWORK UPTIME MEM BIND STATUS
|
|
20
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
21
|
+
:8000 python.exe 14320 payments-api FastAPI 3h 12m 48.7 MB public ● healthy
|
|
22
|
+
:5173 node 52981 dashboard Vite 22m 112.4 MB local ● healthy
|
|
23
|
+
:8080 go 8734 gateway Go 6d 1h 23.1 MB public ● healthy
|
|
24
|
+
:5432 docker 7712 — Docker (PG) 14d 8h 67.0 MB local ● healthy
|
|
25
|
+
|
|
26
|
+
4 ports active · Run portctl <port> for details · --all to show everything
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
$ portctl 5173
|
|
31
|
+
|
|
32
|
+
┌─────────────────────── Port :5173 ───────────────────────┐
|
|
33
|
+
│ │
|
|
34
|
+
│ Process node │
|
|
35
|
+
│ PID 52981 │
|
|
36
|
+
│ Status ● healthy │
|
|
37
|
+
│ Framework Vite │
|
|
38
|
+
│ Memory 112.4 MB │
|
|
39
|
+
│ Uptime 22m │
|
|
40
|
+
│ Started 2026-04-04 10:15:42 │
|
|
41
|
+
│ Bind local (127.0.0.1) │
|
|
42
|
+
│ Command node node_modules/.bin/vite --port 5173 │
|
|
43
|
+
│ │
|
|
44
|
+
│ Directory /home/mubarak/projects/dashboard │
|
|
45
|
+
│ Project dashboard │
|
|
46
|
+
│ Git Branch feat/charts │
|
|
47
|
+
│ │
|
|
48
|
+
│ Process Tree │
|
|
49
|
+
│ → node (52981) │
|
|
50
|
+
│ └─ bash (52900) │
|
|
51
|
+
│ └─ tmux: server (1120) │
|
|
52
|
+
│ │
|
|
53
|
+
└──────────────────────────────────────────────────────────┘
|
|
54
|
+
|
|
55
|
+
Run portctl kill 5173 to stop · portctl cmd 5173 to see startup command
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
$ portctl 8000-8100
|
|
60
|
+
|
|
61
|
+
:8000 python.exe (PID 14320) [FastAPI]
|
|
62
|
+
:8001 free
|
|
63
|
+
:8002 free
|
|
64
|
+
...
|
|
65
|
+
:8080 go (PID 8734) [Go]
|
|
66
|
+
:8081 free
|
|
67
|
+
...
|
|
68
|
+
:8100 free
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
$ portctl free 5173 -- npm run dev
|
|
73
|
+
|
|
74
|
+
✓ Killed node (PID 52981) on port 5173
|
|
75
|
+
Running: npm run dev
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
$ portctl kill 8000 8080
|
|
80
|
+
|
|
81
|
+
✓ Killed python.exe (PID 14320) on port 8000
|
|
82
|
+
✓ Killed go (PID 8734) on port 8080
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Install
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pip install portctl
|
|
89
|
+
# or
|
|
90
|
+
pipx install portctl
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Quick Start
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
portctl # Show dev server ports
|
|
97
|
+
portctl 3000 # Inspect what's on port 3000
|
|
98
|
+
portctl kill 3000 # Kill it
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Commands
|
|
102
|
+
|
|
103
|
+
### List ports
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
portctl # Dev server ports only
|
|
107
|
+
portctl --all # All listening ports
|
|
108
|
+
portctl --sort mem # Sort by memory (also: uptime)
|
|
109
|
+
portctl -f django # Filter by process, framework, or project
|
|
110
|
+
portctl -n 5 # Limit to top N rows
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Inspect a port
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
portctl 3000 # Detail view if occupied, "available" if free
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Shows process info, framework, memory, uptime, bind address, working directory, git branch, and process tree.
|
|
120
|
+
|
|
121
|
+
### Scan a range
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
portctl 8000-9000 # Show which ports are in use
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Kill processes
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
portctl kill 3000 # Kill process on port 3000
|
|
131
|
+
portctl kill 3000 8080 # Kill multiple ports
|
|
132
|
+
portctl kill 3000 --force # Force kill (SIGKILL)
|
|
133
|
+
portctl kill 3000 --dry-run # Preview without killing
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Free a port and run a command
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
portctl free 3000 -- npm start # Kill port 3000, then run npm start
|
|
140
|
+
portctl free 3000 5432 -- docker compose up # Free multiple, then run
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Show startup command
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
portctl cmd 3000 # Print the command that started the process
|
|
147
|
+
portctl cmd 3000 --copy # Copy it to clipboard
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Utilities
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
portctl open 3000 # Open http://localhost:3000 in browser
|
|
154
|
+
portctl copy 3000 # Copy http://localhost:3000 to clipboard
|
|
155
|
+
portctl clean # Kill orphaned/zombie dev processes
|
|
156
|
+
portctl clean --dry-run # Preview without killing
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Features
|
|
160
|
+
|
|
161
|
+
- **Cross-platform** -- works on macOS, Linux, and Windows
|
|
162
|
+
- **Framework detection** -- identifies Next.js, Vite, Django, FastAPI, Flask, Express, and 20+ others
|
|
163
|
+
- **Project detection** -- finds project root and name from package.json, pyproject.toml, Cargo.toml, etc.
|
|
164
|
+
- **Smart filtering** -- shows only dev processes by default, use `--all` for everything
|
|
165
|
+
- **Protected processes** -- refuses to kill system-critical processes (systemd, lsass, etc.)
|
|
166
|
+
- **Git branch** -- shows which branch each process is running from
|
|
167
|
+
|
|
168
|
+
## Platform Support
|
|
169
|
+
|
|
170
|
+
| Platform | Status |
|
|
171
|
+
|----------|--------|
|
|
172
|
+
| macOS | Supported (use `sudo` for full visibility) |
|
|
173
|
+
| Linux | Supported |
|
|
174
|
+
| Windows | Supported (admin recommended for full PID visibility) |
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "portctl"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "Manage your ports — view, inspect, and kill processes on TCP ports"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = {text = "MIT"}
|
|
7
|
+
requires-python = ">=3.9"
|
|
8
|
+
authors = [
|
|
9
|
+
{name = "Mubbie Idoko"},
|
|
10
|
+
]
|
|
11
|
+
keywords = ["ports", "cli", "devtools", "process", "network"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Environment :: Console",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.9",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Topic :: Software Development :: Build Tools",
|
|
25
|
+
"Topic :: System :: Networking",
|
|
26
|
+
"Topic :: Utilities",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"psutil>=5.9",
|
|
30
|
+
"rich>=13.0",
|
|
31
|
+
"typer>=0.9",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/mubbie/portctl"
|
|
36
|
+
Repository = "https://github.com/mubbie/portctl"
|
|
37
|
+
Issues = "https://github.com/mubbie/portctl/issues"
|
|
38
|
+
|
|
39
|
+
[project.scripts]
|
|
40
|
+
portctl = "portctl.cli:_cli_entry"
|
|
41
|
+
|
|
42
|
+
[build-system]
|
|
43
|
+
requires = ["hatchling"]
|
|
44
|
+
build-backend = "hatchling.build"
|
|
45
|
+
|
|
46
|
+
[tool.hatch.build.targets.wheel]
|
|
47
|
+
packages = ["src/portctl"]
|
|
48
|
+
|
|
49
|
+
[tool.ruff]
|
|
50
|
+
target-version = "py39"
|
|
51
|
+
line-length = 120
|
|
52
|
+
|
|
53
|
+
[tool.ruff.lint]
|
|
54
|
+
select = ["E", "F", "I", "W"]
|
|
55
|
+
|
|
56
|
+
[tool.mypy]
|
|
57
|
+
python_version = "3.9"
|
|
58
|
+
warn_return_any = true
|
|
59
|
+
warn_unused_configs = true
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Signal-based dev process classification.
|
|
2
|
+
|
|
3
|
+
Uses positive signals to identify dev processes rather than maintaining
|
|
4
|
+
platform-specific blocklists. A process is "dev" if ANY signal fires.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from portctl.scanner import ProcessInfo
|
|
13
|
+
|
|
14
|
+
from portctl.frameworks import KNOWN_RUNTIMES, is_docker_process
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _signal_has_project_root(proc: ProcessInfo) -> bool:
|
|
18
|
+
return proc.project_root is not None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _signal_known_runtime(proc: ProcessInfo) -> bool:
|
|
22
|
+
if not proc.normalized_name:
|
|
23
|
+
return False
|
|
24
|
+
return proc.normalized_name in KNOWN_RUNTIMES
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _signal_docker_process(proc: ProcessInfo) -> bool:
|
|
28
|
+
return is_docker_process(proc.process_name)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _signal_has_framework(proc: ProcessInfo) -> bool:
|
|
32
|
+
"""If detect_framework already identified a framework, it's a dev process."""
|
|
33
|
+
return proc.framework is not None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def is_dev_process(proc: ProcessInfo) -> bool:
|
|
37
|
+
"""Returns True if ANY signal fires. Use --all to bypass."""
|
|
38
|
+
return (
|
|
39
|
+
_signal_has_project_root(proc)
|
|
40
|
+
or _signal_known_runtime(proc)
|
|
41
|
+
or _signal_docker_process(proc)
|
|
42
|
+
or _signal_has_framework(proc)
|
|
43
|
+
)
|