bhtool 0.2.0__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.0
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
@@ -21,28 +21,29 @@ Assorted CLI utilities that are handy in Bosco’s day-to-day work—version bum
21
21
  ### Install options
22
22
 
23
23
 
24
- | Option | Description |
25
- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
24
+ | Option | Description |
25
+ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
26
26
  | **`uvx bhtool …`** | Run from PyPI on demand (no clone). Example:`uvx bhtool --help`, `uvx bhtool run cursor`. |
27
- | **`uv tool install bhtool`** | Install**`b`** / **`bhtool`** globally from PyPI (released versions). |
27
+ | **`uv tool install bhtool`** | Install**`b`** / **`bhtool`** globally from PyPI (released versions). |
28
28
  | **`uv sync`** then **`uv run bhtool …`** | Use the project venv from a checkout (no global install). |
29
- | **`uv tool install .`** | Install**`b`** / **`bhtool`** globally from the current directory (pinned copy). |
30
- | **`uv tool install --editable .`** | Install**`b`** / **`bhtool`** globally in **editable** mode from a checkout (code changes apply without reinstall). |
29
+ | **`uv tool install .`** | Install**`b`** / **`bhtool`** globally from the current directory (pinned copy). |
30
+ | **`uv tool install --editable .`** | Install**`b`** / **`bhtool`** globally in **editable** mode from a checkout (code changes apply without reinstall). |
31
31
 
32
32
  ## Commands
33
33
 
34
34
 
35
- | Command | Description | |
36
- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- |
37
- | **bumpver** | Bump version in`pyproject.toml`, commit, push; PyPI publish is on by default (`--no-publish` to skip) | |
38
- | **run** | Open macOS applications by partial name (e.g.`uvx bhtool run cursor` or `b run cursor` after a local install) | |
39
- | **clr-chmod** | Remove execute bit from non-shell files; tighten group/other write | |
40
- | **npread** | Print the shape of NumPy`.npy` files | |
41
- | **psword** | Find processes by name;`--kill` to terminate | |
42
- | **rm-npm** | Recursively delete`node_modules` dirs and `package-lock.json` files | |
43
- | **text** | Convert between Markdown, HTML, DOCX, and Pug (needs`pandoc` and related tools on `PATH`) | |
44
- | **todict** | JSON or YAML file, or stdin, → Python`dict(...)`-style repr on stdout | |
45
- | **movies** | `uvx bhtool movies` (or `b movies`) — ask an LLM for normalized names; writes `movie_mapping.json` in the **current working directory**; `--execute` applies renames (default is dry-run table) | |
35
+ | Command | Description | |
36
+ | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- |
37
+ | **bumpver** | Bump version in`pyproject.toml`, commit, push; PyPI publish is on by default (`--no-publish` to skip) | |
38
+ | **run** | Open macOS applications by partial name (e.g.`uvx bhtool run cursor` or `b run cursor` after a local install) | |
39
+ | **clr-chmod** | Remove execute bit from non-shell files; tighten group/other write | |
40
+ | **npread** | Print the shape of NumPy`.npy` files | |
41
+ | **psword** | Find processes by name;`--kill` to terminate | |
42
+ | **portkill** | Find processes listening on TCP ports;`--kill` to terminate | |
43
+ | **rm-npm** | Recursively delete`node_modules` dirs and `package-lock.json` files | |
44
+ | **text** | Convert between Markdown, HTML, DOCX, and Pug (needs`pandoc` and related tools on `PATH`) | |
45
+ | **todict** | JSON or YAML file, or stdin, → Python`dict(...)`-style repr on stdout | |
46
+ | **movies** | `uvx bhtool movies` (or `b movies`) — ask an LLM for normalized names; writes `movie_mapping.json` in the **current working directory**; `--execute` applies renames (default is dry-run table) | |
46
47
 
47
48
  ### Movies + LLM
48
49
 
@@ -59,7 +60,7 @@ Node CLI: pug formatting, agent config copy. Usage: `jtools <subcommand>`
59
60
  **Run (after install):** `npx jtools <subcommand>` resolves the local `jtools` binary (same as `node_modules/.bin/jtools`). Examples: `npx jtools format-pug`, `npx jtools copy-agent`.
60
61
 
61
62
 
62
- | Subcommand | Description |
63
- | ---------------- | ------------------------------------------- |
64
- | **format-pug** | Format pug in`./index.html` |
63
+ | Subcommand | Description |
64
+ | -------------- | ---------------------------------------- |
65
+ | **format-pug** | Format pug in`./index.html` |
65
66
  | **copy-agent** | Copy`~/.claude/CLAUDE.md` → `./AGENT.md` |
