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.
Files changed (63) hide show
  1. {optforge-0.2.0 → optforge-0.3.0}/.gitignore +4 -0
  2. optforge-0.3.0/Makefile +41 -0
  3. {optforge-0.2.0 → optforge-0.3.0}/PKG-INFO +15 -43
  4. {optforge-0.2.0 → optforge-0.3.0}/README.md +11 -18
  5. {optforge-0.2.0 → optforge-0.3.0}/pyproject.toml +11 -12
  6. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/__init__.py +1 -3
  7. optforge-0.3.0/src/optforge/_version.py +5 -0
  8. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/cli.py +2 -0
  9. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/core/cli.py +2 -2
  10. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/core/console.py +6 -4
  11. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/core/net.py +23 -13
  12. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/core/processes.py +1 -1
  13. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/core/version.py +3 -3
  14. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/diskcheck/cli.py +2 -4
  15. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/envcheck/cli.py +2 -4
  16. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/envcheck/models.py +2 -2
  17. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/envcheck/service.py +3 -10
  18. optforge-0.3.0/src/optforge/killNotif/__init__.py +7 -0
  19. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/killNotif/cli.py +2 -4
  20. optforge-0.3.0/src/optforge/killNotif/core.py +43 -0
  21. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/portcheck/cli.py +5 -15
  22. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/portcheck/models.py +3 -3
  23. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/portcheck/scanner.py +1 -3
  24. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/portcheck/service.py +5 -2
  25. optforge-0.3.0/src/optforge/py.typed +0 -0
  26. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/pycacheClean/cleaner.py +6 -1
  27. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/pycacheClean/cli.py +5 -13
  28. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/pycacheClean/service.py +1 -3
  29. optforge-0.3.0/tests/test_cli.py +35 -0
  30. optforge-0.3.0/tests/test_console.py +45 -0
  31. optforge-0.3.0/tests/test_diskcheck.py +55 -0
  32. optforge-0.3.0/tests/test_envcheck.py +49 -0
  33. optforge-0.3.0/tests/test_formatting.py +21 -0
  34. optforge-0.3.0/tests/test_killnotif.py +46 -0
  35. optforge-0.3.0/tests/test_net.py +51 -0
  36. optforge-0.3.0/tests/test_portcheck.py +76 -0
  37. optforge-0.3.0/tests/test_pycacheclean.py +105 -0
  38. optforge-0.3.0/tests/test_redaction.py +29 -0
  39. optforge-0.3.0/tests/test_version.py +12 -0
  40. optforge-0.2.0/Makefile +0 -37
  41. optforge-0.2.0/src/optforge/killNotif/core.py +0 -24
  42. {optforge-0.2.0 → optforge-0.3.0}/LICENSE +0 -0
  43. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/__main__.py +0 -0
  44. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/core/__init__.py +0 -0
  45. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/core/errors.py +0 -0
  46. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/core/formatting.py +0 -0
  47. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/core/redaction.py +0 -0
  48. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/diskcheck/__init__.py +0 -0
  49. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/diskcheck/models.py +0 -0
  50. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/diskcheck/service.py +0 -0
  51. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/envcheck/__init__.py +0 -0
  52. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/killNotif/job.ps1 +0 -0
  53. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/portcheck/__init__.py +0 -0
  54. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/portcheck/binder.py +0 -0
  55. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/portcheck/connector.py +0 -0
  56. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/portcheck/waiter.py +0 -0
  57. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/pycacheClean/__init__.py +0 -0
  58. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/pycacheClean/models.py +0 -0
  59. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/pycacheClean/scanner.py +0 -0
  60. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/volume/__init__.py +0 -0
  61. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/volume/cli.py +0 -0
  62. {optforge-0.2.0 → optforge-0.3.0}/src/optforge/volume/core.py +0 -0
  63. {optforge-0.2.0 → optforge-0.3.0}/tests/test_volume.py +0 -0
@@ -7,6 +7,10 @@ wheels/
7
7
  *.egg-info
8
8
  .ruff_cache
9
9
  .mypy_cache
10
+ .pytest_cache
11
+ .coverage
12
+ htmlcov/
10
13
  # Virtual environments
11
14
  .venv
12
15
  .vscode/
16
+ .idea/
@@ -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.2.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 License
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.13
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
- ### Install with pipx
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 git+https://github.com/cmd-3324/OptForge.git
115
+ pipx install optforge
135
116
  ```
136
117
 
137
- If you are working from a local clone:
118
+ ### Install from PyPI with pip
138
119
 
139
120
  ```bash
140
- cd OptForge
141
- pipx install --editable .
121
+ python -m pip install optforge
142
122
  ```
143
123
 
144
- ### Install with pip
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
- python -m pip install .
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.13+
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
- ### Install with pipx
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 git+https://github.com/cmd-3324/OptForge.git
82
+ pipx install optforge
81
83
  ```
82
84
 
83
- If you are working from a local clone:
85
+ ### Install from PyPI with pip
84
86
 
85
87
  ```bash
86
- cd OptForge
87
- pipx install --editable .
88
+ python -m pip install optforge
88
89
  ```
89
90
 
90
- ### Install with pip
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
- python -m pip install .
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.13+
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
- version = "0.2.0"
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 = { file = "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.13"
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.24.0"]
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 = "py313"
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.13"
85
+ python_version = "3.14"
87
86
  files = ["src"]
88
87
  check_untyped_defs = true
89
88
  disallow_untyped_defs = true
@@ -2,9 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from optforge.core.version import get_version
6
-
7
- __version__ = get_version()
5
+ from optforge._version import __version__
8
6
 
9
7
 
10
8
  def main() -> None:
@@ -0,0 +1,5 @@
1
+ """Single source of truth for the package version."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __version__ = "0.3.0"
@@ -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
- ) -> bool:
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(f"[green]{message}[/green]")
41
+ self.out.print(Text(message, style="green"))
42
42
 
43
43
  def warning(self, message: str) -> None:
44
- self.err.print(f"[yellow]{message}[/yellow]")
44
+ self.err.print(Text(message, style="yellow"))
45
45
 
46
46
  def error(self, message: str) -> None:
47
- self.err.print(f"[red]error:[/red] {message}")
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(f"[dim]{message}[/dim]")
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
- from concurrent.futures import ThreadPoolExecutor
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
- executor = ThreadPoolExecutor(max_workers=1, thread_name_prefix="optforge-dns")
79
- future = executor.submit(resolve_endpoints, host, port)
80
- try:
81
- return future.result(timeout=timeout)
82
- except FuturesTimeoutError as exc:
83
- raise OperationFailed(
84
- f"Timed out after {timeout:g}s while resolving {host!r}."
85
- ) from exc
86
- finally:
87
- executor.shutdown(wait=False, cancel_futures=True)
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 (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
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
- FALLBACK_VERSION = "0.2.0"
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 fallback."""
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 FALLBACK_VERSION
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 Enum
6
+ from enum import StrEnum
7
7
  from typing import Any
8
8
 
9
9
 
10
- class OutputFormat(str, Enum):
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(' \t\n#"\'')
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}"'
@@ -0,0 +1,7 @@
1
+ """Windows toast notification cleanup."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from optforge.killNotif.core import clear_notifications
6
+
7
+ __all__ = ["clear_notifications"]
@@ -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.command()
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)