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.
@@ -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
@@ -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
@@ -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,7 @@
1
+ """portctl — manage your ports.
2
+
3
+ A cross-platform CLI for viewing, inspecting, and killing processes on TCP ports.
4
+ Uses psutil for cross-platform support (macOS, Linux, Windows).
5
+ """
6
+
7
+ __version__ = "0.1.1"
@@ -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
+ )