optforge 0.2.0__tar.gz → 0.3.0__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.
- {optforge-0.2.0 → optforge-0.3.0}/.gitignore +4 -0
- optforge-0.3.0/Makefile +41 -0
- {optforge-0.2.0 → optforge-0.3.0}/PKG-INFO +15 -43
- {optforge-0.2.0 → optforge-0.3.0}/README.md +11 -18
- {optforge-0.2.0 → optforge-0.3.0}/pyproject.toml +11 -12
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/__init__.py +1 -3
- optforge-0.3.0/src/optforge/_version.py +5 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/cli.py +2 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/core/cli.py +2 -2
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/core/console.py +6 -4
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/core/net.py +23 -13
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/core/processes.py +1 -1
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/core/version.py +3 -3
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/diskcheck/cli.py +2 -4
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/envcheck/cli.py +2 -4
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/envcheck/models.py +2 -2
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/envcheck/service.py +3 -10
- optforge-0.3.0/src/optforge/killNotif/__init__.py +7 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/killNotif/cli.py +2 -4
- optforge-0.3.0/src/optforge/killNotif/core.py +43 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/portcheck/cli.py +5 -15
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/portcheck/models.py +3 -3
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/portcheck/scanner.py +1 -3
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/portcheck/service.py +5 -2
- optforge-0.3.0/src/optforge/py.typed +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/pycacheClean/cleaner.py +6 -1
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/pycacheClean/cli.py +5 -13
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/pycacheClean/service.py +1 -3
- optforge-0.3.0/tests/test_cli.py +35 -0
- optforge-0.3.0/tests/test_console.py +45 -0
- optforge-0.3.0/tests/test_diskcheck.py +55 -0
- optforge-0.3.0/tests/test_envcheck.py +49 -0
- optforge-0.3.0/tests/test_formatting.py +21 -0
- optforge-0.3.0/tests/test_killnotif.py +46 -0
- optforge-0.3.0/tests/test_net.py +51 -0
- optforge-0.3.0/tests/test_portcheck.py +76 -0
- optforge-0.3.0/tests/test_pycacheclean.py +105 -0
- optforge-0.3.0/tests/test_redaction.py +29 -0
- optforge-0.3.0/tests/test_version.py +12 -0
- optforge-0.2.0/Makefile +0 -37
- optforge-0.2.0/src/optforge/killNotif/core.py +0 -24
- {optforge-0.2.0 → optforge-0.3.0}/LICENSE +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/__main__.py +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/core/__init__.py +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/core/errors.py +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/core/formatting.py +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/core/redaction.py +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/diskcheck/__init__.py +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/diskcheck/models.py +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/diskcheck/service.py +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/envcheck/__init__.py +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/killNotif/job.ps1 +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/portcheck/__init__.py +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/portcheck/binder.py +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/portcheck/connector.py +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/portcheck/waiter.py +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/pycacheClean/__init__.py +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/pycacheClean/models.py +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/pycacheClean/scanner.py +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/volume/__init__.py +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/volume/cli.py +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/src/optforge/volume/core.py +0 -0
- {optforge-0.2.0 → optforge-0.3.0}/tests/test_volume.py +0 -0
optforge-0.3.0/Makefile
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
.PHONY: help install lint format format-check typecheck test check build clean
|
|
2
|
+
|
|
3
|
+
UV ?= uv
|
|
4
|
+
|
|
5
|
+
help:
|
|
6
|
+
@echo "OptForge targets"
|
|
7
|
+
@echo " install Sync the virtual environment (uv sync --all-extras)"
|
|
8
|
+
@echo " lint Run ruff"
|
|
9
|
+
@echo " format Format with ruff"
|
|
10
|
+
@echo " format-check Verify formatting without changing files"
|
|
11
|
+
@echo " typecheck Run mypy"
|
|
12
|
+
@echo " test Run pytest"
|
|
13
|
+
@echo " check lint + format-check + typecheck + test"
|
|
14
|
+
@echo " build Build sdist and wheel"
|
|
15
|
+
@echo " clean Remove caches and build artifacts"
|
|
16
|
+
|
|
17
|
+
install:
|
|
18
|
+
$(UV) sync --all-extras
|
|
19
|
+
|
|
20
|
+
lint:
|
|
21
|
+
$(UV) run ruff check .
|
|
22
|
+
|
|
23
|
+
format:
|
|
24
|
+
$(UV) run ruff format .
|
|
25
|
+
|
|
26
|
+
format-check:
|
|
27
|
+
$(UV) run ruff format --check .
|
|
28
|
+
|
|
29
|
+
typecheck:
|
|
30
|
+
$(UV) run mypy src
|
|
31
|
+
|
|
32
|
+
test:
|
|
33
|
+
$(UV) run pytest
|
|
34
|
+
|
|
35
|
+
check: lint format-check typecheck test
|
|
36
|
+
|
|
37
|
+
build:
|
|
38
|
+
$(UV) build
|
|
39
|
+
|
|
40
|
+
clean:
|
|
41
|
+
$(UV) run python -c "import pathlib, shutil; [shutil.rmtree(p, ignore_errors=True) for p in pathlib.Path('.').rglob('__pycache__')]; [shutil.rmtree(pathlib.Path(p), ignore_errors=True) for p in ('.mypy_cache', '.ruff_cache', '.pytest_cache', 'dist', 'build', 'htmlcov')]; [pathlib.Path(p).unlink(missing_ok=True) for p in ('.coverage',)]"
|
|
@@ -1,46 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: optforge
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Cross-platform developer CLI tools for ports, environment variables, disk usage, and Python cache cleanup.
|
|
5
5
|
Project-URL: Homepage, https://github.com/cmd-3324/OptForge
|
|
6
6
|
Project-URL: Repository, https://github.com/cmd-3324/OptForge
|
|
7
7
|
Project-URL: Issues, https://github.com/cmd-3324/OptForge/issues
|
|
8
8
|
Author-email: Max Miller <ali.alipour4576@gmail.com>
|
|
9
|
-
License: MIT
|
|
10
|
-
|
|
11
|
-
Copyright (c) 2026 OptForge contributors
|
|
12
|
-
|
|
13
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
-
in the Software without restriction, including without limitation the rights
|
|
16
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
-
furnished to do so, subject to the following conditions:
|
|
19
|
-
|
|
20
|
-
The above copyright notice and this permission notice shall be included in all
|
|
21
|
-
copies or substantial portions of the Software.
|
|
22
|
-
|
|
23
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
-
SOFTWARE.
|
|
9
|
+
License-Expression: MIT
|
|
30
10
|
License-File: LICENSE
|
|
31
11
|
Keywords: cli,developer-tools,disk,environment,ports,pycache
|
|
32
12
|
Classifier: Development Status :: 4 - Beta
|
|
33
13
|
Classifier: Environment :: Console
|
|
34
14
|
Classifier: Intended Audience :: Developers
|
|
35
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
36
15
|
Classifier: Operating System :: OS Independent
|
|
37
16
|
Classifier: Programming Language :: Python :: 3
|
|
38
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
39
17
|
Classifier: Programming Language :: Python :: 3.14
|
|
40
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
41
19
|
Classifier: Topic :: System :: Systems Administration
|
|
42
20
|
Classifier: Typing :: Typed
|
|
43
|
-
Requires-Python: >=3.
|
|
21
|
+
Requires-Python: >=3.14.7
|
|
44
22
|
Requires-Dist: psutil>=6.0.0
|
|
45
23
|
Requires-Dist: pycaw>=20240210; sys_platform == 'win32'
|
|
46
24
|
Requires-Dist: rich>=13.7.0
|
|
@@ -50,6 +28,7 @@ Requires-Dist: mypy>=1.10.0; extra == 'dev'
|
|
|
50
28
|
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
|
|
51
29
|
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
52
30
|
Requires-Dist: ruff>=0.6.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: types-psutil>=6.0.0; extra == 'dev'
|
|
53
32
|
Description-Content-Type: text/markdown
|
|
54
33
|
|
|
55
34
|
# OptForge
|
|
@@ -126,50 +105,43 @@ Most tools are designed to be simple, fast, and cross-platform; `killNotif` is W
|
|
|
126
105
|
|
|
127
106
|
## Installation
|
|
128
107
|
|
|
129
|
-
|
|
108
|
+
Python 3.14.7 or newer is required.
|
|
109
|
+
|
|
110
|
+
### Install from PyPI with pipx
|
|
130
111
|
|
|
131
112
|
The recommended installation method is pipx:
|
|
132
113
|
|
|
133
114
|
```bash
|
|
134
|
-
pipx install
|
|
115
|
+
pipx install optforge
|
|
135
116
|
```
|
|
136
117
|
|
|
137
|
-
|
|
118
|
+
### Install from PyPI with pip
|
|
138
119
|
|
|
139
120
|
```bash
|
|
140
|
-
|
|
141
|
-
pipx install --editable .
|
|
121
|
+
python -m pip install optforge
|
|
142
122
|
```
|
|
143
123
|
|
|
144
|
-
### Install
|
|
124
|
+
### Install from a local clone
|
|
145
125
|
|
|
146
126
|
```bash
|
|
147
127
|
git clone https://github.com/cmd-3324/OptForge.git
|
|
148
128
|
cd OptForge
|
|
149
|
-
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### Download the latest Windows executable
|
|
153
|
-
|
|
154
|
-
You can download the latest Windows `.exe` release directly from GitHub Releases:
|
|
155
|
-
|
|
156
|
-
```text
|
|
157
|
-
https://github.com/cmd-3324/OptForge/releases/latest
|
|
129
|
+
pipx install --editable .
|
|
158
130
|
```
|
|
159
131
|
|
|
160
|
-
This is useful if you want to run OptForge without installing Python dependencies manually.
|
|
161
|
-
|
|
162
132
|
## Quick start
|
|
163
133
|
|
|
164
134
|
After installation, the package exposes the following commands:
|
|
165
135
|
|
|
166
136
|
```bash
|
|
137
|
+
optforge --help
|
|
167
138
|
portcheck --help
|
|
168
139
|
envcheck --help
|
|
169
140
|
diskcheck --help
|
|
170
141
|
pycache-clean --help
|
|
171
142
|
vol --help
|
|
172
143
|
killNotif --help # Windows 10/11 only
|
|
144
|
+
optforge killNotif --help
|
|
173
145
|
```
|
|
174
146
|
|
|
175
147
|
## Platform support
|
|
@@ -293,7 +265,7 @@ make test
|
|
|
293
265
|
|
|
294
266
|
## Requirements
|
|
295
267
|
|
|
296
|
-
- Python 3.
|
|
268
|
+
- Python 3.14.7+
|
|
297
269
|
- Typer
|
|
298
270
|
- Rich
|
|
299
271
|
- psutil
|
|
@@ -72,50 +72,43 @@ Most tools are designed to be simple, fast, and cross-platform; `killNotif` is W
|
|
|
72
72
|
|
|
73
73
|
## Installation
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
Python 3.14.7 or newer is required.
|
|
76
|
+
|
|
77
|
+
### Install from PyPI with pipx
|
|
76
78
|
|
|
77
79
|
The recommended installation method is pipx:
|
|
78
80
|
|
|
79
81
|
```bash
|
|
80
|
-
pipx install
|
|
82
|
+
pipx install optforge
|
|
81
83
|
```
|
|
82
84
|
|
|
83
|
-
|
|
85
|
+
### Install from PyPI with pip
|
|
84
86
|
|
|
85
87
|
```bash
|
|
86
|
-
|
|
87
|
-
pipx install --editable .
|
|
88
|
+
python -m pip install optforge
|
|
88
89
|
```
|
|
89
90
|
|
|
90
|
-
### Install
|
|
91
|
+
### Install from a local clone
|
|
91
92
|
|
|
92
93
|
```bash
|
|
93
94
|
git clone https://github.com/cmd-3324/OptForge.git
|
|
94
95
|
cd OptForge
|
|
95
|
-
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### Download the latest Windows executable
|
|
99
|
-
|
|
100
|
-
You can download the latest Windows `.exe` release directly from GitHub Releases:
|
|
101
|
-
|
|
102
|
-
```text
|
|
103
|
-
https://github.com/cmd-3324/OptForge/releases/latest
|
|
96
|
+
pipx install --editable .
|
|
104
97
|
```
|
|
105
98
|
|
|
106
|
-
This is useful if you want to run OptForge without installing Python dependencies manually.
|
|
107
|
-
|
|
108
99
|
## Quick start
|
|
109
100
|
|
|
110
101
|
After installation, the package exposes the following commands:
|
|
111
102
|
|
|
112
103
|
```bash
|
|
104
|
+
optforge --help
|
|
113
105
|
portcheck --help
|
|
114
106
|
envcheck --help
|
|
115
107
|
diskcheck --help
|
|
116
108
|
pycache-clean --help
|
|
117
109
|
vol --help
|
|
118
110
|
killNotif --help # Windows 10/11 only
|
|
111
|
+
optforge killNotif --help
|
|
119
112
|
```
|
|
120
113
|
|
|
121
114
|
## Platform support
|
|
@@ -239,7 +232,7 @@ make test
|
|
|
239
232
|
|
|
240
233
|
## Requirements
|
|
241
234
|
|
|
242
|
-
- Python 3.
|
|
235
|
+
- Python 3.14.7+
|
|
243
236
|
- Typer
|
|
244
237
|
- Rich
|
|
245
238
|
- psutil
|
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "optforge"
|
|
3
|
-
|
|
3
|
+
dynamic = ["version"]
|
|
4
4
|
description = "Cross-platform developer CLI tools for ports, environment variables, disk usage, and Python cache cleanup."
|
|
5
5
|
readme = "README.md"
|
|
6
|
-
license =
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
7
8
|
authors = [
|
|
8
9
|
{ name = "Max Miller", email = "ali.alipour4576@gmail.com" },
|
|
9
10
|
]
|
|
10
11
|
|
|
11
|
-
requires-python = ">=3.
|
|
12
|
+
requires-python = ">=3.14.7"
|
|
12
13
|
keywords = ["cli", "developer-tools", "ports", "environment", "disk", "pycache"]
|
|
13
14
|
classifiers = [
|
|
14
15
|
"Development Status :: 4 - Beta",
|
|
15
16
|
"Environment :: Console",
|
|
16
17
|
"Intended Audience :: Developers",
|
|
17
|
-
"License :: OSI Approved :: MIT License",
|
|
18
18
|
"Operating System :: OS Independent",
|
|
19
19
|
"Programming Language :: Python :: 3",
|
|
20
|
-
"Programming Language :: Python :: 3.13",
|
|
21
20
|
"Programming Language :: Python :: 3.14",
|
|
22
21
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
23
22
|
"Topic :: System :: Systems Administration",
|
|
@@ -36,6 +35,7 @@ dev = [
|
|
|
36
35
|
"pytest>=8.0.0",
|
|
37
36
|
"pytest-cov>=5.0.0",
|
|
38
37
|
"ruff>=0.6.0",
|
|
38
|
+
"types-psutil>=6.0.0",
|
|
39
39
|
]
|
|
40
40
|
|
|
41
41
|
[project.scripts]
|
|
@@ -54,9 +54,12 @@ Repository = "https://github.com/cmd-3324/OptForge"
|
|
|
54
54
|
Issues = "https://github.com/cmd-3324/OptForge/issues"
|
|
55
55
|
|
|
56
56
|
[build-system]
|
|
57
|
-
requires = ["hatchling>=1.
|
|
57
|
+
requires = ["hatchling>=1.33.0"]
|
|
58
58
|
build-backend = "hatchling.build"
|
|
59
59
|
|
|
60
|
+
[tool.hatch.version]
|
|
61
|
+
path = "src/optforge/_version.py"
|
|
62
|
+
|
|
60
63
|
[tool.hatch.build.targets.wheel]
|
|
61
64
|
packages = ["src/optforge"]
|
|
62
65
|
|
|
@@ -72,18 +75,14 @@ include = [
|
|
|
72
75
|
|
|
73
76
|
[tool.ruff]
|
|
74
77
|
line-length = 120
|
|
75
|
-
target-version = "
|
|
78
|
+
target-version = "py314"
|
|
76
79
|
src = ["src", "tests"]
|
|
77
80
|
|
|
78
81
|
[tool.ruff.lint]
|
|
79
82
|
select = ["E", "F", "I", "UP", "B", "SIM", "C4", "PIE", "RUF"]
|
|
80
|
-
ignore = ["B008"]
|
|
81
|
-
|
|
82
|
-
[tool.ruff.lint.per-file-ignores]
|
|
83
|
-
"tests/*" = ["S101"]
|
|
84
83
|
|
|
85
84
|
[tool.mypy]
|
|
86
|
-
python_version = "3.
|
|
85
|
+
python_version = "3.14"
|
|
87
86
|
files = ["src"]
|
|
88
87
|
check_untyped_defs = true
|
|
89
88
|
disallow_untyped_defs = true
|
|
@@ -9,6 +9,7 @@ import typer
|
|
|
9
9
|
from optforge.core.version import get_version
|
|
10
10
|
from optforge.diskcheck.cli import app as diskcheck_app
|
|
11
11
|
from optforge.envcheck.cli import app as envcheck_app
|
|
12
|
+
from optforge.killNotif.cli import app as killnotif_app
|
|
12
13
|
from optforge.portcheck.cli import app as portcheck_app
|
|
13
14
|
from optforge.pycacheclean.cli import app as pycacheclean_app
|
|
14
15
|
from optforge.volume.cli import app as volume_app
|
|
@@ -49,6 +50,7 @@ app.add_typer(envcheck_app, name="envcheck")
|
|
|
49
50
|
app.add_typer(diskcheck_app, name="diskcheck")
|
|
50
51
|
app.add_typer(pycacheclean_app, name="pycache-clean")
|
|
51
52
|
app.add_typer(pycacheclean_app, name="pycacheClean", hidden=True)
|
|
53
|
+
app.add_typer(killnotif_app, name="killNotif")
|
|
52
54
|
app.add_typer(volume_app, name="vol")
|
|
53
55
|
|
|
54
56
|
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from enum import IntEnum
|
|
6
6
|
from types import TracebackType
|
|
7
|
-
from typing import Self
|
|
7
|
+
from typing import Literal, Self
|
|
8
8
|
|
|
9
9
|
import typer
|
|
10
10
|
|
|
@@ -42,7 +42,7 @@ class ErrorBoundary:
|
|
|
42
42
|
exc_type: type[BaseException] | None,
|
|
43
43
|
exc: BaseException | None,
|
|
44
44
|
traceback: TracebackType | None,
|
|
45
|
-
) ->
|
|
45
|
+
) -> Literal[False]:
|
|
46
46
|
if exc is None:
|
|
47
47
|
return False
|
|
48
48
|
if isinstance(exc, PermissionDenied):
|
|
@@ -38,16 +38,18 @@ class ConsoleIO:
|
|
|
38
38
|
self.out.print(Text(str(value)))
|
|
39
39
|
|
|
40
40
|
def success(self, message: str) -> None:
|
|
41
|
-
self.out.print(
|
|
41
|
+
self.out.print(Text(message, style="green"))
|
|
42
42
|
|
|
43
43
|
def warning(self, message: str) -> None:
|
|
44
|
-
self.err.print(
|
|
44
|
+
self.err.print(Text(message, style="yellow"))
|
|
45
45
|
|
|
46
46
|
def error(self, message: str) -> None:
|
|
47
|
-
|
|
47
|
+
line = Text("error: ", style="bold red")
|
|
48
|
+
line.append(str(message))
|
|
49
|
+
self.err.print(line)
|
|
48
50
|
|
|
49
51
|
def hint(self, message: str) -> None:
|
|
50
|
-
self.err.print(
|
|
52
|
+
self.err.print(Text(message, style="dim"))
|
|
51
53
|
|
|
52
54
|
def json(self, data: Any) -> None:
|
|
53
55
|
"""Emit exact, machine-parseable JSON on stdout."""
|
|
@@ -9,8 +9,7 @@ each returned endpoint with the remaining time.
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
11
|
import socket
|
|
12
|
-
|
|
13
|
-
from concurrent.futures import TimeoutError as FuturesTimeoutError
|
|
12
|
+
import threading
|
|
14
13
|
from dataclasses import dataclass
|
|
15
14
|
from typing import Any
|
|
16
15
|
|
|
@@ -74,14 +73,25 @@ def resolve_endpoints(host: str, port: int, *, passive: bool = False) -> list[En
|
|
|
74
73
|
|
|
75
74
|
|
|
76
75
|
def resolve_endpoints_with_timeout(host: str, port: int, timeout: float) -> list[Endpoint]:
|
|
77
|
-
"""Resolve ``host`` while enforcing ``timeout`` on the DNS lookup.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
76
|
+
"""Resolve ``host`` while enforcing ``timeout`` on the DNS lookup.
|
|
77
|
+
|
|
78
|
+
The lookup runs in a daemon thread so a resolver that ignores its own
|
|
79
|
+
deadline cannot block interpreter shutdown.
|
|
80
|
+
"""
|
|
81
|
+
endpoints: list[Endpoint] = []
|
|
82
|
+
failures: list[BaseException] = []
|
|
83
|
+
|
|
84
|
+
def worker() -> None:
|
|
85
|
+
try:
|
|
86
|
+
endpoints.extend(resolve_endpoints(host, port))
|
|
87
|
+
except BaseException as exc:
|
|
88
|
+
failures.append(exc)
|
|
89
|
+
|
|
90
|
+
thread = threading.Thread(target=worker, name="optforge-dns", daemon=True)
|
|
91
|
+
thread.start()
|
|
92
|
+
thread.join(timeout)
|
|
93
|
+
if thread.is_alive():
|
|
94
|
+
raise OperationFailed(f"Timed out after {timeout:g}s while resolving {host!r}.")
|
|
95
|
+
if failures:
|
|
96
|
+
raise failures[0]
|
|
97
|
+
return endpoints
|
|
@@ -29,7 +29,7 @@ class ProcessNameResolver:
|
|
|
29
29
|
return cached
|
|
30
30
|
try:
|
|
31
31
|
resolved = self._factory(pid).name()
|
|
32
|
-
except
|
|
32
|
+
except psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess:
|
|
33
33
|
resolved = _UNKNOWN
|
|
34
34
|
self._cache[pid] = resolved
|
|
35
35
|
return resolved
|
|
@@ -4,12 +4,12 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from importlib.metadata import PackageNotFoundError, version
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
from optforge._version import __version__
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def get_version(distribution: str = "optforge") -> str:
|
|
11
|
-
"""Return the installed distribution version, or the source-tree
|
|
11
|
+
"""Return the installed distribution version, or the source-tree version."""
|
|
12
12
|
try:
|
|
13
13
|
return version(distribution)
|
|
14
14
|
except PackageNotFoundError:
|
|
15
|
-
return
|
|
15
|
+
return __version__
|
|
@@ -19,7 +19,7 @@ app = typer.Typer(
|
|
|
19
19
|
help="Inspect disk usage and warn when storage pressure crosses a threshold.",
|
|
20
20
|
no_args_is_help=False,
|
|
21
21
|
rich_markup_mode="rich",
|
|
22
|
-
context_settings={"help_option_names": ["-h", "--help"]},
|
|
22
|
+
context_settings={"help_option_names": ["-h", "--help"], "allow_interspersed_args": True},
|
|
23
23
|
)
|
|
24
24
|
console = ConsoleIO()
|
|
25
25
|
|
|
@@ -102,9 +102,7 @@ def main(
|
|
|
102
102
|
over_threshold = warn_at is not None and any(info.percent_used >= warn_at for info in infos)
|
|
103
103
|
if over_threshold:
|
|
104
104
|
if not as_json:
|
|
105
|
-
console.warning(
|
|
106
|
-
f"Storage pressure: at least one volume is at or above {warn_at:g}% used."
|
|
107
|
-
)
|
|
105
|
+
console.warning(f"Storage pressure: at least one volume is at or above {warn_at:g}% used.")
|
|
108
106
|
raise typer.Exit(ExitCode.NO)
|
|
109
107
|
raise typer.Exit(ExitCode.OK)
|
|
110
108
|
|
|
@@ -18,7 +18,7 @@ app = typer.Typer(
|
|
|
18
18
|
help="Inspect environment variables and runtime configuration. Secrets are redacted by default.",
|
|
19
19
|
no_args_is_help=False,
|
|
20
20
|
rich_markup_mode="rich",
|
|
21
|
-
context_settings={"help_option_names": ["-h", "--help"]},
|
|
21
|
+
context_settings={"help_option_names": ["-h", "--help"], "allow_interspersed_args": True},
|
|
22
22
|
)
|
|
23
23
|
console = ConsoleIO()
|
|
24
24
|
|
|
@@ -67,9 +67,7 @@ def main(
|
|
|
67
67
|
OutputFormat,
|
|
68
68
|
typer.Option("--format", "-f", case_sensitive=False, help="Output format."),
|
|
69
69
|
] = OutputFormat.table,
|
|
70
|
-
as_json: Annotated[
|
|
71
|
-
bool, typer.Option("--json", help="Alias for --format json.")
|
|
72
|
-
] = False,
|
|
70
|
+
as_json: Annotated[bool, typer.Option("--json", help="Alias for --format json.")] = False,
|
|
73
71
|
version: Annotated[
|
|
74
72
|
bool | None,
|
|
75
73
|
typer.Option(
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
-
from enum import
|
|
6
|
+
from enum import StrEnum
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class OutputFormat(
|
|
10
|
+
class OutputFormat(StrEnum):
|
|
11
11
|
"""Supported ``envcheck`` output formats."""
|
|
12
12
|
|
|
13
13
|
table = "table"
|
|
@@ -8,7 +8,7 @@ from collections.abc import Mapping
|
|
|
8
8
|
from optforge.core.redaction import SecretRedactor
|
|
9
9
|
from optforge.envcheck.models import EnvEntry
|
|
10
10
|
|
|
11
|
-
_QUOTE_TRIGGERS = frozenset(
|
|
11
|
+
_QUOTE_TRIGGERS = frozenset(" \t\n#\"'")
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class EnvironmentService:
|
|
@@ -43,9 +43,7 @@ class EnvironmentService:
|
|
|
43
43
|
redacted = False
|
|
44
44
|
if redact and sensitive:
|
|
45
45
|
value, redacted = self._redactor.redact(key, raw)
|
|
46
|
-
entries.append(
|
|
47
|
-
EnvEntry(key=key, value=value, sensitive=sensitive, redacted=redacted)
|
|
48
|
-
)
|
|
46
|
+
entries.append(EnvEntry(key=key, value=value, sensitive=sensitive, redacted=redacted))
|
|
49
47
|
return entries
|
|
50
48
|
|
|
51
49
|
@staticmethod
|
|
@@ -57,10 +55,5 @@ class EnvironmentService:
|
|
|
57
55
|
def _dotenv_line(entry: EnvEntry) -> str:
|
|
58
56
|
if not entry.value or not (set(entry.value) & _QUOTE_TRIGGERS):
|
|
59
57
|
return f"{entry.key}={entry.value}"
|
|
60
|
-
escaped = (
|
|
61
|
-
entry.value.replace("\\", "\\\\")
|
|
62
|
-
.replace('"', '\\"')
|
|
63
|
-
.replace("\n", "\\n")
|
|
64
|
-
.replace("\t", "\\t")
|
|
65
|
-
)
|
|
58
|
+
escaped = entry.value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n").replace("\t", "\\t")
|
|
66
59
|
return f'{entry.key}="{escaped}"'
|
|
@@ -38,9 +38,7 @@ AumidOpt = Annotated[
|
|
|
38
38
|
help="Only clear notifications from this AppUserModelID.",
|
|
39
39
|
),
|
|
40
40
|
]
|
|
41
|
-
QuietOpt = Annotated[
|
|
42
|
-
bool, typer.Option("--quiet", "-q", help="Print nothing; use the exit code only.")
|
|
43
|
-
]
|
|
41
|
+
QuietOpt = Annotated[bool, typer.Option("--quiet", "-q", help="Print nothing; use the exit code only.")]
|
|
44
42
|
JsonOpt = Annotated[bool, typer.Option("--json", help="Print machine-readable JSON.")]
|
|
45
43
|
|
|
46
44
|
|
|
@@ -71,7 +69,7 @@ def _run(aumid: str | None) -> int:
|
|
|
71
69
|
raise OperationFailed(f"unexpected helper output: {exc}") from exc
|
|
72
70
|
|
|
73
71
|
|
|
74
|
-
@app.
|
|
72
|
+
@app.callback(invoke_without_command=True)
|
|
75
73
|
def main(
|
|
76
74
|
aumid: AumidOpt = None,
|
|
77
75
|
json_output: JsonOpt = False,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Run the bundled PowerShell helper that clears Windows toast history."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
JOB = Path(__file__).with_name("job.ps1")
|
|
9
|
+
POWERSHELL_TIMEOUT_SECONDS = 30
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def clear_notifications(aumid: str | None = None) -> int:
|
|
13
|
+
"""Clear all notifications (or one sender's). Returns senders cleared."""
|
|
14
|
+
if not JOB.exists():
|
|
15
|
+
raise FileNotFoundError(f"helper script missing: {JOB}")
|
|
16
|
+
|
|
17
|
+
cmd = [
|
|
18
|
+
"powershell",
|
|
19
|
+
"-NoProfile",
|
|
20
|
+
"-NonInteractive",
|
|
21
|
+
"-ExecutionPolicy",
|
|
22
|
+
"RemoteSigned",
|
|
23
|
+
"-File",
|
|
24
|
+
str(JOB),
|
|
25
|
+
]
|
|
26
|
+
if aumid:
|
|
27
|
+
cmd += ["-Aumid", aumid]
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
res = subprocess.run(
|
|
31
|
+
cmd,
|
|
32
|
+
capture_output=True,
|
|
33
|
+
text=True,
|
|
34
|
+
timeout=POWERSHELL_TIMEOUT_SECONDS,
|
|
35
|
+
)
|
|
36
|
+
except subprocess.TimeoutExpired as exc:
|
|
37
|
+
raise RuntimeError(f"PowerShell timed out after {POWERSHELL_TIMEOUT_SECONDS}s.") from exc
|
|
38
|
+
|
|
39
|
+
if res.returncode != 0:
|
|
40
|
+
raise RuntimeError((res.stderr or res.stdout).strip() or "job.ps1 failed")
|
|
41
|
+
|
|
42
|
+
out = res.stdout.strip().splitlines()[-1] if res.stdout.strip() else "0"
|
|
43
|
+
return int(out)
|