bhtool-0.2.2/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # bhtool
2
+
3
+ Assorted CLI utilities that are handy in Bosco’s day-to-day work—version bumps, file conversions, process helpers, macOS shortcuts, LLM-assisted movie renaming, and other small odds and ends. **Python ≥3.13.**
4
+
5
+ ### Install options
6
+
7
+
8
+ | Option | Description |
9
+ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
10
+ | **`uvx bhtool …`** | Run from PyPI on demand (no clone). Example:`uvx bhtool --help`, `uvx bhtool run cursor`. |
11
+ | **`uv tool install bhtool`** | Install**`b`** / **`bhtool`** globally from PyPI (released versions). |
12
+ | **`uv sync`** then **`uv run bhtool …`** | Use the project venv from a checkout (no global install). |
13
+ | **`uv tool install .`** | Install**`b`** / **`bhtool`** globally from the current directory (pinned copy). |
14
+ | **`uv tool install --editable .`** | Install**`b`** / **`bhtool`** globally in **editable** mode from a checkout (code changes apply without reinstall). |
15
+
16
+ ## Commands
17
+
18
+
19
+ | Command | Description | |
20
+ | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- |
21
+ | **bumpver** | Bump version in`pyproject.toml`, commit, push; PyPI publish is on by default (`--no-publish` to skip) | |
22
+ | **run** | Open macOS applications by partial name (e.g.`uvx bhtool run cursor` or `b run cursor` after a local install) | |
23
+ | **clr-chmod** | Remove execute bit from non-shell files; tighten group/other write | |
24
+ | **npread** | Print the shape of NumPy`.npy` files | |
25
+ | **psword** | Find processes by name;`--kill` to terminate | |
26
+ | **portkill** | Find processes listening on TCP ports;`--kill` to terminate | |
27
+ | **rm-npm** | Recursively delete`node_modules` dirs and `package-lock.json` files | |
28
+ | **text** | Convert between Markdown, HTML, DOCX, and Pug (needs`pandoc` and related tools on `PATH`) | |
29
+ | **todict** | JSON or YAML file, or stdin, → Python`dict(...)`-style repr on stdout | |
30
+ | **movies** | `uvx bhtool movies` (or `b movies`) — ask an LLM for normalized names; writes `movie_mapping.json` in the **current working directory**; `--execute` applies renames (default is dry-run table) | |
31
+
32
+ ### Movies + LLM
33
+
34
+ `LLM_SERVICE` selects the backend (`openai`, `groq`, `ollama`, `bedrock`, …). Set it in the process environment or in **`bhtool/.env`** (loaded from the installed package directory). Model defaults come from [microeval](https://pypi.org/project/microeval/)’s config for that service.
35
+
36
+ ---
37
+
38
+ ## jtools
39
+
40
+ Node CLI: pug formatting, agent config copy. Usage: `jtools <subcommand>`
41
+
42
+ **Install:** `cd jtools && npm install` — dependencies only; this package is not published, so run it from that directory (or `npm link` after install).
43
+
44
+ **Run (after install):** `npx jtools <subcommand>` resolves the local `jtools` binary (same as `node_modules/.bin/jtools`). Examples: `npx jtools format-pug`, `npx jtools copy-agent`.
45
+
46
+
47
+ | Subcommand | Description |
48
+ | -------------- | ---------------------------------------- |
49
+ | **format-pug** | Format pug in`./index.html` |
50
+ | **copy-agent** | Copy`~/.claude/CLAUDE.md` → `./AGENT.md` |
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """Unified bhtool CLI (`b` and `bhtool` on PATH)."""
4
+
5
+ from typing import Literal
6
+
7
+ from cyclopts import App
8
+
9
+ app = App(name="bhtool", help="boscoh tools")
10
+
11
+
12
+ @app.command(name="run")
13
+ def run_cmd(*params: str):
14
+ """Open macOS applications by partial name match."""
15
+ if not params:
16
+ app["run"].help_print()
17
+ return
18
+ import bhtool.run as run_mod
19
+
20
+ run_mod.run(*params)
21
+
22
+
23
+ @app.command(name="bumpver")
24
+ def bumpver_cmd(
25
+ part: Literal["major", "minor", "patch"] | None = None, *, publish: bool = True
26
+ ):
27
+ """Bump version in pyproject.toml, commit, push, and optionally publish to PyPI."""
28
+ import bhtool.bumpver as bumpver_mod
29
+
30
+ bumpver_mod.bumpver(part, publish=publish)
31
+
32
+
33
+ @app.command(name="clr_chmod")
34
+ def clr_chmod_cmd():
35
+ """Remove execute permissions from non-script files and write permissions from group/other."""
36
+ import bhtool.clr_chmod as clr_chmod_mod
37
+
38
+ clr_chmod_mod.clr_chmod()
39
+
40
+
41
+ @app.command(name="npread")
42
+ def npread_cmd(*files: str):
43
+ """Print shape of numpy .npy files."""
44
+ if not files:
45
+ app["npread"].help_print()
46
+ return
47
+ import bhtool.npread as npread_mod
48
+
49
+ npread_mod.npread(files)
50
+
51
+
52
+ @app.command(name="psword")
53
+ def psword_cmd(*words: str, kill: bool = False):
54
+ """Find processes by name and optionally kill them."""
55
+ if not words:
56
+ app["psword"].help_print()
57
+ return
58
+ import bhtool.psword as psword_mod
59
+
60
+ psword_mod.psword(words, kill=kill)
61
+
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
+
78
+ @app.command(name="rm_npm")
79
+ def rm_npm_cmd():
80
+ """Recursively remove node_modules directories and package-lock.json files."""
81
+ import bhtool.rm_npm as rm_npm_mod
82
+
83
+ rm_npm_mod.rm_npm()
84
+
85
+
86
+ @app.command(name="text")
87
+ def text_cmd(in_fname: str | None = None, out_fname: str | None = None):
88
+ """Interconvert text-based formats: md, pug, docx, html."""
89
+ if in_fname is None or out_fname is None:
90
+ app["text"].help_print()
91
+ return
92
+ import bhtool.text as text_mod
93
+
94
+ text_mod.convert_text(in_fname, out_fname)
95
+
96
+
97
+ @app.command(name="todict")
98
+ def todict_cmd(json: str | None = None, yaml: str | None = None):
99
+ """Convert JSON or YAML to Python dict format."""
100
+ import bhtool.todict as todict_mod
101
+
102
+ todict_mod.todict(json=json, yaml=yaml)
103
+
104
+
105
+ @app.command(name="movies")
106
+ def movies_cmd(root_dir: str | None = None, execute: bool = False):
107
+ """Normalize movie directory and file names with an LLM (dry-run table by default).
108
+
109
+ :param root_dir: Root directory containing movies (positional); default is current working directory.
110
+ :param execute: If true, perform renames; otherwise dry run (table output).
111
+ """
112
+ import bhtool.list_movies as list_movies_mod
113
+
114
+ if root_dir == "rename":
115
+ root_dir = None
116
+ list_movies_mod.rename(root_dir=root_dir, execute=execute)
117
+
118
+
119
+ def main() -> None:
120
+ app()
@@ -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}")
@@ -4,7 +4,7 @@
4
4
 
5
5
  from path import Path
6
6
 
7
- from bhtool.utils import run
7
+ from bhtool.utils import run as run_shell
8
8
 
9
9
  search_dirs = [Path("/Applications"), Path.home() / "MyApps"]
10
10
 
@@ -20,4 +20,4 @@ def run(*params: str):
20
20
 
21
21
  params = [f'"{p}"' for p in params]
22
22
  cmd = "open -a " + " ".join(params)
23
- run(cmd)
23
+ run_shell(cmd)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bhtool"
3
- version = "0.2.0"
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"
bhtool-0.2.0/README.md DELETED
@@ -1,49 +0,0 @@
1
- # bhtool
2
-
3
- Assorted CLI utilities that are handy in Bosco’s day-to-day work—version bumps, file conversions, process helpers, macOS shortcuts, LLM-assisted movie renaming, and other small odds and ends. **Python ≥3.13.**
4
-
5
- ### Install options
6
-
7
-
8
- | Option | Description |
9
- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
10
- | **`uvx bhtool …`** | Run from PyPI on demand (no clone). Example:`uvx bhtool --help`, `uvx bhtool run cursor`. |
11
- | **`uv tool install bhtool`** | Install**`b`** / **`bhtool`** globally from PyPI (released versions). |
12
- | **`uv sync`** then **`uv run bhtool …`** | Use the project venv from a checkout (no global install). |
13
- | **`uv tool install .`** | Install**`b`** / **`bhtool`** globally from the current directory (pinned copy). |
14
- | **`uv tool install --editable .`** | Install**`b`** / **`bhtool`** globally in **editable** mode from a checkout (code changes apply without reinstall). |
15
-
16
- ## Commands
17
-
18
-
19
- | Command | Description | |
20
- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- |
21
- | **bumpver** | Bump version in`pyproject.toml`, commit, push; PyPI publish is on by default (`--no-publish` to skip) | |
22
- | **run** | Open macOS applications by partial name (e.g.`uvx bhtool run cursor` or `b run cursor` after a local install) | |
23
- | **clr-chmod** | Remove execute bit from non-shell files; tighten group/other write | |
24
- | **npread** | Print the shape of NumPy`.npy` files | |
25
- | **psword** | Find processes by name;`--kill` to terminate | |
26
- | **rm-npm** | Recursively delete`node_modules` dirs and `package-lock.json` files | |
27
- | **text** | Convert between Markdown, HTML, DOCX, and Pug (needs`pandoc` and related tools on `PATH`) | |
28
- | **todict** | JSON or YAML file, or stdin, → Python`dict(...)`-style repr on stdout | |
29
- | **movies** | `uvx bhtool movies` (or `b movies`) — ask an LLM for normalized names; writes `movie_mapping.json` in the **current working directory**; `--execute` applies renames (default is dry-run table) | |
30
-
31
- ### Movies + LLM
32
-
33
- `LLM_SERVICE` selects the backend (`openai`, `groq`, `ollama`, `bedrock`, …). Set it in the process environment or in **`bhtool/.env`** (loaded from the installed package directory). Model defaults come from [microeval](https://pypi.org/project/microeval/)’s config for that service.
34
-
35
- ---
36
-
37
- ## jtools
38
-
39
- Node CLI: pug formatting, agent config copy. Usage: `jtools <subcommand>`
40
-
41
- **Install:** `cd jtools && npm install` — dependencies only; this package is not published, so run it from that directory (or `npm link` after install).
42
-
43
- **Run (after install):** `npx jtools <subcommand>` resolves the local `jtools` binary (same as `node_modules/.bin/jtools`). Examples: `npx jtools format-pug`, `npx jtools copy-agent`.
44
-
45
-
46
- | Subcommand | Description |
47
- | ---------------- | ------------------------------------------- |
48
- | **format-pug** | Format pug in`./index.html` |
49
- | **copy-agent** | Copy`~/.claude/CLAUDE.md` → `./AGENT.md` |
@@ -1,105 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- """Unified bhtool CLI (`b` and `bhtool` on PATH)."""
4
-
5
- from typing import Literal
6
-
7
- from cyclopts import App
8
-
9
- app = App(name="bhtool", help="boscoh tools")
10
-
11
-
12
- @app.command(name="run")
13
- def run(*params: str):
14
- """Open macOS applications by partial name match."""
15
- if not params:
16
- app["run"].help_print()
17
- return
18
- from bhtool.run import run
19
-
20
- run(*params)
21
-
22
-
23
- @app.command
24
- def bumpver(
25
- part: Literal["major", "minor", "patch"] | None = None, *, publish: bool = True
26
- ):
27
- """Bump version in pyproject.toml, commit, push, and optionally publish to PyPI."""
28
- from bhtool.bumpver import bumpver
29
-
30
- bumpver(part, publish=publish)
31
-
32
-
33
- @app.command
34
- def clr_chmod():
35
- """Remove execute permissions from non-script files and write permissions from group/other."""
36
- from bhtool.clr_chmod import clr_chmod as clr_chmod_run
37
-
38
- clr_chmod_run()
39
-
40
-
41
- @app.command
42
- def npread(*files: str):
43
- """Print shape of numpy .npy files."""
44
- if not files:
45
- app["npread"].help_print()
46
- return
47
- from bhtool.npread import npread
48
-
49
- npread(files)
50
-
51
-
52
- @app.command
53
- def psword(*words: str, kill: bool = False):
54
- """Find processes by name and optionally kill them."""
55
- if not words:
56
- app["psword"].help_print()
57
- return
58
- from bhtool.psword import psword
59
-
60
- psword(words, kill=kill)
61
-
62
-
63
- @app.command
64
- def rm_npm():
65
- """Recursively remove node_modules directories and package-lock.json files."""
66
- from bhtool.rm_npm import rm_npm
67
-
68
- rm_npm()
69
-
70
-
71
- @app.command
72
- def text(in_fname: str | None = None, out_fname: str | None = None):
73
- """Interconvert text-based formats: md, pug, docx, html."""
74
- if in_fname is None or out_fname is None:
75
- app["text"].help_print()
76
- return
77
- from bhtool.text import convert_text
78
-
79
- convert_text(in_fname, out_fname)
80
-
81
-
82
- @app.command
83
- def todict(json: str | None = None, yaml: str | None = None):
84
- """Convert JSON or YAML to Python dict format."""
85
- from bhtool.todict import todict
86
-
87
- todict(json=json, yaml=yaml)
88
-
89
-
90
- @app.command(name="movies")
91
- def movies(root_dir: str | None = None, execute: bool = False):
92
- """Normalize movie directory and file names with an LLM (dry-run table by default).
93
-
94
- :param root_dir: Root directory containing movies (positional); default is current working directory.
95
- :param execute: If true, perform renames; otherwise dry run (table output).
96
- """
97
- from bhtool.list_movies import rename
98
-
99
- if root_dir == "rename":
100
- root_dir = None
101
- rename(root_dir=root_dir, execute=execute)
102
-
103
-
104
- def main() -> None:
105
- app()
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