bhtool 0.2.1__tar.gz → 0.2.2__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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: bhtool
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Assorted CLI utilities for Bosco’s workflows (dev, media, macOS, LLM helpers).
5
5
  Author-email: Bosco Ho <apposite@gmail.com>
6
6
  Requires-Python: >=3.13
@@ -39,6 +39,7 @@ Assorted CLI utilities that are handy in Bosco’s day-to-day work—version bum
39
39
  | **clr-chmod** | Remove execute bit from non-shell files; tighten group/other write | |
40
40
  | **npread** | Print the shape of NumPy`.npy` files | |
41
41
  | **psword** | Find processes by name;`--kill` to terminate | |
42
+ | **portkill** | Find processes listening on TCP ports;`--kill` to terminate | |
42
43
  | **rm-npm** | Recursively delete`node_modules` dirs and `package-lock.json` files | |
43
44
  | **text** | Convert between Markdown, HTML, DOCX, and Pug (needs`pandoc` and related tools on `PATH`) | |
44
45
  | **todict** | JSON or YAML file, or stdin, → Python`dict(...)`-style repr on stdout | |
@@ -23,6 +23,7 @@ Assorted CLI utilities that are handy in Bosco’s day-to-day work—version bum
23
23
  | **clr-chmod** | Remove execute bit from non-shell files; tighten group/other write | |
24
24
  | **npread** | Print the shape of NumPy`.npy` files | |
25
25
  | **psword** | Find processes by name;`--kill` to terminate | |
26
+ | **portkill** | Find processes listening on TCP ports;`--kill` to terminate | |
26
27
  | **rm-npm** | Recursively delete`node_modules` dirs and `package-lock.json` files | |
27
28
  | **text** | Convert between Markdown, HTML, DOCX, and Pug (needs`pandoc` and related tools on `PATH`) | |
28
29
  | **todict** | JSON or YAML file, or stdin, → Python`dict(...)`-style repr on stdout | |
@@ -60,6 +60,21 @@ def psword_cmd(*words: str, kill: bool = False):
60
60
  psword_mod.psword(words, kill=kill)
61
61
 
62
62
 
63
+ @app.command(name="portkill")
64
+ def portkill_cmd(*ports: int, kill: bool = False):
65
+ """Find processes listening on TCP ports and optionally kill them.
66
+
67
+ :param ports: TCP ports to inspect (one or more).
68
+ :param kill: If true, SIGKILL each listener; otherwise list them.
69
+ """
70
+ if not ports:
71
+ app["portkill"].help_print()
72
+ return
73
+ import bhtool.portkill as portkill_mod
74
+
75
+ portkill_mod.portkill(ports, kill=kill)
76
+
77
+
63
78
  @app.command(name="rm_npm")
64
79
  def rm_npm_cmd():
65
80
  """Recursively remove node_modules directories and package-lock.json files."""
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """Find and kill processes listening on TCP ports"""
4
+
5
+ import os
6
+ import re
7
+ import signal
8
+
9
+ from bhtool.utils import run_output
10
+
11
+
12
+ def _lsof_pids(port: int) -> list[int]:
13
+ txt = run_output(f"lsof -nP -iTCP:{port} -sTCP:LISTEN -t")
14
+ return [int(line) for line in txt.splitlines() if line.strip().isdigit()]
15
+
16
+
17
+ def _ss_pids(port: int) -> list[int]:
18
+ txt = run_output(f"ss -ltnp 'sport = :{port}'")
19
+ return [int(pid) for pid in re.findall(r"pid=(\d+)", txt)]
20
+
21
+
22
+ def _command(pid: int) -> str:
23
+ cmd = run_output(f"ps -p {pid} -o command=").strip()
24
+ if len(cmd) > 80:
25
+ cmd = cmd[:80] + "..."
26
+ return cmd
27
+
28
+
29
+ def find_listeners(port: int) -> list[tuple[int, str]]:
30
+ """Return ``(pid, command)`` pairs for processes LISTENing on ``port``."""
31
+ pids = _lsof_pids(port) or _ss_pids(port)
32
+ return [(pid, _command(pid)) for pid in sorted(set(pids))]
33
+
34
+
35
+ def portkill(ports: tuple[int, ...], kill: bool = False):
36
+ for port in ports:
37
+ listeners = find_listeners(port)
38
+ if not listeners:
39
+ print(f"no process listening on port {port}")
40
+ continue
41
+ for pid, cmd in listeners:
42
+ if kill:
43
+ print(f"kill target ({cmd})")
44
+ try:
45
+ os.kill(pid, signal.SIGKILL)
46
+ except ProcessLookupError:
47
+ print(f"process {pid} already gone")
48
+ else:
49
+ print(f"process {pid}: {cmd}")
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bhtool"
3
- version = "0.2.1"
3
+ version = "0.2.2"
4
4
  description = "Assorted CLI utilities for Bosco’s workflows (dev, media, macOS, LLM helpers)."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -1,25 +0,0 @@
1
- {
2
- "directories": [
3
- {
4
- "original": "bhtool",
5
- "normalized": "Bhtool"
6
- },
7
- {
8
- "original": "build",
9
- "normalized": "Build"
10
- },
11
- {
12
- "original": "dist",
13
- "normalized": "Dist"
14
- },
15
- {
16
- "original": "jtools",
17
- "normalized": "Jtools"
18
- },
19
- {
20
- "original": "src",
21
- "normalized": "Src"
22
- }
23
- ],
24
- "files": []
25
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes